Skip to content
Snippets Groups Projects
Commit b3f6dadc authored by moon2's avatar moon2 :speech_balloon:
Browse files

arp

parent 61371f0a
Branches
No related tags found
1 merge request!661Draft: return of melodic demo
...@@ -76,10 +76,6 @@ class MelodicApp(Application): ...@@ -76,10 +76,6 @@ class MelodicApp(Application):
if new_page is self._fg_page: if new_page is self._fg_page:
return return
new_page.full_redraw = True new_page.full_redraw = True
if new_page.use_bottom_petals and not self._fg_page.use_bottom_petals:
if not self.drone_toggle.value:
for i in range(1, 10, 2):
self.poly_squeeze.signals.trigger_in[i].stop()
self.petal_block = [True for _ in self.petal_block] self.petal_block = [True for _ in self.petal_block]
self._fg_page = new_page self._fg_page = new_page
...@@ -197,16 +193,20 @@ class MelodicApp(Application): ...@@ -197,16 +193,20 @@ class MelodicApp(Application):
self.blm = bl00mbox.Channel("mono synth") self.blm = bl00mbox.Channel("mono synth")
self.blm.volume = 13000 self.blm.volume = 13000
self.poly_squeeze = self.blm.new(bl00mbox.plugins.poly_squeeze, 1, 10) self.poly_squeeze = self.blm.new(bl00mbox.plugins.poly_squeeze, 1, 10)
self.arp = self.blm.new(arp)
self.synth = self.blm.new(mix_env) self.synth = self.blm.new(mix_env)
self.arp.signals.trigger_in = self.poly_squeeze.signals.trigger_out[0]
self.arp.signals.input = self.poly_squeeze.signals.pitch_out[0]
self.synth.signals.trigger = self.arp.signals.trigger_out
self.synth.signals.pitch = self.arp.signals.output
self.synth.signals.output = self.blm.mixer self.synth.signals.output = self.blm.mixer
self.synth.signals.trigger = self.poly_squeeze.signals.trigger_out[0]
self.synth.signals.pitch = self.poly_squeeze.signals.pitch_out[0]
mod_envs = [self.blm.new(env) for x in range(2)] mod_envs = [self.blm.new(env) for x in range(2)]
for mod_env in mod_envs: for mod_env in mod_envs:
mod_env.always_render = True mod_env.always_render = True
mod_env.signals.trigger = self.poly_squeeze.signals.trigger_out[0] mod_env.signals.trigger = self.arp.signals.trigger_out
self.mixer_page = self.synth.make_mixer_page() self.mixer_page = self.synth.make_mixer_page()
...@@ -237,6 +237,7 @@ class MelodicApp(Application): ...@@ -237,6 +237,7 @@ class MelodicApp(Application):
self.scale_page = ScaleSetupPage("scale") self.scale_page = ScaleSetupPage("scale")
self.steps_page = StepsPage("steps") self.steps_page = StepsPage("steps")
self.arp_page = ArpPage("arp")
self.notes_page = SubMenuPage("notes") self.notes_page = SubMenuPage("notes")
self.sound_page = SubMenuPage("sounds") self.sound_page = SubMenuPage("sounds")
...@@ -265,7 +266,7 @@ class MelodicApp(Application): ...@@ -265,7 +266,7 @@ class MelodicApp(Application):
self.sound_page.savepage = SoundSavePage("sound", 5) self.sound_page.savepage = SoundSavePage("sound", 5)
self.notes_page.menupages = [ self.notes_page.menupages = [
self.scale_page, self.scale_page,
DummyPage("arp"), self.arp_page,
DummyPage("bend"), DummyPage("bend"),
DummyPage("steps"), DummyPage("steps"),
# self.steps_page, # self.steps_page,
...@@ -348,17 +349,10 @@ class MelodicApp(Application): ...@@ -348,17 +349,10 @@ class MelodicApp(Application):
if self.input.captouch.petals[i].whole.pressed: if self.input.captouch.petals[i].whole.pressed:
self.poly_squeeze.signals.pitch_in[i].tone = self.scale[i] self.poly_squeeze.signals.pitch_in[i].tone = self.scale[i]
self.poly_squeeze.signals.trigger_in[i].start() self.poly_squeeze.signals.trigger_in[i].start()
elif ( elif self.input.captouch.petals[i].whole.released:
self.input.captouch.petals[i].whole.released
and not self.drone_toggle.value
):
self.poly_squeeze.signals.trigger_in[i].stop() self.poly_squeeze.signals.trigger_in[i].stop()
if self.drone_toggle.changed and not self.drone_toggle.value: self.arp.block_stop = self.drone_toggle.value
for i in range(10):
if not ins.captouch.petals[i].pressed:
self.poly_squeeze.signals.trigger_in[i].stop()
self.drone_toggle.changed = False
if self.fg_page.use_bottom_petals: if self.fg_page.use_bottom_petals:
for petal in [7, 9, 1, 3]: for petal in [7, 9, 1, 3]:
......
...@@ -81,8 +81,12 @@ class StyleClassic(StyleTheme): ...@@ -81,8 +81,12 @@ class StyleClassic(StyleTheme):
ctx.arc(0, 150, rad, 0, math.tau, 1).fill() ctx.arc(0, 150, rad, 0, math.tau, 1).fill()
ctx.rgb(*app.cols.bg) ctx.rgb(*app.cols.bg)
ctx.text_align = ctx.CENTER ctx.text_align = ctx.CENTER
ctx.font_size = 19
if arrow: if arrow:
if isinstance(arrow, str):
ctx.move_to(0, pos + 9 + 9)
ctx.font_size = 16
ctx.text(arrow)
else:
ctx.move_to(-10, pos + 9) ctx.move_to(-10, pos + 9)
ctx.rel_line_to(10, 5) ctx.rel_line_to(10, 5)
ctx.rel_line_to(10, -5) ctx.rel_line_to(10, -5)
...@@ -93,6 +97,7 @@ class StyleClassic(StyleTheme): ...@@ -93,6 +97,7 @@ class StyleClassic(StyleTheme):
ctx.rel_line_to(5 * i, 4) ctx.rel_line_to(5 * i, 4)
ctx.rel_line_to(-5 * i, 3) ctx.rel_line_to(-5 * i, 3)
ctx.stroke() ctx.stroke()
ctx.font_size = 19
ctx.move_to(0, pos) ctx.move_to(0, pos)
ctx.text(text) ctx.text(text)
ctx.restore() ctx.restore()
......
...@@ -256,3 +256,104 @@ class mix_env(bl00mbox.Patch): ...@@ -256,3 +256,104 @@ class mix_env(bl00mbox.Patch):
page.params += [param] page.params += [param]
page.params += [mix_params[1]] page.params += [mix_params[1]]
return page return page
class arp(bl00mbox.Patch):
def __init__(self, chan):
super().__init__(chan)
self.num_steps = 12
self.plugins.seq = self._channel.new(
bl00mbox.plugins.sequencer, 2, self.num_steps
)
self.plugins.mp = self._channel.new(bl00mbox.plugins.multipitch, 1)
self.plugins.mp.signals.shift[0] = self.plugins.seq.signals.track[0]
self.plugins.tm = self._channel.new(bl00mbox.plugins.trigger_merge, 2)
self.plugins.tm.signals.input[0] = self.plugins.mp.signals.trigger_thru
self.plugins.tm.signals.input[1] = self.plugins.seq.signals.track[1]
self.plugins.block = self._channel.new(bl00mbox.plugins.trigger_merge)
self.plugins.mp.signals.trigger_in = self.plugins.block.signals.output
self.signals.input = self.plugins.mp.signals.input
self.signals.output = self.plugins.mp.signals.output[0]
self.signals.trigger_in = self.plugins.block.signals.input[0]
self.signals.trigger_out = self.plugins.tm.signals.output
self.signals.bpm = self.plugins.seq.signals.bpm
self.signals.bpm = 80
default_pattern = [0, -12, 0, 12, 0, -12, 0, -12]
self.max_step = len(default_pattern)
default_pattern += [0] * (self.num_steps - self.max_step)
self.max_step -= 1
table = self.plugins.seq.table_int16_array
table[0] = 32767
for i in range(self.num_steps):
self.tone_set(i, default_pattern[i])
self.plugins.seq.trigger_start(1, i)
self._bypass = False
self.bypass = True
@property
def bypass(self):
return self._bypass
@bypass.setter
def bypass(self, val):
val = bool(val)
if val is self._bypass:
return
elif val:
self.plugins.seq.signals.sync_in.stop()
else:
self.plugins.seq.signals.sync_in = self.plugins.mp.signals.trigger_thru
self._bypass = val
def tone_set(self, step_index, tone):
if step_index >= self.num_steps:
return
table = self.plugins.seq.table_int16_array
table[step_index + 1] = 18367 + int(200 * tone)
def tone_get(self, step_index):
if step_index >= self.num_steps:
return
table = self.plugins.seq.table_int16_array
return (table[step_index + 1] - 18367) / 200
def trigger_set(self, step_index, val):
if val > 0:
self.plugins.seq.trigger_start(1, step_index)
elif val < 0:
self.plugins.seq.trigger_stop(1, step_index)
else:
self.plugins.seq.trigger_clear(1, step_index)
def trigger_get(self, step_index):
ret = self.plugins.seq.trigger_state(1, step_index)
if ret > 0:
return 1
elif ret < 0:
return -1
return 0
@property
def max_step(self):
return self.plugins.seq.signals.step_end.value
@max_step.setter
def max_step(self, val):
self.plugins.seq.signals.step_end = val
@property
def step(self):
return self.plugins.seq.signals.step.value
@property
def block_stop(self):
return self.plugins.block.block_stop
@block_stop.setter
def block_stop(self, val):
self.plugins.block.block_stop = val
...@@ -930,6 +930,197 @@ class StepsPage(LonerPage): ...@@ -930,6 +930,197 @@ class StepsPage(LonerPage):
ctx.restore() ctx.restore()
class ArpPage(LonerPage):
def get_help(self):
return (
"You can configure the arpeggiator here. An arp applies "
"a sequence of pitch shift values to whatever you are "
"playing.\n\n"
"Petal 5 allows you to tap a tempo for the arp. You can also "
"turn it off entirely by holding the petal for a second.\n\n"
"To edit the sequence, use the app button l+r to select a stage "
"in the sequence. You can then peform the following actions:\n\n"
"Petal 1+3 set the pitch shift value of the stage. The pitch "
"of the first stage is fixed to 0 and cannot be edited.\n\n"
"Petal 9 sets the currently selected stage as the last one "
"before the sequence overflows.\n\n"
"Petal 7 sets the event type of the selected stage: A filled "
"square means the envelope generators of the synthesizer are "
"retriggered when this stage is reached, an empty square means "
"they aren't.\n\n"
# "retriggered when this stage is reached, a cross means they "
# 'receive a "stop" event, and an empty square means they remain '
# "in whatever state they were.\n\n"
"\n\n\n\n\n\n\n"
"Hold petal 7 while adjusting pitch shift with petal 1+3 to do "
"so in 1/4 semitone resolution for microtonal experiments."
)
def __init__(self, name):
super().__init__(name)
self.pos = 0
self.microtonal_latch = False
self.tap_acc = 3000
self.press_counter = 0
def lr_press_event(self, app, lr):
self.full_redraw = True
self.pos += lr
self.pos %= app.arp.num_steps
def think(self, ins, delta_ms, app):
lr_dir = app.input.captouch.petals[1].whole.pressed
lr_dir -= app.input.captouch.petals[3].whole.pressed
if lr_dir and self.pos:
if ins.captouch.petals[7].pressed:
self.microtonal_latch = True
lr_dir /= 4
val = app.arp.tone_get(self.pos) + lr_dir
val = min(15, max(-12, val))
app.arp.tone_set(self.pos, val)
self.full_redraw = True
if app.input.captouch.petals[9].whole.pressed:
app.arp.max_step = self.pos
self.full_redraw = True
if app.input.captouch.petals[7].whole.released:
if not self.microtonal_latch:
val = app.arp.trigger_get(self.pos) + 1
# we wondered when it'd come bite us! the radspa signal
# event encoding can't send two stops in a row, which
# isn't very good. anyways, it causes problems here in
# the edge case where u have zero retriggers but stops
# in the sequencer.
# trivial solutions likely drag performance down so we
# switch when we have a mature fix. gonna hide our shame
# here until then:
val = val % 2
# val = ((val + 1) % 3) - 1
app.arp.trigger_set(self.pos, val)
self.full_redraw = True
else:
self.microtonal_latch = False
if app.input.captouch.petals[5].whole.pressed:
bpm = 60000 / self.tap_acc
self.tap_acc = 0
self.press_counter = 0
if app.arp.bypass:
app.arp.bypass = False
self.full_redraw = True
if bpm > 30 and bpm < 300:
app.arp.signals.bpm = bpm
self.full_redraw = True
if ins.captouch.petals[5].pressed and self.press_counter is not None:
if self.press_counter > 1000:
app.arp.bypass = True
self.full_redraw = True
self.press_counter = None
else:
self.press_counter += delta_ms
self.tap_acc += delta_ms
def draw(self, ctx, app):
dot_size = 12
dot_dist = 16
ctx.font = "Arimo Bold"
if self.full_redraw:
self.full_redraw = False
ctx.rgb(*app.cols.bg).rectangle(-120, -120, 240, 240).fill()
app.draw_title(ctx, self.display_name)
if app.arp.bypass:
app.draw_modulator_indicator(ctx, "start")
else:
app.draw_modulator_indicator(
ctx,
"tap bpm: " + str(app.arp.signals.bpm.value),
arrow="(hold: stop)",
)
for i in range(app.arp.num_steps):
tone = app.arp.tone_get(i)
trig = app.arp.trigger_get(i)
size = dot_size
if trig != 1:
size -= 2
xpos = (i - 5.5) * dot_dist - size / 2
ypos = -tone * 3 - size / 2
if self.pos == i:
col = app.cols.alt
round_tone = round(tone * 4)
if round_tone:
ctx.save()
ctx.rgb(*col)
ctx.font_size = 16
ctx.move_to(0, 0)
if tone < 0:
ctx.translate(xpos + size, ypos - 4)
align = ctx.LEFT
else:
ctx.translate(xpos + size, ypos + size + 4)
align = ctx.RIGHT
ctx.rotate(-math.tau / 4)
ctx.text_align = align
if round_tone % 4:
ctx.text(f"{tone:.2f}")
else:
ctx.text(f"{int(tone)}")
ctx.restore()
else:
col = app.cols.fg
if i > app.arp.max_step:
ctx.rgb(*[x * 0.5 for x in col])
else:
ctx.rgb(*col)
if trig == 1:
ctx.rectangle(xpos, ypos, size, size).fill()
elif trig == 0:
ctx.rectangle(xpos, ypos, size, size).stroke()
elif trig == -1:
ctx.move_to(xpos, ypos)
ctx.rel_line_to(size, size)
ctx.rel_move_to(0, -size)
ctx.rel_line_to(-size, size)
ctx.stroke()
# arrows
ctx.rgb(*app.cols.hi)
ctx.move_to(100, 50)
ctx.rel_line_to(-4, -6)
ctx.rel_line_to(8, 0)
ctx.rel_line_to(-4, 6)
ctx.stroke()
ctx.move_to(70, -93)
ctx.rel_line_to(-4, 6)
ctx.rel_line_to(8, 0)
ctx.rel_line_to(-4, -6)
ctx.stroke()
# :|
ctx.move_to(-70, -93).rel_line_to(0, 10).stroke()
ctx.rectangle(-75, -91, 2, 2).fill()
ctx.rectangle(-75, -87, 2, 2).fill()
# event selector
size = 8
xpos, ypos = -100, 50 - 3
ctx.rectangle(xpos, ypos, size, size).fill()
size = 6
xpos, ypos = -100 + 7, 50 + 3
ctx.rectangle(xpos, ypos, size, size).stroke()
xpos, ypos = -100 - 2, 50 + 6
# ctx.move_to(xpos, ypos)
# ctx.rel_line_to(size, size)
# ctx.rel_move_to(0, -size)
# ctx.rel_line_to(-size, size)
# ctx.stroke()
i = app.arp.step
ctx.rgb(*app.cols.bg)
ctx.rectangle(-120, -60, 240, 4).fill()
ctx.rgb(*app.cols.hi)
size = 4
xpos = (i - 5.5) * dot_dist - size / 2
ctx.rectangle(xpos, -60, size, size).fill()
class ScaleSetupPage(LonerPage): class ScaleSetupPage(LonerPage):
def get_help(self): def get_help(self):
return ( return (
...@@ -1083,6 +1274,7 @@ class ParameterPage(Page): ...@@ -1083,6 +1274,7 @@ class ParameterPage(Page):
def back_press_event(self, app): def back_press_event(self, app):
if self.subwindow: if self.subwindow:
self.subwindow = 0 self.subwindow = 0
self.full_redraw = True
else: else:
self.scroll_to_parent(app) self.scroll_to_parent(app)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment