Skip to content

Commit

Permalink
juno.py modified to respect voices=.. (for tulip juno6 app).
Browse files Browse the repository at this point in the history
  • Loading branch information
dpwe committed Apr 17, 2024
1 parent 064cb7c commit 0acb3cd
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 108 deletions.
6 changes: 2 additions & 4 deletions amy_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,10 +363,8 @@ def nothing(message):
# Do juno
for i in range(128):
amy.log_patch()
p = juno.JunoPatch()
j = p.from_patch_number(i)
j.base_oscs = list()
v = j.get_new_voices(1)
j = juno.JunoPatch()
j.set_patch(i)
f.write("\t/* %d: Juno %s */ \"%s\",\n" % (i, j.name, amy.retrieve_patch()))
num_oscs.append(5)
# Do dx7
Expand Down
133 changes: 29 additions & 104 deletions juno.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,20 +151,6 @@ def get_juno_patch(patch_number):
return name, bytes(sysex)


class NoteObj:
"""Object to receive note_on(note, velocity) messages."""

def __init__(self, osc):
self.osc = osc

def note_on(self, note, velocity, time=None):
self.note = note
amy.send(osc=self.osc, note=note, vel=velocity, time=time)

def note_off(self, time=None):
amy.send(osc=self.osc, vel=0, time=time)


class JunoPatch:
"""Encapsulates information in a Juno Patch."""
name = ""
Expand Down Expand Up @@ -210,12 +196,6 @@ class JunoPatch:
BITS1 = ['stop_16', 'stop_8', 'stop_4', 'pulse', 'saw']
BITS2 = ['pwm_manual', 'vcf_neg', 'vca_gate']

# Attributes for voice management.
next_osc = 0
# How many AMY oscs are used per voice?
oscs_per_voice = 5
# List of base_oscs for allocated voices.
base_oscs = []
# Patch number we're based on, if any.
patch_number = None
# Name, if any
Expand All @@ -228,6 +208,8 @@ class JunoPatch:
sub_freq = '440'
# List of the 5 basic oscs that need cloning.
oscs_to_clone = set()
# Amy voices
amy_voices = list()

@staticmethod
def from_patch_number(patch_number):
Expand Down Expand Up @@ -263,8 +245,8 @@ def _init_from_sysex(self, sysexbytes):
# Bits 3 & 4 also have flipped endianness & sense.
setattr(self, 'hpf', [3, 2, 1, 0][int(sysexbytes[17]) >> 3])

def __init__(self, base_osc=0):
self.next_osc = base_osc
def set_voices(self, amy_voices):
self.amy_voices = amy_voices

def _breakpoint_string(self):
"""Format a breakpoint string from the ADSR parameters reaching a peak."""
Expand All @@ -275,7 +257,7 @@ def _breakpoint_string(self):

