Skip to content

Commit

Permalink
juno6.py: Add modified state persistance via midi.py patch_state.
Browse files Browse the repository at this point in the history
  • Loading branch information
dpwe committed May 28, 2024
1 parent d031509 commit 38e3dc0
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 15 deletions.
51 changes: 38 additions & 13 deletions tulip/shared/py/juno6.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,24 @@ def set_value(self, value):

import juno, midi

midi_channel = 1
juno_patch_for_midi_channel = {}
# Midi channel 0 is not accessible, it's used to store "initial state" juno. Actual UI switches to a real MIDI channel (1 up) once the UI is up.
midi_channel = 0
juno_patch_for_midi_channel = {0: juno.JunoPatch.from_patch_number(0)}


def hexify(bytelist):
return ' '.join('%02x' % b for b in bytelist)


def set_patch_with_state(jp, patch_num, midi_channel):
"""Mutates juno_patch in-place to use patch_num, or override by state for midi_chan if any."""
jp.set_patch(patch_num)
# Maybe this channel has existing modified state?
state = midi.config.get_channel_state(midi_channel)
if state:
#print('Got existing state for channel %d: %s' % (m_channel, hexify(state)))
jp.set_sysex(state)


# Get the patch to channel mapping and the AMY voices from midi.patch_map if set
for m_channel in range(1, 17):
Expand All @@ -310,7 +326,7 @@ def set_value(self, value):
if patch_num is not None and patch_num < 128:
jp = juno.JunoPatch() # .from_patch_number(patch_num)
jp.set_voices(amy_voices)
jp.set_patch(patch_num)
set_patch_with_state(jp, patch_num, m_channel)
juno_patch_for_midi_channel[m_channel] = jp

def current_juno():
Expand All @@ -322,12 +338,10 @@ def current_juno():
return None



# I think we need to ensure current_juno() is init'ed to support the jcb callbacks during UI construction.
current_juno().init_AMY()


#current_juno().init_AMY()

# Make the callback function.
def jcb(arg):
callback = lambda x: current_juno().set_param(arg, x)
Expand Down Expand Up @@ -409,18 +423,16 @@ def setup_from_patch(patch):

def setup_from_patch_number(patch_number):
global midi_channel
#print("resetting patch number to %d" % (patch_number))

# See how many voices are allocated going in.
_, amy_voices = midi.config.channel_info(midi_channel)
#print("channel", midi_channel, "voices", amy_voices)
# Use no fewer than 4.
polyphony = max(4, len(amy_voices))
num_amy_voices = 0 if amy_voices == None else len(amy_voices)
polyphony = max(4, num_amy_voices)
music_map(midi_channel, patch_number, polyphony)
_, amy_voices = midi.config.channel_info(midi_channel)
jp = juno.JunoPatch() #.from_patch_number(patch_number)
jp.set_voices(amy_voices)
jp.set_patch(patch_number)
set_patch_with_state(jp, patch_number, midi_channel)
juno_patch_for_midi_channel[midi_channel] = jp

#current_juno().patch_number = patch_number
Expand All @@ -430,12 +442,22 @@ def setup_from_patch_number(patch_number):
def setup_from_midi_chan(new_midi_channel):
"""Switch which JunoPatch we display based on MIDI channel."""
global midi_channel
#print("setup chan %d" % (new_midi_channel))
if new_midi_channel == midi_channel:
#print("midi channel %d is already selected" % midi_channel)
return
old_midi_channel = midi_channel # In case we need to unwind.
# Store state of current channel
state = current_juno().to_sysex()
#print("setup_from_midi: saving state for channel %d: %s" % (midi_channel, hexify(state)))
midi.config.set_channel_state(midi_channel, state)
midi_channel = (new_midi_channel)
new_patch = current_juno()
if(new_patch == None):
print("No Juno on midi channel %d" % midi_channel)
patch_selector.set_text("None assigned")
patch_selector.value = -1
patch_selector.value = -1
# Unwind
midi_channel = old_midi_channel
else:
#print("new patch patch is %d" % (new_patch.patch_number))
new_patch.init_AMY()
Expand Down Expand Up @@ -539,6 +561,9 @@ def midi_event_cb(x):


def quit(screen):
state = current_juno().to_sysex()
#print("quit: saving state for channel %d: %s" % (midi_channel, hexify(state)))
midi.config.set_channel_state(midi_channel, state)
midi_remove_callback(midi_event_cb)

def run(screen):
Expand Down
13 changes: 13 additions & 0 deletions tulip/shared/py/midi.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ def channel_info(self, channel):
self.synth_per_channel[channel].amy_voices,
)

def get_channel_state(self, channel):
if channel not in self.synth_per_channel:
return None
return self.synth_per_channel[channel].patch_state

def set_channel_state(self, channel, state):
if channel not in self.synth_per_channel:
raise ValueError('Attempting to set state for unallocated channel %d.' % channel)
self.synth_per_channel[channel].patch_state = state

def voices_for_channel(self, channel):
"""Return a list of AMY voices assigned to a channel."""
if channel not in self.synth_per_channel:
Expand Down Expand Up @@ -202,6 +212,7 @@ class Synth:
Provides read-back attributes (for voices.py UI):
synth.amy_voices
synth.patch_number
synth.patch_state - patch-specific data only used by clients e.g. UI state
Argument voice_source provides the following methods:
voice_source.get_new_voices(num_voices) returns num_voices VoiceObjects.
Expand Down Expand Up @@ -233,6 +244,7 @@ def __init__(self, voice_source, num_voices=6):
# Fields used by UI
#self.num_voices = num_voices
self.patch_number = None
self.patch_state = None

@property
def amy_voices(self):
Expand Down Expand Up @@ -356,6 +368,7 @@ def __init__(self, num_voices=10):
# Fields used by UI
self.amy_voices = self.oscs # Actually osc numbers not amy voices.
self.patch_number = 0
self.patch_state = None

def note_on(self, note, velocity, time=None):
osc = self.oscs[self.next_osc]
Expand Down
3 changes: 1 addition & 2 deletions tulip/shared/py/voices.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# voices.py
# drums.py
# lvgl drum machine for Tulip
# lvgl MIDI patch setting + arpeggiator for Tulip.

import tulip
import midi
Expand Down

0 comments on commit 38e3dc0

Please sign in to comment.