diff --git a/docs/badge/bl00mbox.rst b/docs/badge/bl00mbox.rst index 9a6568bc86f7ce6831b651c781227547db7867fa..20a820d5d6f53393f7ded102f03ad70cd29e2bb6 100644 --- a/docs/badge/bl00mbox.rst +++ b/docs/badge/bl00mbox.rst @@ -11,13 +11,9 @@ Upcoming features (in no specific order) -1) Expose hardware such as captouch and IMU as pseudo-signals that buds can subscribe to. This frees the repl for parameter manipulation while the backend takes care of the playing surface, neat for live coding. +1) Expose hardware such as captouch and IMU as pseudo-signals that plugins can subscribe to. This frees the repl for parameter manipulation while the backend takes care of the playing surface, neat for live coding. -2) Cross-channel connections - -3) Stepped value naming - -4) Better signal/bud representation in patches +2) Stepped value naming Patches ------------- @@ -35,8 +31,7 @@ with a channel instance, but you can also spawn one directly: >>> blm.volume = 5000 The easiest way to get sound is to use patches. These are "macroscopic" units -that directly connect to the output and provide a compact UI. Here's how to -create one: +and can often be used without much thinking: .. code-block:: pycon @@ -44,15 +39,17 @@ create one: >>> bl00mbox.patches. # create a patch instance >>> tiny = blm.new(bl00mbox.patches.tinysynth_fm) + # connect sound output to mixer of the channel + >>> tiny.signals.output = blm.mixer # play it! - >>> tiny.start() + >>> tiny.signals.trigger.start() # try autocomplete here too! >>> tiny. # patches come with very individual parameters! - >>> tiny.fm_waveform(tiny.SAW) - >>> tiny.start() + >>> tiny.signals.waveform = 0 + >>> tiny.signals.trigger.start() -Buds +Plugins ---------- We can inspect the patch we created earlier: @@ -61,13 +58,13 @@ We can inspect the patch we created earlier: >>> tiny [patch] tinysynth_fm - [bud 32] osc_fm - output [output]: 0 => input in [bud 34] ampliverter + [plugin 32] osc_fm + output [output]: 0 => input in [plugin 34] ampliverter pitch [input/pitch]: 18367 / 0.0 semitones / 440.0Hz waveform [input]: -1 - lin_fm [input]: 0 <= output in [bud 35] osc_fm - [bud 33] env_adsr - output [output]: 0 => gain in [bud 34] ampliverter + lin_fm [input]: 0 <= output in [plugin 35] osc_fm + [plugin 33] env_adsr + output [output]: 0 => gain in [plugin 34] ampliverter phase [output]: 0 input [input]: 32767 trigger [input/trigger]: 0 @@ -76,20 +73,20 @@ We can inspect the patch we created earlier: sustain [ms] [input]: 0 release [input]: 100 gate [input]: 0 - [bud 34] ampliverter + [plugin 34] ampliverter output [output]: 0 ==> [channel mixer] - input [input]: 0 <= output in [bud 32] osc_fm - gain [input]: 0 <= output in [bud 33] env_adsr + input [input]: 0 <= output in [plugin 32] osc_fm + gain [input]: 0 <= output in [plugin 33] env_adsr bias [input]: 0 - [bud 35] osc_fm - output [output]: 0 => lin_fm in [bud 32] osc_fm + [plugin 35] osc_fm + output [output]: 0 => lin_fm in [plugin 32] osc_fm pitch [input/pitch]: 21539 / 15.86 semitones / 1099.801Hz waveform [input]: 1 lin_fm [input]: 0 -The patch is actually composed of buds! Buds are wrappers that contain atomic plugins. Each -plugin is composed of signals that can be connected to other signals. Signals can have different +The patch is actually composed of plugins and connections! Plugins are atomic signal processing +units. Each plugin has signals that can be connected to other signals. Signals can have different properties that are listed behind their name in square brackets. For starters, each signal is either an input or output. Connections always happen between an input and an output. Outputs can fan out to multiple inputs, but inputs can only receive data from a single output. If no @@ -99,7 +96,7 @@ output is connected to an input, it has a static value. A special case is the channel mixer (an [input] signal) which only fakes being a bl00mbox signal and can accept multiple outputs. -Let's play around with that a bit more and create some fresh unbothered buds: +Let's play around with that a bit more and create some fresh unbothered plugins: .. code-block:: pycon @@ -107,29 +104,29 @@ Let's play around with that a bit more and create some fresh unbothered buds: >>> bl00mbox.plugins. # print details about specific plugin >>> bl00mbox.plugins.ampliverter - # create a new bud + # create a new plugin >>> osc = blm.new(bl00mbox.plugins.osc_fm) >>> env = blm.new(bl00mbox.plugins.env_adsr) -You can inspect properties of the new buds just as with a patch - in fact, many patches simply print -all their contained buds and maybe some extra info (but that doesn't have to be the case and is up +You can inspect properties of the new plugins just as with a patch - in fact, many patches simply print +all their contained plugins and maybe some extra info (but that doesn't have to be the case and is up to the patch designer). .. note:: - As of now patch designers can hide buds within the internal structure however they like and + As of now patch designers can hide plugins within the internal structure however they like and you kind of have to know where to find stuff. We'll come up with a better solution soon! .. code-block:: pycon - # print general info about bud + # print general info about plugin >>> osc - [bud 36] osc_fm + [plugin 36] osc_fm output [output]: 0 pitch [input/pitch]: 18367 / 0.0 semitones / 440.0Hz waveform [input]: -16000 lin_fm [input]: 0 - # print info about a specific bud signal + # print info about a specific plugin signal >>> env.signals.trigger trigger [input/trigger]: 0 @@ -186,9 +183,9 @@ and we can get them individually: >>> chan_one [channel 1: shoegaze] (foreground) volume: 3000 - buds: 18 + plugins: 18 [channel mixer] (1 connections) - output in [bud 1] lowpass + output in [plugin 1] lowpass We have accidentially grabbed the channel used by the shoegaze application! Each application should have its own channel(s), so in order to get a free one we'll request a free one from the @@ -209,7 +206,7 @@ backend by skipping the number. We can also provide a name for a new channel ins >>> chan_free [channel 3: hewwo] (foreground) volume: 3000 - buds: 0 + plugins: 0 [channel mixer] (0 connections) In case there's no free channel yet you get channel 31, the garbage channel. It behaves like @@ -258,12 +255,12 @@ doing so: What constitutes a channel interaction for auto channel foregrounding is a bit in motion at this point and generally unreliable. For applications it is ideal to mark the channel manually when using it. When -exiting, an application should free the channel with automatically clears all buds. A channel should +exiting, an application should free the channel with automatically clears all plugins. A channel should be no longer used after freeing: .. code-block:: pycon - # this clears all buds and sets the internal "free" marker to zero + # this clears all plugins and sets the internal "free" marker to zero >>> chan_one.free = True # good practice to not accidentially use a free channel >>> chan_one = None @@ -278,7 +275,7 @@ Some other misc channel operations for live coding mostly: >>> bl00mbox.Channels.print_overview() [channel 3: hewwo] (foreground) volume: 3000 - buds: 0 + plugins: 0 [channel mixer] (0 connections) Example 1: Auto bassline diff --git a/python_payload/apps/demo_harmonic/__init__.py b/python_payload/apps/demo_harmonic/__init__.py index bf26c7b95143d7d4b92fd6dae38d6285cf9feb63..98402812b3506a1af08b3db5fd6a1ffd021ca643 100644 --- a/python_payload/apps/demo_harmonic/__init__.py +++ b/python_payload/apps/demo_harmonic/__init__.py @@ -31,14 +31,15 @@ class HarmonicApp(Application): self.cp_prev = captouch.read() for i, synth in enumerate(self.synths): - synth.decay(500) - synth.waveform(-32767) - synth.attack(50) - synth.volume(0.3) - synth.sustain(0.9) - synth.release(800) - synth.fm_waveform(-32767) - synth.fm(1.5) + synth.signals.decay = 500 + synth.signals.waveform = -32767 + synth.signals.attack = 50 + synth.signals.volume = 0.3 * 32767 + synth.signals.sustain = 0.9 * 32767 + synth.signals.release = 800 + synth.signals.fm_waveform = -32767 + synth.signals.output = blm.mixer + # synth.fm = 1.5 self._set_chord(3) self.prev_captouch = [0] * 10 @@ -71,11 +72,11 @@ class HarmonicApp(Application): self._set_chord(k) else: k = int(i / 2) - self.synths[k].tone(self.chord[k]) - self.synths[k].start() + self.synths[k].signals.pitch.tone = self.chord[k] + self.synths[k].signals.trigger.start() self.color_intensity = 1.0 elif (not cts.petals[i].pressed) and self.cp_prev.petals[i].pressed: if (1 + i) % 2: k = int(i / 2) - self.synths[k].stop() + self.synths[k].signals.trigger.stop() self.cp_prev = cts diff --git a/python_payload/apps/demo_melodic/__init__.py b/python_payload/apps/demo_melodic/__init__.py index 35fe150e16fb2cbb6ccba2bb100e663ab453b1bd..6866af46cd3012c466e0f50389f024938878108c 100644 --- a/python_payload/apps/demo_melodic/__init__.py +++ b/python_payload/apps/demo_melodic/__init__.py @@ -67,16 +67,18 @@ def run(ins: InputState) -> None: k -= 10 k = 3 - k note = scale[k] + 12 * octave - synths[0].tone(note) - synths[0].start() + synths[0].signals.pitch.tone = note + synths[0].signals.trigger.start() def init() -> None: global synths for i in range(1): - synths += [blm.new(bl00mbox.patches.tinysynth_fm)] + synth = blm.new(bl00mbox.patches.tinysynth_fm) + synth.signals.output = blm.mixer + synths += [synth] for synth in synths: - synth.decay(100) + synth.signals.decay = 100 def foreground() -> None: diff --git a/python_payload/apps/otamatone/__init__.py b/python_payload/apps/otamatone/__init__.py index 484b3e9e04f2c6148702c88620eb5567d8f6bfb0..cb0ffeba0a5a7f912343b9aa6bbbe0e0cb4536e1 100644 --- a/python_payload/apps/otamatone/__init__.py +++ b/python_payload/apps/otamatone/__init__.py @@ -84,7 +84,8 @@ class Otamatone(Application): self._blm = bl00mbox.Channel("Otamatone") self._osc = self._blm.new(bl00mbox.patches.tinysynth) - self._osc.waveform(2) + self._osc.signals.output = self._blm.mixer + self._osc.signals.waveform = 2 self._intensity = 0.0 self.input.captouch.petals[self.PETAL_NO].whole.repeat_disable() @@ -123,12 +124,12 @@ class Otamatone(Application): if petal.whole.down: if self._intensity < 1.0: self._intensity += 0.1 * (delta_ms / 20) - self._osc.tone(ctrl * 12) - self._osc.start() + self._osc.signals.pitch.tone = ctrl * 12 + self._osc.signals.trigger.start() if petal.whole.up: self._intensity = 0 - self._osc.stop() + self._osc.signals.trigger.stop() self._blob._yell = self._intensity * 0.8 + (ctrl + 1) * 0.1 diff --git a/python_payload/apps/shoegaze/__init__.py b/python_payload/apps/shoegaze/__init__.py index 2cc92d184a59535b4e4abdc5a8a8c3563c16dfe3..f625b651ced7efb7bd33485e753a7276c5742b6a 100644 --- a/python_payload/apps/shoegaze/__init__.py +++ b/python_payload/apps/shoegaze/__init__.py @@ -65,13 +65,13 @@ class ShoegazeApp(Application): self.git_mixer.signals.input2 = self.git_strings[2].signals.output self.git_mixer.signals.input3 = self.git_strings[3].signals.output self.git_mixer.signals.output = self.git_lp.signals.input - self.git_fuzz.buds.dist.signals.input = self.git_lp.signals.output + self.git_fuzz.signals.input = self.git_lp.signals.output self.bass_lp.signals.input = self.bass_string.signals.output self.main_mixer.signals.input1 = self.bass_lp.signals.output - self.main_fuzz.buds.dist.signals.input = self.main_mixer.signals.output - self.main_fuzz.buds.dist.signals.output = self.main_lp.signals.input + self.main_fuzz.signals.input = self.main_mixer.signals.output + self.main_fuzz.signals.output = self.main_lp.signals.input self.main_lp.signals.output = self.blm.mixer self.git_delay.signals.time = 200 @@ -104,12 +104,12 @@ class ShoegazeApp(Application): self.main_lp.signals.freq = 2500 self.main_lp.signals.gain = 2000 self.git_mixer.signals.gain = 4000 - self.main_lp.signals.input = self.main_fuzz.buds.dist.signals.output + self.main_lp.signals.input = self.main_fuzz.signals.output if self.delay_on: - self.git_delay.signals.input = self.git_fuzz.buds.dist.signals.output + self.git_delay.signals.input = self.git_fuzz.signals.output self.main_mixer.signals.input0 = self.git_delay.signals.output else: - self.main_mixer.signals.input0 = self.git_fuzz.buds.dist.signals.output + self.main_mixer.signals.input0 = self.git_fuzz.signals.output else: self.bass_lp.signals.gain = 2000 self.git_lp.signals.gain = 2000 diff --git a/python_payload/apps/simple_drums/__init__.py b/python_payload/apps/simple_drums/__init__.py index f7d722f4ebdc82fdd51d115ff6296f9b1998dad8..b17027a918623a190299dab34f1c1e15311c0640 100644 --- a/python_payload/apps/simple_drums/__init__.py +++ b/python_payload/apps/simple_drums/__init__.py @@ -48,17 +48,21 @@ class SimpleDrums(Application): # Dot(20, 20, 0, 40, self._track_col(1)).draw(0,ctx) self.snare = self.blm.new(bl00mbox.patches.sampler, "snare.wav") # Dot(30, 30, 2, -20, self._track_col(2)).draw(0,ctx) - self.kick.sampler.signals.trigger = self.seq.seqs[0].signals.output - self.hat.sampler.signals.trigger = self.seq.seqs[1].signals.output - self.snare.sampler.signals.trigger = self.seq.seqs[2].signals.output + self.kick.signals.output = self.blm.mixer + self.snare.signals.output = self.blm.mixer + self.hat.signals.output = self.blm.mixer + self.kick.signals.trigger = self.seq.plugins.sequencer0.signals.output + self.hat.signals.trigger = self.seq.plugins.sequencer1.signals.output + self.snare.signals.trigger = self.seq.plugins.sequencer2.signals.output + self.seq.signals.bpm.value = 80 + self.track_names = ["kick", "hihat", "snare"] self.track = 0 - self.seq.bpm = 80 self.blm.background_mute_override = True self.tap_tempo_press_counter = 0 self.delta_acc = 0 self.stopped = False - self.bpm = self.seq.bpm + self.bpm = self.seq.signals.bpm.value # True if a given group should be highlighted, when a corresponding # petal is pressed. @@ -96,7 +100,7 @@ class SimpleDrums(Application): (0.15, 0.15, 0.15), ) ) - st = self.seq.seqs[0].signals.step.value + st = self.seq.signals.step.value for track in range(3): rgb = self._track_col(track) @@ -143,7 +147,7 @@ class SimpleDrums(Application): ctx.move_to(0, -65) ctx.rgb(255, 255, 255) - ctx.text(str(self.seq.bpm) + " bpm") + ctx.text(str(self.seq.signals.bpm) + " bpm") ctx.font_size = 15 @@ -160,7 +164,7 @@ class SimpleDrums(Application): def think(self, ins: InputState, delta_ms: int) -> None: super().think(ins, delta_ms) - st = self.seq.seqs[0].signals.step.value + st = self.seq.signals.step.value rgb = self._track_col(self.track) if st == 0: leds.set_all_rgb(*[int(x / 4) for x in rgb]) @@ -183,19 +187,19 @@ class SimpleDrums(Application): self.track = (self.track - 1) % 3 if petals[0].whole.pressed: if self.stopped: - self.seq.bpm = self.bpm + self.seq.signals.bpm = self.bpm self.blm.background_mute_override = True self.stopped = False elif self.delta_acc < 3000 and self.delta_acc > 10: bpm = int(60000 / self.delta_acc) if bpm > 40 and bpm < 500: - self.seq.bpm = bpm + self.seq.signals.bpm = bpm self.bpm = bpm self.delta_acc = 0 if petals[0].whole.down: if self.tap_tempo_press_counter > 500: - self.seq.bpm = 0 + self.seq.signals.bpm = 0 self.stopped = True self.blm.background_mute_override = False else: diff --git a/python_payload/bl00mbox/_patches.py b/python_payload/bl00mbox/_patches.py index ac4142ae1578e862f4ab0837cf5d4efe19df6697..bf7fb5895b1171dbe9572d3bd995f20f69397ba2 100644 --- a/python_payload/bl00mbox/_patches.py +++ b/python_payload/bl00mbox/_patches.py @@ -10,124 +10,106 @@ except ImportError: class _Patch: - def bl00mbox_patch_marker(self): - return True + def __init__(self, chan): + self.plugins = _PatchPluginList() + self.signals = _PatchSignalList() + self._channel = chan + def __repr__(self): + ret = "[patch] " + type(self).__name__ + ret += "\n [signals:]\n " + "\n ".join(repr(self.signals).split("\n")) + ret += "\n [plugins:]\n " + "\n ".join(repr(self.plugins).split("\n")) + return ret -class _PatchSignalList: - pass +class _PatchItemList: + def __init__(self): + self._items = [] -class _PatchBudList: - pass + def __iter__(self): + return iter(self._items) + def __repr__(self): + return "\n".join([x + ": " + repr(getattr(self, x)) for x in self._items]) -class tinysynth(_Patch): - def __init__(self, chan): - self.channel = chan - self.SINE = -32767 - self.TRI = -1 - self.SQUARE = 1 - self.SAW = 32767 - - self.osc = chan._new_bud(420) - self.env = chan._new_bud(42) - self.amp = chan._new_bud(69) - self.amp.signals.output.value = chan.mixer - self.amp.signals.gain.value = self.env.signals.output - self.amp.signals.input.value = self.osc.signals.output - self.env.signals.sustain.value = 0 - self.env.signals.decay.value = 500 - self.release(100) + def __setattr__(self, key, value): + current_value = getattr(self, key, None) - def __repr__(self): - ret = "[patch] tinysynth" - ret += "\n " + "\n ".join(repr(self.osc).split("\n")) - ret += "\n " + "\n ".join(repr(self.env).split("\n")) - ret += "\n " + "\n ".join(repr(self.amp).split("\n")) - return ret + if current_value is None and not key.startswith("_"): + self._items.append(key) - def tone(self, val): - self.osc.signals.pitch.tone = val + super().__setattr__(key, value) - def freq(self, val): - self.osc.signals.pitch.freq = val - def volume(self, val): - self.env.signals.input.value = 32767 * val +class _PatchSignalList(_PatchItemList): + def __setattr__(self, key, value): + current_value = getattr(self, key, None) + if isinstance(current_value, bl00mbox.Signal): + current_value.value = value + return + super().__setattr__(key, value) - def start(self): - self.env.signals.trigger.start() - def stop(self): - self.env.signals.trigger.stop() +class _PatchPluginList(_PatchItemList): + pass - def waveform(self, val): - self.osc.signals.waveform.value = val - def attack(self, val): - self.env.signals.attack.value = val +class tinysynth(_Patch): + def __init__(self, chan): + super().__init__(chan) + self.plugins.osc = self._channel.new(bl00mbox.plugins.osc_fm) + self.plugins.env = self._channel.new(bl00mbox.plugins.env_adsr) + self.plugins.amp = self._channel.new(bl00mbox.plugins.ampliverter) - def decay(self, val): - self.env.signals.decay.value = val + self.plugins.amp.signals.gain = self.plugins.env.signals.output + self.plugins.amp.signals.input = self.plugins.osc.signals.output + self.plugins.env.signals.decay = 500 - def sustain(self, val): - self.env.signals.sustain.value = val * 32767 + self.signals.output = self.plugins.amp.signals.output + self.signals.pitch = self.plugins.osc.signals.pitch + self.signals.waveform = self.plugins.osc.signals.waveform - def release(self, val): - self.env.signals.release.value = val + self.signals.trigger = self.plugins.env.signals.trigger + self.signals.attack = self.plugins.env.signals.attack + self.signals.sustain = self.plugins.env.signals.sustain + self.signals.decay = self.plugins.env.signals.decay + self.signals.release = self.plugins.env.signals.release + self.signals.volume = self.plugins.env.signals.input.value + self.signals.release = 100 class tinysynth_fm(tinysynth): def __init__(self, chan): - tinysynth.__init__(self, chan) - self.mod_osc = chan._new_bud(420) - self.fm_mult = 2.5 - self.mod_osc.signals.output.value = self.osc.signals.lin_fm - self.decay(1000) - self.attack(20) - self._update_mod_osc() - self.fm_waveform(self.SQUARE) - self.waveform(self.TRI) - - def fm_waveform(self, val): - self.mod_osc.signals.waveform.value = val + super().__init__(chan) + self.plugins.mod_osc = self._channel.new(bl00mbox.plugins.osc_fm) + self.plugins.mod_osc.signals.output = self.plugins.osc.signals.lin_fm + self.signals.fm_waveform = self.plugins.mod_osc.signals.waveform + self.signals.fm_pitch = self.plugins.mod_osc.signals.pitch + self.signals.decay = 1000 + self.signals.attack = 20 + self.signals.waveform = -1 + self.signals.fm_waveform = 0 - def __repr__(self): - ret = tinysynth.__repr__(self) - ret = ret.split("\n") - ret[0] += "_fm" - ret = "\n".join(ret) - ret += "\n " + "\n ".join(repr(self.mod_osc).split("\n")) - return ret - - def fm(self, val): - self.fm_mult = val - self._update_mod_osc() - - def tone(self, val): - self.osc.signals.pitch.tone = val - self._update_mod_osc() - - def freq(self, val): - self.osc.signals.pitch.freq = val - self._update_mod_osc() + self.sync_mod_osc(2.5) - def _update_mod_osc(self): - self.mod_osc.signals.pitch.freq = self.fm_mult * self.osc.signals.pitch.freq + def sync_mod_osc(self, val): + self.signals.fm_pitch.freq = int(val) * self.signals.pitch.freq class sampler(_Patch): - """needs a wave file with path relative to samples/""" + """ + needs a wave file with path relative to /flash/sys/samples/ + """ def __init__(self, chan, filename): + super().__init__(chan) if wave is None: pass # raise Bl00mboxError("wave library not found") f = wave.open("/flash/sys/samples/" + filename, "r") self.len_frames = f.getnframes() - self.sampler = chan._new_bud(696969, self.len_frames) + self.plugins.sampler = chan._new_plugin(696969, self.len_frames) assert f.getsampwidth() == 2 assert f.getnchannels() in (1, 2) @@ -135,12 +117,12 @@ class sampler(_Patch): if f.getnchannels() == 1: # fast path for mono - table = self.sampler.table_bytearray + table = self.plugins.sampler.table_bytearray for i in range(0, self.len_frames * 2, 100): table[i : i + 100] = f.readframes(50) else: # somewhat fast path for stereo - table = self.sampler.table_int16_array + table = self.plugins.sampler.table_int16_array for i in range(self.len_frames): frame = f.readframes(1) value = int.from_bytes(frame[0:2], "little") @@ -148,13 +130,8 @@ class sampler(_Patch): f.close() self._filename = filename - self.sampler.signals.output = chan.mixer - - def start(self): - self.sampler.signals.trigger.start() - - def stop(self): - self.sampler.signals.trigger.stop() + self.signals.trigger = self.plugins.sampler.signals.trigger + self.signals.output = self.plugins.sampler.signals.output @property def filename(self): @@ -162,30 +139,42 @@ class sampler(_Patch): class step_sequencer(_Patch): - def __init__(self, chan): + def __init__(self, chan, num=4): + super().__init__(chan) + if num > 32: + num = 32 + if num < 0: + num = 0 self.seqs = [] - for i in range(4): - seq = chan._new_bud(56709) + prev_seq = None + for i in range(num): + seq = chan._new_plugin(56709) seq.table = [-32767] + ([0] * 16) - if len(self.seqs): - self.seqs[-1].signals.sync_out = seq.signals.sync_in + if prev_seq is None: + self.signals.bpm = seq.signals.bpm + self.signals.beat_div = seq.signals.beat_div + self.signals.step = seq.signals.step + self.signals.step_len = seq.signals.step_len + else: + prev_seq.signals.sync_out = seq.signals.sync_in + prev_seq = seq self.seqs += [seq] - self._bpm = 120 + setattr(self.plugins, "sequencer" + str(i), seq) def __repr__(self): ret = "[patch] step sequencer" # ret += "\n " + "\n ".join(repr(self.seqs[0]).split("\n")) ret += ( "\n bpm: " - + str(self.seqs[0].signals.bpm.value) + + str(self.signals.bpm.value) + " @ 1/" - + str(self.seqs[0].signals.beat_div.value) + + str(self.signals.beat_div.value) ) ret += ( - "\n step: " - + str(self.seqs[0].signals.step.value) + " step: " + + str(self.signals.step.value) + "/" - + str(self.seqs[0].signals.step_len.value) + + str(self.signals.step_len.value) ) ret += "\n [tracks]" for x, seq in enumerate(self.seqs): @@ -196,18 +185,9 @@ class step_sequencer(_Patch): + "".join(["X " if x > 0 else ". " for x in seq.table[1:]]) + "]" ) + ret += "\n" + "\n".join(super().__repr__().split("\n")[1:]) return ret - @property - def bpm(self): - return self._bpm - - @bpm.setter - def bpm(self, bpm): - for seq in self.seqs: - seq.signals.bpm.value = bpm - self._bpm = bpm - def trigger_start(self, track, step): a = self.seqs[track].table a[step + 1] = 32767 @@ -228,18 +208,13 @@ class step_sequencer(_Patch): else: self.trigger_stop(track, step) - @property - def step(self): - return self.seqs[0].signals.step - class fuzz(_Patch): def __init__(self, chan): - self.buds = _PatchBudList() - self.signals = _PatchSignalList() - self.buds.dist = chan.new(bl00mbox.plugins.distortion) - self.signals.input = self.buds.dist.signals.input - self.signals.output = self.buds.dist.signals.output + super().__init__(chan) + self.plugins.dist = chan.new(bl00mbox.plugins.distortion) + self.signals.input = self.plugins.dist.signals.input + self.signals.output = self.plugins.dist.signals.output self._intensity = 2 self._volume = 32767 self._gate = 0 @@ -287,29 +262,27 @@ class fuzz(_Patch): gate = self.gate >> 9 for i in range(64 - gate, 64 + gate): table[i] = 0 - self.buds.dist.table = table + self.plugins.dist.table = table class karplus_strong(_Patch): def __init__(self, chan): - self.buds = _PatchBudList() - self.signals = _PatchSignalList() - - self.buds.noise = chan._new_bud(bl00mbox.plugins.noise_burst) - self.buds.noise.signals.length = 25 + super().__init__(chan) + self.plugins.noise = chan._new_plugin(bl00mbox.plugins.noise_burst) + self.plugins.noise.signals.length = 25 - self.buds.flanger = chan._new_bud(bl00mbox.plugins.flanger) + self.plugins.flanger = chan._new_plugin(bl00mbox.plugins.flanger) - self.buds.flanger.signals.resonance = 32500 - self.buds.flanger.signals.manual.tone = "A2" + self.plugins.flanger.signals.resonance = 32500 + self.plugins.flanger.signals.manual.tone = "A2" - self.buds.flanger.signals.input = self.buds.noise.signals.output + self.plugins.flanger.signals.input = self.plugins.noise.signals.output - self.signals.trigger = self.buds.noise.signals.trigger - self.signals.pitch = self.buds.flanger.signals.manual - self.signals.output = self.buds.flanger.signals.output + self.signals.trigger = self.plugins.noise.signals.trigger + self.signals.pitch = self.plugins.flanger.signals.manual + self.signals.output = self.plugins.flanger.signals.output - self.signals.level = self.buds.flanger.signals.level + self.signals.level = self.plugins.flanger.signals.level self.decay = 1000 @property @@ -318,9 +291,9 @@ class karplus_strong(_Patch): @decay.setter def decay(self, val): - tone = self.buds.flanger.signals.manual.tone + tone = self.plugins.flanger.signals.manual.tone loss = (50 * (2 ** (-tone / 12))) // (val / 1000) if loss < 2: loss = 2 - self.buds.flanger.signals.resonance = 32767 - loss + self.plugins.flanger.signals.resonance = 32767 - loss self._decay = val diff --git a/python_payload/bl00mbox/_user.py b/python_payload/bl00mbox/_user.py index d78b9001e93e845439c98d1826440e9704cd7910..faee8855db7829c7896fffe6f726b9ea72928e0c 100644 --- a/python_payload/bl00mbox/_user.py +++ b/python_payload/bl00mbox/_user.py @@ -14,22 +14,22 @@ class Bl00mboxError(Exception): pass -def _makeSignal(bud, signal_num): +def _makeSignal(plugin, signal_num): hints = sys_bl00mbox.channel_bud_get_signal_hints( - bud.channel_num, bud.bud_num, signal_num + plugin.channel_num, plugin.bud_num, signal_num ) if hints & 2: - signal = SignalOutput(bud, signal_num) + signal = SignalOutput(plugin, signal_num) signal._hints = "output" elif hints & 1: if hints & 4: - signal = SignalInputTrigger(bud, signal_num) + signal = SignalInputTrigger(plugin, signal_num) signal._hints = "input/trigger" elif hints & 32: - signal = SignalInputPitch(bud, signal_num) + signal = SignalInputPitch(plugin, signal_num) signal._hints = "input/pitch" else: - signal = SignalInput(bud, signal_num) + signal = SignalInput(plugin, signal_num) signal._hints = "input" return signal @@ -44,7 +44,7 @@ class ChannelMixer: ret += " (" + str(len(self.connections)) + " connections)" for con in self.connections: ret += "\n " + con.name - ret += " in [bud " + str(con._bud.bud_num) + "] " + con._bud.name + ret += " in [plugin " + str(con._plugin.bud_num) + "] " + con._plugin.name return ret def _unplug_all(self): @@ -61,33 +61,33 @@ class ChannelMixer: s = sys_bl00mbox.channel_get_signal_by_mixer_list_pos( self._channel.channel_num, i ) - sig = Signal(Bud(self._channel, 0, bud_num=b), s) + sig = Signal(Plugin(self._channel, 0, bud_num=b), s) ret += [sig] return ret class Signal: - def __init__(self, bud, signal_num): - self._bud = bud + def __init__(self, plugin, signal_num): + self._plugin = plugin self._signal_num = signal_num self._name = sys_bl00mbox.channel_bud_get_signal_name( - bud.channel_num, bud.bud_num, signal_num + plugin.channel_num, plugin.bud_num, signal_num ) mpx = sys_bl00mbox.channel_bud_get_signal_name_multiplex( - bud.channel_num, bud.bud_num, signal_num + plugin.channel_num, plugin.bud_num, signal_num ) if mpx != (-1): self._name += str(mpx) self._description = sys_bl00mbox.channel_bud_get_signal_description( - bud.channel_num, bud.bud_num, signal_num + plugin.channel_num, plugin.bud_num, signal_num ) self._unit = sys_bl00mbox.channel_bud_get_signal_unit( - bud.channel_num, bud.bud_num, signal_num + plugin.channel_num, plugin.bud_num, signal_num ) self._hints = "" def __repr__(self): - self._bud._check_existence() + self._plugin._check_existence() ret = self.name if len(self.unit): @@ -110,10 +110,10 @@ class Signal: conret += [ direction + con.name - + " in [bud " - + str(con._bud.bud_num) + + " in [plugin " + + str(con._plugin.bud_num) + "] " - + con._bud.name + + con._plugin.name ] if isinstance(con, ChannelMixer): conret += [" ==> [channel mixer]"] @@ -146,9 +146,9 @@ class Signal: @property def value(self): - self._bud._check_existence() + self._plugin._check_existence() return sys_bl00mbox.channel_bud_get_signal_value( - self._bud.channel_num, self._bud.bud_num, self._signal_num + self._plugin.channel_num, self._plugin.bud_num, self._signal_num ) @@ -157,29 +157,31 @@ class SignalOutput(Signal): def value(self, val): if val == None: sys_bl00mbox.channel_disconnect_signal_tx( - self._bud.channel_num, self._bud.bud_num, self._signal_num + self._plugin.channel_num, self._plugin.bud_num, self._signal_num ) elif isinstance(val, SignalInput): val.value = self elif isinstance(val, ChannelMixer): - if val._channel.channel_num == self._bud.channel_num: + if val._channel.channel_num == self._plugin.channel_num: sys_bl00mbox.channel_connect_signal_to_output_mixer( - self._bud.channel_num, self._bud.bud_num, self._signal_num + self._plugin.channel_num, self._plugin.bud_num, self._signal_num ) @property def connections(self): cons = [] - chan = self._bud.channel_num - bud = self._bud.bud_num + chan = self._plugin.channel_num + bud_num = self._plugin.bud_num sig = self._signal_num - for i in range(sys_bl00mbox.channel_subscriber_num(chan, bud, sig)): - b = sys_bl00mbox.channel_get_bud_by_subscriber_list_pos(chan, bud, sig, i) + for i in range(sys_bl00mbox.channel_subscriber_num(chan, bud_num, sig)): + b = sys_bl00mbox.channel_get_bud_by_subscriber_list_pos( + chan, bud_num, sig, i + ) s = sys_bl00mbox.channel_get_signal_by_subscriber_list_pos( - chan, bud, sig, i + chan, bud_num, sig, i ) if (s >= 0) and (b > 0): - cons += [_makeSignal(Bud(Channel(chan), 0, bud_num=b), s)] + cons += [_makeSignal(Plugin(Channel(chan), 0, bud_num=b), s)] elif s == -1: cons += [ChannelMixer(Channel(chan))] return cons @@ -188,18 +190,18 @@ class SignalOutput(Signal): class SignalInput(Signal): @Signal.value.setter def value(self, val): - self._bud._check_existence() + self._plugin._check_existence() if isinstance(val, SignalOutput): if len(self.connections): if not sys_bl00mbox.channel_disconnect_signal_rx( - self._bud.channel_num, self._bud.bud_num, self._signal_num + self._plugin.channel_num, self._plugin.bud_num, self._signal_num ): return sys_bl00mbox.channel_connect_signal( - self._bud.channel_num, - self._bud.bud_num, + self._plugin.channel_num, + self._plugin.bud_num, self._signal_num, - val._bud.bud_num, + val._plugin.bud_num, val._signal_num, ) elif isinstance(val, SignalInput): @@ -208,23 +210,26 @@ class SignalInput(Signal): elif (type(val) == int) or (type(val) == float): if len(self.connections): if not sys_bl00mbox.channel_disconnect_signal_rx( - self._bud.channel_num, self._bud.bud_num, self._signal_num + self._plugin.channel_num, self._plugin.bud_num, self._signal_num ): return sys_bl00mbox.channel_bud_set_signal_value( - self._bud.channel_num, self._bud.bud_num, self._signal_num, int(val) + self._plugin.channel_num, + self._plugin.bud_num, + self._signal_num, + int(val), ) @property def connections(self): cons = [] - chan = self._bud.channel_num - bud = self._bud.bud_num + chan = self._plugin.channel_num + bud = self._plugin.bud_num sig = self._signal_num b = sys_bl00mbox.channel_get_source_bud(chan, bud, sig) s = sys_bl00mbox.channel_get_source_signal(chan, bud, sig) if (s >= 0) and (b > 0): - cons += [_makeSignal(Bud(Channel(chan), 0, bud_num=b), s)] + cons += [_makeSignal(Plugin(Channel(chan), 0, bud_num=b), s)] return cons @@ -240,8 +245,8 @@ class SignalInputTrigger(SignalInput): class SignalInputPitch(SignalInput): - def __init__(self, bud, signal_num): - SignalInput.__init__(self, bud, signal_num) + def __init__(self, plugin, signal_num): + SignalInput.__init__(self, plugin, signal_num) @property def tone(self): @@ -271,26 +276,26 @@ class SignalInputPitch(SignalInput): class SignalList: - def __init__(self, bud): + def __init__(self, plugin): self._list = [] for signal_num in range( - sys_bl00mbox.channel_bud_get_num_signals(bud.channel_num, bud.bud_num) + sys_bl00mbox.channel_bud_get_num_signals(plugin.channel_num, plugin.bud_num) ): hints = sys_bl00mbox.channel_bud_get_signal_hints( - bud.channel_num, bud.bud_num, signal_num + plugin.channel_num, plugin.bud_num, signal_num ) if hints & 2: - signal = SignalOutput(bud, signal_num) + signal = SignalOutput(plugin, signal_num) signal._hints = "output" elif hints & 1: if hints & 4: - signal = SignalInputTrigger(bud, signal_num) + signal = SignalInputTrigger(plugin, signal_num) signal._hints = "input/trigger" elif hints & 32: - signal = SignalInputPitch(bud, signal_num) + signal = SignalInputPitch(plugin, signal_num) signal._hints = "input/pitch" else: - signal = SignalInput(bud, signal_num) + signal = SignalInput(plugin, signal_num) signal._hints = "input" self._list += [signal] setattr(self, signal.name.split(" ")[0], signal) @@ -303,7 +308,7 @@ class SignalList: super().__setattr__(key, value) -class Bud: +class Plugin: def __init__(self, channel, plugin_id, init_var=0, bud_num=None): self._channel_num = channel.channel_num if bud_num == None: @@ -312,7 +317,7 @@ class Bud: self.channel_num, self.plugin_id, init_var ) if self._bud_num == None: - raise Bl00mboxError("bud init failed") + raise Bl00mboxError("plugin init failed") else: self._bud_num = bud_num self._check_existence() @@ -324,7 +329,7 @@ class Bud: def __repr__(self): self._check_existence() - ret = "[bud " + str(self.bud_num) + "] " + self.name + ret = "[plugin " + str(self.bud_num) + "] " + self.name for sig in self.signals._list: ret += "\n " + "\n ".join(repr(sig).split("\n")) return ret @@ -335,7 +340,7 @@ class Bud: def _check_existence(self): if not sys_bl00mbox.channel_bud_exists(self.channel_num, self.bud_num): - raise Bl00mboxError("bud has been deleted") + raise Bl00mboxError("plugin has been deleted") @property def channel_num(self): @@ -429,9 +434,9 @@ class Channel: ret += " (background mute override)" ret += "\n volume: " + str(self.volume) b = sys_bl00mbox.channel_buds_num(self.channel_num) - ret += "\n buds: " + str(b) - if len(self.buds) != b: - ret += " (desync" + str(len(self.buds)) + ")" + ret += "\n plugins: " + str(b) + if len(self.plugins) != b: + ret += " (desync" + str(len(self.plugins)) + ")" ret += "\n " + "\n ".join(repr(self.mixer).split("\n")) return ret @@ -463,16 +468,16 @@ class Channel: self.clear() sys_bl00mbox.channel_set_free(self.channel_num, val) - def _new_bud(self, thing, init_var=None): - bud_init_var = 0 + def _new_plugin(self, thing, init_var=None): + plugin_init_var = 0 if (type(init_var) == int) or (type(init_var) == float): - bud_init_var = int(init_var) - bud = None + plugin_init_var = int(init_var) + plugin = None if isinstance(thing, bl00mbox._plugins._Plugin): - bud = Bud(self, thing.plugin_id, bud_init_var) + plugin = Plugin(self, thing.plugin_id, plugin_init_var) if type(thing) == int: - bud = Bud(self, thing, bud_init_var) - return bud + plugin = Plugin(self, thing, plugin_init_var) + return plugin def _new_patch(self, patch, init_var=None): if init_var == None: @@ -499,17 +504,17 @@ class Channel: if issubclass(thing, bl00mbox.patches._Patch): return self._new_patch(thing, init_var) if isinstance(thing, bl00mbox._plugins._Plugin) or (type(thing) == int): - return self._new_bud(thing, init_var) + return self._new_plugin(thing, init_var) @property - def buds(self): - buds = [] + def plugins(self): + plugins = [] for i in range(sys_bl00mbox.channel_buds_num(self.channel_num)): b = sys_bl00mbox.channel_get_bud_by_list_pos(self.channel_num, i) - bud = Bud(self, 0, bud_num=b) - buds += [bud] - return buds + plugin = Plugin(self, 0, bud_num=b) + plugins += [plugin] + return plugins @property def channel_num(self): diff --git a/python_payload/mypystubs/bl00mbox/_patches.pyi b/python_payload/mypystubs/bl00mbox/_patches.pyi index 69c1d88e4d3e3c2796a2a42cccc92e8b5f49a0b0..50581ef2e28c190f132425a3d21a718ce9284849 100644 --- a/python_payload/mypystubs/bl00mbox/_patches.pyi +++ b/python_payload/mypystubs/bl00mbox/_patches.pyi @@ -2,47 +2,42 @@ import bl00mbox from typing import List -class _Patch: ... +class _Patch: + plugins: _PatchPluginList + signals: _PatchSignalList class _PatchSignalList: def __getattr__(self, name: str) -> bl00mbox.Signal: ... - -class _PatchBudList: - def __getattr__(self, name: str) -> bl00mbox.Bud: ... - -class tinysynth(_Patch): - def decay(self, v: float) -> None: ... - def waveform(self, v: int) -> None: ... - def attack(self, v: float) -> None: ... - def volume(self, v: float) -> None: ... - def sustain(self, v: float) -> None: ... - def release(self, v: float) -> None: ... - def tone(self, v: float) -> None: ... - def start(self) -> None: ... - def stop(self) -> None: ... - -class tinysynth_fm(tinysynth): - def fm_waveform(self, v: int) -> None: ... - def fm(self, v: float) -> None: ... + def __setattr__( + self, + name: str, + value: int | float | bl00mbox.Signal | bl00mbox.ChannelMixer, + ) -> None: ... + +class _PatchPluginList: + def __getattr__(self, name: str) -> bl00mbox.Plugin: ... + def __setattr__( + self, + name: str, + value: bl00mbox.Plugin, + ) -> None: ... + +class tinysynth(_Patch): ... +class tinysynth_fm(tinysynth): ... class step_sequencer(_Patch): - seqs: List[bl00mbox.Bud] + seqs: List[bl00mbox.Plugin] bpm: int def trigger_state(self, track: int, i: int) -> bool: ... def trigger_toggle(self, track: int, i: int) -> None: ... -class sampler(_Patch): - sampler: bl00mbox.Bud +class sampler(_Patch): ... class fuzz(_Patch): - buds: _PatchBudList - signals: _PatchSignalList volume: int intensity: float gate: int class karplus_strong(_Patch): - buds: _PatchBudList - signals: _PatchSignalList decay: int diff --git a/python_payload/mypystubs/bl00mbox/_user.pyi b/python_payload/mypystubs/bl00mbox/_user.pyi index 74ff88265021c888872c8ee4a2f4d91faf98667e..054bb681cba785d1f423051efb5d208163932cf2 100644 --- a/python_payload/mypystubs/bl00mbox/_user.pyi +++ b/python_payload/mypystubs/bl00mbox/_user.pyi @@ -13,7 +13,7 @@ class Signal: value: Any # value: None | int | float | "Signal" | "ChannelMixer" - def __init__(self, bud: "Bud", signal_num: int): ... + def __init__(self, plugin: "Plugin", signal_num: int): ... # SignalInputTrigger functions def start(self, velocity: int = 32767) -> None: ... @@ -28,7 +28,7 @@ class SignalList: def __setattr__(self, name: str, value: Signal | int | "ChannelMixer") -> None: ... def __getattr__(self, name: str) -> Signal: ... -class Bud: +class Plugin: signals: SignalList def __init__( @@ -51,11 +51,11 @@ class Channel: def __init__(self, name: str): ... def clear(self) -> None: ... def _new_patch(self, patch: Type[T], init_var: Optional[Any] = None) -> T: ... - def _new_bud( + def _new_plugin( self, thing: bl00mbox._plugins._Plugin | int, init_var: Optional[int | float] = None, - ) -> Bud: ... + ) -> Plugin: ... @overload def new(self, thing: Type[P], init_var: Optional[Any] = None) -> P: ... @overload @@ -63,7 +63,7 @@ class Channel: self, thing: bl00mbox._plugins._Plugin | int, init_var: Optional[int | float] = None, - ) -> Bud: ... + ) -> Plugin: ... class ChannelMixer: _channel: Channel