Skip to content

Commit

Permalink
fs/app/juno_ui: Update, incl pitch_bend.
Browse files Browse the repository at this point in the history
  • Loading branch information
dpwe committed Feb 19, 2024
1 parent 93265fb commit 8752660
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 28 deletions.
31 changes: 19 additions & 12 deletions tulip/fs/app/juno_ui/juno.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,15 @@ def to_decay_time(val):
# return np.log(0.05) / np.log(0.5) * time
# from Arturia video
#return 80 * exp2(0.066 * val * 127) - 80
return 80 * exp2(0.09 * val * 127) - 80
return 80 * exp2(0.085 * val * 127) - 80


def to_release_time(val):
"""Convert a midi value (0..127) to a time for ADSR."""
#time = 100 * np.exp(np.log(16) * midi/100)
#return np.log(0.05) / np.log(0.5) * time
# from Arturia video
#return 70 * exp2(0.066 * val * 127) - 70
return 70 * exp2(0.066 * val * 127) - 70


Expand All @@ -105,7 +106,7 @@ def level_to_amp(level):
def to_lfo_freq(val):
# LFO frequency in Hz varies from 0.5 to 30
# from Arturia video
return float("%.3f" % (0.6 * exp2(0.042 * val * 127) - 0.1))
return float("%.3f" % (0.6 * exp2(0.04 * val * 127) - 0.1))


def to_lfo_delay(val):
Expand All @@ -118,15 +119,17 @@ def to_lfo_delay(val):

def to_resonance(val):
# Q goes from 0.5 to 16 exponentially
return float("%.3f" % (0.5 * exp2(5.0 * val)))
return float("%.3f" % (0.7 * exp2(4.0 * val)))


def to_filter_freq(val):
# filter_freq goes from ? 100 to 6400 Hz with 18 steps/octave
#return float("%.3f" % (100 * np.exp2(midi / 20.0)))
# from Arturia video
#return float("%.3f" % (6.5 * exp2(0.11 * val * 127)))
return float("%.3f" % (25 * exp2(0.055 * val * 127)))
#return float("%.3f" % (25 * exp2(0.055 * val * 127)))
#return float("%.3f" % (25 * exp2(0.083 * val * 127)))
return float("%.3f" % (13 * exp2(0.0938 * val * 127)))


def ffmt(val):
Expand Down Expand Up @@ -200,7 +203,7 @@ class JunoPatch:
'dco': ['dco_lfo', 'dco_pwm', 'dco_noise', 'dco_sub', 'stop_16', 'stop_8', 'stop_4',
'pulse', 'saw', 'pwm_manual', 'vca_level'],
'vcf': ['vcf_neg', 'vcf_env', 'vcf_freq', 'vcf_lfo', 'vcf_res', 'vcf_kbd'],
'env': ['env_a', 'env_d', 'env_s', 'env_r'],
'env': ['env_a', 'env_d', 'env_s', 'env_r', 'vca_gate'],
'cho': ['chorus', 'hpf']}

# These lists name the fields in the order they appear in the sysex.
Expand Down Expand Up @@ -341,11 +344,10 @@ def init_clones(self):
chained_osc=other_base_osc + self.voice_oscs[i + 1])

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

def _freq_coef_string(self, base_freq):
return '%s,1,0,0,0,%s' % (
return '%s,1,0,0,0,%s,1' % (
ffmt(base_freq), ffmt(0.03 * to_level(self.dco_lfo)))

def clone_voice_oscs(self):
Expand Down Expand Up @@ -403,14 +405,14 @@ def update_vcf(self):
filter_freq='%s,%s,0,0,%s,%s' % (
ffmt(to_filter_freq(self.vcf_freq)),
ffmt(to_level(self.vcf_kbd)),
ffmt(20 * vcf_env_polarity * to_level(self.vcf_env)),
ffmt(5 * to_level(self.vcf_lfo))))
ffmt(11 * vcf_env_polarity * to_level(self.vcf_env)),
ffmt(1.25 * to_level(self.vcf_lfo))))
self.recloning_needed = True

def update_env(self):
bp1_coefs = self._breakpoint_string()
if self.vca_gate:
bp0_coefs=''
bp0_coefs='0,1,0,0'
else:
bp0_coefs = self._breakpoint_string()
self.amy_send(osc=self.pwm_osc, bp0=bp0_coefs, bp1=bp1_coefs)
Expand All @@ -420,7 +422,9 @@ def update_cho(self):
# Chorus & HPF
eq_l = eq_m = eq_h = 0
if self.hpf == 0:
eq_l = 10
eq_l = 7
eq_m = -3
eq_h = -3
elif self.hpf == 1:
pass
elif self.hpf == 2:
Expand Down Expand Up @@ -512,3 +516,6 @@ def set_patch(self, patch):
self._init_from_patch_number(patch)
print("New patch", patch, ":", self.name)
self.init_AMY()

def set_pitch_bend(self, value):
amy.send(pitch_bend=value)
59 changes: 43 additions & 16 deletions tulip/fs/app/juno_ui/juno_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def set_value(self, v):
w = 2 * self.padx
h = self.text_height
tulip.bg_rect(x, y, w, h, self.bg_color, True)
tulip.bg_str("%.2f" % self.value, x, y, self.text_color, self.body_font, w, h)
tulip.bg_str("%d" % round(127 * self.value), x, y, self.text_color, self.body_font, w, h)
tulip.ui_slider(self.id_, self.value)
if self.value_callback_fn is not None:
self.value_callback_fn(self.value)
Expand Down Expand Up @@ -197,6 +197,12 @@ def __init__(self, name, tags, callbacks=None, checkbox_style=0):
for tag in self.tags:
self.state[tag] = False