def init_AMY(self):
"""Output AMY commands to set up patches on all the allocated voices.
Send amy.send(osc=base_osc, note=50, vel=1) afterwards."""
Send amy.send(osc=0, note=50, vel=1) afterwards."""
#amy.reset()
# base_osc is pulse/PWM
# base_osc + 1 is SAW
Expand All @@ -284,7 +266,7 @@ def init_AMY(self):
# base_osc + 4 is LFO
# env0 is VCA
# env1 is VCF
# These are offsets from the base_oscs[].
# These are the canonical oscs (to add to amy.send(voices=..)).
self.pwm_osc = 0
self.saw_osc = 1
self.sub_osc = 2
Expand All @@ -303,25 +285,8 @@ def init_AMY(self):
self.update_vcf()
self.update_env()
self.update_cho()
self.init_voices()

def init_voices(self):
"""Having set up the base set of oscs, copy to the other voices."""
# Assume base_oscs[0] is configured, setup the remainder.
if self.base_oscs:
base_osc = self.base_oscs[0]
self.clone_oscs()
for other_base_osc in self.base_oscs[1:]:
for osc in self.voice_oscs + [self.lfo_osc]:
proto_osc = base_osc + osc
target_osc = other_base_osc + osc
amy.send(osc=target_osc, clone_osc=proto_osc)
# chained_osc is cleared by clone_osc, set them up.
for i in range(len(self.voice_oscs) - 1):
# The first 3 voice oscs chain to the next one
amy.send(osc=other_base_osc + self.voice_oscs[i],
chained_osc=other_base_osc + self.voice_oscs[i + 1])

self.clone_oscs()

def _amp_coef_string(self, level):
return '0,0,%s,1,0,0' % ffmt(max(.001, to_level(level) * to_level(self.vca_level)))

Expand All @@ -331,27 +296,24 @@ def _freq_coef_string(self, base_freq):

def clone_oscs(self):
"""Clone modifications on osc 0 to remaining oscs, then fixup."""
if not self.base_oscs:
return
base_osc = self.base_oscs[0]
clone_osc = base_osc + self.pwm_osc
clone_osc = self.pwm_osc
# Make the clones have their filters turned off.
amy.send(osc=clone_osc, filter_type=amy.FILTER_NONE)
amy.send(osc=base_osc + self.saw_osc, clone_osc=clone_osc)
amy.send(osc=base_osc + self.saw_osc, wave=amy.SAW_UP,
self.amy_send(osc=clone_osc, filter_type=amy.FILTER_NONE)
self.amy_send(osc=self.saw_osc, clone_osc=clone_osc)
self.amy_send(osc=self.saw_osc, wave=amy.SAW_UP,
amp=self._amp_coef_string(float(self.saw)),
chained_osc=base_osc + self.sub_osc)
amy.send(osc=base_osc + self.sub_osc, clone_osc=clone_osc)
amy.send(osc=base_osc + self.sub_osc, wave=amy.PULSE,
chained_osc=self.sub_osc)
self.amy_send(osc=self.sub_osc, clone_osc=clone_osc)
self.amy_send(osc=self.sub_osc, wave=amy.PULSE,
amp=self._amp_coef_string(self.dco_sub),
freq=self.sub_freq,
chained_osc=base_osc + self.nse_osc)
amy.send(osc=base_osc + self.nse_osc, clone_osc=clone_osc)
amy.send(osc=base_osc + self.nse_osc, wave=amy.NOISE,
chained_osc=self.nse_osc)
self.amy_send(osc=self.nse_osc, clone_osc=clone_osc)
self.amy_send(osc=self.nse_osc, wave=amy.NOISE,
amp=self._amp_coef_string(self.dco_noise)) # No chained_osc.
# Re-enable the filter on PWM osc. Also, enable its chaining.
amy.send(osc=clone_osc, filter_type=amy.FILTER_LPF24,
chained_osc=base_osc + self.saw_osc)
self.amy_send(osc=clone_osc, filter_type=amy.FILTER_LPF24,
chained_osc=self.saw_osc)

def update_lfo(self):
lfo_args = {'freq': to_lfo_freq(self.lfo_rate),
Expand Down Expand Up @@ -433,8 +395,7 @@ def update_cho(self):
# We choose juno 60-style I+II. Juno 6-style would be freq=8 depth=0.25
chorus_args['chorus_freq'] = 1
chorus_args['chorus_depth'] = 0.08
#self.amy_send(osc=amy.CHORUS_OSC, **chorus_args)
# *Don't* repeat for all the notes, these ones are global.
# *Don't* send to oscs, these ones are global.
amy.send(**chorus_args)

# Setters for each Juno UI control
Expand All @@ -449,62 +410,26 @@ def set_param(self, param, val):
getattr(self, 'update_' + group)()
if self.recloning_needed:
self.clone_oscs()
self.update_voices()

def send_deferred_params(self):
for group, params in self.post_set_fn.items():
if self.dirty_params.intersection(params):
getattr(self, 'update_' + group)()
self.clone_oscs()
self.update_voices()
self.dirty_params = set()
self.defer_param_updates = False

def amy_send(self, osc, **kwargs):
if self.base_oscs:
base_osc = self.base_oscs[0]
# Adjust relative args.
offset_args = dict(kwargs)
for relative_arg in ['mod_source', 'chained_osc']:
if relative_arg in offset_args:
offset_args[relative_arg] += base_osc
# Apply configuration in full to first voice.
amy.send(osc=base_osc + osc, **kwargs)
self.oscs_to_clone.add(osc)

def update_voices(self):
# Assume base_oscs[0] is configured, setup the remaining base_osc voices.
if self.base_oscs:
base_osc = self.base_oscs[0]
for other_base_osc in self.base_oscs[1:]:
if self.pwm_osc in self.oscs_to_clone:
self.oscs_to_clone.add(self.saw_osc)
self.oscs_to_clone.add(self.sub_osc)
self.oscs_to_clone.add(self.nse_osc)
for osc in self.oscs_to_clone:
proto_osc = base_osc + osc
target_osc = other_base_osc + osc
amy.send(osc=target_osc, clone_osc=proto_osc)
# First 3 oscs must be chained to successors.
if osc in [self.pwm_osc, self.saw_osc, self.sub_osc]:
amy.send(osc=target_osc, chained_osc=target_osc + 1)
self.oscs_to_clone = set()

def get_new_voices(self, num_voices):
"""Setup a bunch of secondary voices."""
note_objs = []
for voice_num in range(num_voices):
base_osc = self.next_osc
self.next_osc += self.oscs_per_voice
self.base_oscs.append(base_osc)
note_objs.append(NoteObj(base_osc))
self.init_AMY()
return note_objs

def set_patch(self, patch):
self._init_from_patch_number(patch)
print("New patch", patch, ":", self.name)
#print("New patch", patch, ":", self.name)
self.init_AMY()

def set_pitch_bend(self, value):
# Global, not osc-specific.
amy.send(pitch_bend=value)

def amy_send(self, osc, **kwargs):
voices_str = None
if self.amy_voices:
voices_str = ','.join([str(v) for v in self.amy_voices])
amy.send(voices=voices_str, osc=osc, **kwargs)

0 comments on commit 0acb3cd

Please sign in to comment.