| """ | |
| ## Sol DrumBud v0.1 ## | |
| ## Leonora Tindall <nora@nora.codes> ## | |
| ## Licensed GPLv.3 ## | |
| A configurable drum trigger helper for Sol. | |
| Use it to trigger 8 more drums from the Beatstep Pro, or trigger your complex | |
| Boolean logic rhythms from 8 more directions. | |
| Provides 8 percussion triggers on the Sol's outputs. | |
| By default works with MIDI channel 10 and notes 44 to 51 | |
| (the Beatstep Pro's upper row). | |
| If using with ORCA, please recall that what is often called MIDI Ch 1 | |
| is really Ch 0, and ORCA uses the real MIDI number. | |
| Throughout the source code, I will refer to MIDI channels starting at 1. | |
| """ | |
| # ----------------- Config ---------------------------------------------------# | |
| MIDI_CHANNEL = 10 | |
| # Keys (numbers on the left) are MIDI note values indexed from 0. | |
| # Values (strings on the right) are output names. | |
| CHANNEL_CONFIG = { | |
| 44: "a", | |
| 45: 1, | |
| 46: "b", | |
| 47: 2, | |
| 48: "c", | |
| 49: 3, | |
| 50: "d", | |
| 51: 4 | |
| } | |
| # ----------------- Script ---------------------------------------------------# | |
| import winterbloom_sol as sol | |
| from winterbloom_sol import trigger | |
| import winterbloom_smolmidi as midi | |
| CHANNEL_NUMBERS = [1, 2, 3, 4] | |
| CHANNEL_NAMES = ["a", "b", "c", "d"] | |
| class CvAsGateState: | |
| "Contains the state of a CV channel's trigger value, to be updated by Trigger.step()" | |
| def __init__(self): | |
| self.value = False | |
| self.trigger = trigger.Trigger(self) | |
| self.retrigger = trigger.Retrigger(self) | |
| def step(self): | |
| self.trigger.step() | |
| self.retrigger.step() | |
| def voltage(self): | |
| if self.value: | |
| return 5.0 | |
| else: | |
| return 0.0 | |
| # Channel correspondance setup | |
| pseudogates = {} | |
| for cv_output in CHANNEL_NAMES: | |
| pseudogates[cv_output] = CvAsGateState() | |
| def loop(last, state, outputs): | |
| # Always update the state of the pseudogates | |
| global pseudogates | |
| for channel_name, pseudogate in pseudogates.items(): | |
| pseudogate.step() | |
| outputs.set_cv(channel_name, pseudogate.voltage()) | |
| # Check whether or not there's a new MIDI message to process. | |
| if state.message is last.message or state.message is None: | |
| return # No change! | |
| # Messages without a channel are useless to us. | |
| if state.message.channel is None: | |
| print(f"ignoring message no channel") | |
| return # Don't care. | |
| ch = state.message.channel + 1 # MIDI channels are indexed from 0 | |
| # Messages on a channel we're not configured for aren't useful. | |
| if ch is not MIDI_CHANNEL: | |
| print(f"ignoring message wrong channel {ch}") | |
| return # Don't care. | |
| if state.message.type is midi.NOTE_ON and state.message.data[0] in CHANNEL_CONFIG.keys(): | |
| output = CHANNEL_CONFIG[state.message.data[0]] | |
| if type(output) is int and output in CHANNEL_NUMBERS: | |
| print(f"trigger real gate {output}") | |
| outputs.trigger_gate(output) | |
| elif type(output) is str and len(output) is 1 and output in CHANNEL_NAMES: | |
| print(f"trigger pseudogate {output}") | |
| pseudogates[output].trigger.trigger() | |
| else: | |
| print("bad configuration {output} is neither int from 1 to 4 nor string from 'a' to 'd'") | |
| else: | |
| print(f"ignoring message wrong type {state.message.type} or note {state.message.data[0]}") | |
| sol.run(loop) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment