Skip to content

Instantly share code, notes, and snippets.

@NoraCodes
Created October 7, 2020 00:28
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NoraCodes/3c838d3f55903867574eb7ee5fc94141 to your computer and use it in GitHub Desktop.
Save NoraCodes/3c838d3f55903867574eb7ee5fc94141 to your computer and use it in GitHub Desktop.
"""
## 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