def current_button_index(self):
for index, tag in enumerate(self.tags):
if self.state[tag]:
return index
return -1

def draw(self):
x = self.x + self.padx
y = self.y + self.y_txt
Expand Down Expand Up @@ -237,6 +243,12 @@ def set_value(self, tag):
if self.value_callback_fns[button_tag] is not None:
self.value_callback_fns[button_tag](self.state[button_tag])

def next(self):
self.set_value(self.tags[(self.current_button_index() + 1) % len(self.tags)])

def prev(self):
self.set_value(self.tags[(self.current_button_index() - 1) % len(self.tags)])

def callback(self, ui_id):
# RadioButton deselects all other buttons.
for id_, button_tag in zip(self.ids, self.tags):
Expand All @@ -262,6 +274,9 @@ def set_value(self, tag, value):
if self.value_callback_fns[button_tag] is not None:
self.value_callback_fns[button_tag](self.state[button_tag])

def get_value(self, tag):
return self.state[tag]

def callback(self, ui_id):
for id_, button_tag in zip(self.ids, self.tags):
if ui_id == id_:
Expand Down Expand Up @@ -463,19 +478,23 @@ def setup_from_midi_chan(new_midi_channel):
# Wire up MIDI controls

# Oxygen49 slider IDs, starting from left.
#SLIDER_IDS = [0x5b, 0x5d, 0x46, 0x47, 0x73, 0x74, 0x75, 0x76, 0x7]
SLIDER_IDS = [74, 71, 91, 93, 73, 72, 5, 84, 7]
SLIDER_IDS = [0x49, 0x4b, 0x48, 0x4a, 0x4f, 0x54, 0x5b, 0x5d, 0x7]
#SLIDER_IDS = [74, 71, 91, 93, 73, 72, 5, 84, 7]
# Oxygen49 knobs, top row then second row.
#KNOB_IDS = [0x11, 0x1a, 0x1c, 0x1e, 0x1b, 0x1d, 0xd, 0x4c]
KNOB_IDS = [75, 76, 92, 95, 10, 77, 78, 79]
KNOB_IDS = [0x10, 0x11, 0x12, 0x0a, 0x13, 0x50, 0x51, 0x14]
#KNOB_IDS = [75, 76, 92, 95, 10, 77, 78, 79]
# Oxygen49 buttons. They toggle between 0 and 0x7f.
#BUTTON_IDS = [0x4a, 0x19, 0x77, 0x4f, 0x55, 0x66, 0x6b, 0x70]
BUTTON_IDS = [50, 51, 52, 53, 54, 55]
BUTTON_IDS = [0x18, 0x19, 0x1a, 0x1b, 0x2c, 0x2d, 0x2e, 0x2f, 0x00, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76]
# I had to program these. See Oxygen49-UserGuide-v1.3.pdf + notes.
#BUTTON_IDS = [96, 97, 98, 99, 100, 101, 102, 103, 104, 113, 114, 115, 116, 117, 118]
PITCH_WHEEL = 0 # Pitch wheel is a special case, hard-coded in juno.py.
MOD_WHEEL = 1

param_map = {
KNOB_IDS[0]: 'lfo_rate',
KNOB_IDS[1]: 'lfo_delay_time',
KNOB_IDS[2]: 'dco_lfo',
#KNOB_IDS[2]: 'dco_lfo',
MOD_WHEEL: 'dco_lfo',
KNOB_IDS[3]: 'dco_pwm',
SLIDER_IDS[0]: 'dco_sub',
SLIDER_IDS[1]: 'dco_noise',
Expand All @@ -489,24 +508,32 @@ def setup_from_midi_chan(new_midi_channel):
SLIDER_IDS[5]: 'env_d',
SLIDER_IDS[6]: 'env_s',
SLIDER_IDS[7]: 'env_r',
BUTTON_IDS[0]: 'Pulse',
BUTTON_IDS[1]: 'Saw',
BUTTON_IDS[2]: 'chorus_mode',
BUTTON_IDS[0]: 'dco_range',
BUTTON_IDS[1]: 'dco_pwm_mode',
BUTTON_IDS[2]: 'Pulse',
BUTTON_IDS[3]: 'Saw',
BUTTON_IDS[4]: 'hpf_freq',
BUTTON_IDS[5]: 'vcf_pol',
BUTTON_IDS[6]: 'vca_mode',
BUTTON_IDS[7]: 'chorus_mode',
}

def control_change(control, value):
#print("juno_ui control_change: control", control, "value", value)
value = value / 127.0
if control == 0: # Pitch bend.
current_juno().set_pitch_bend(2 * value - 1)
if control in param_map:
param_name = param_map[control]
# Special cases.
if param_name == 'Pulse' or param_name == 'Saw':
dco_wave.set_value(param_name, value != 0)
dco_wave.set_value(param_name, not dco_wave.get_value(param_name))
return # Early exit.
elif param_name == 'chorus_mode':
value = 'Off' if value == 0 else 'I'
#jp.set_param(param_name, value)
globals()[param_name].set_value(value)
param_obj = globals()[param_name]
if isinstance(param_obj, RadioButton):
param_obj.next() # value ignored.
return
param_obj.set_value(value)



Expand Down
2 changes: 2 additions & 0 deletions tulip/fs/app/juno_ui/polyvoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ def midi_event_cb(x):
set_patch_fn(m[1])
elif m[0] == 0xe0: # Pitch bend.
control_change_fn(0, m[2])
# Special case. Pitch bend is -1.0 .. 1.0.
#amy.send(pitch_bend=(m[2] / 64 + m[1] / 8192) - 1.0)
elif m[0] == 0xb0: # Other control slider.
control_change_fn(m[1], m[2])

Expand Down

0 comments on commit 8752660

Please sign in to comment.