diff --git a/amy_headers.py b/amy_headers.py index 591b171..1ac7aa0 100644 --- a/amy_headers.py +++ b/amy_headers.py @@ -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 diff --git a/juno.py b/juno.py index f478282..d2b6e51 100644 --- a/juno.py +++ b/juno.py @@ -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 = "" @@ -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 @@ -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): @@ -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.""" @@ -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 @@ -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 @@ -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))) @@ -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), @@ -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 @@ -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)