-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added arpegg.py, working with voices.py (for latest chan).
- Loading branch information
Showing
3 changed files
with
252 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
"""Arpeggiator for midi input.""" | ||
|
||
import time | ||
import random | ||
|
||
class ArpeggiatorSynth: | ||
"""Create arpeggios.""" | ||
# State | ||
synth = None # Downstream synthesizer object. | ||
current_active_notes = None # Set of notes currently down on keyboard. | ||
arpeggiate_base_notes = None # Set of notes currently driving the arpeggio. | ||
full_sequence = None # List of notes in arpeggio (including direction and octave). | ||
current_note = None # Last note sent to synth (i.e., next note-off). | ||
current_step = -1 # Current position in sequence. | ||
running = False # Currently mid-sequence. Goes false if no notes are playing. | ||
# UI control items | ||
active = False | ||
hold = False | ||
octaves = 1 | ||
direction = "up" | ||
period_ms = 125 | ||
# Velocity for all the notes generated by the sequencer. | ||
velocity = 0.5 | ||
# Notes at or above the split_note are always passed through live, not sequenced. | ||
split_note = 128 # Split is off the end of the keyboard, i.e., inactive. | ||
|
||
def __init__(self, synth, channel=0): | ||
self.synth = synth | ||
self.channel = channel # This is just bookkeeping for my owner, not used. | ||
self.current_active_notes = set() | ||
self.arpeggiate_base_notes = set() | ||
self.full_sequence = [] | ||
|
||
def note_on(self, note, vel): | ||
if not self.active or note >= self.split_note: | ||
return self.synth.note_on(note, vel) | ||
if self.hold and not self.current_active_notes: | ||
# First note after all keys off resets hold set. | ||
self.arpeggiate_base_notes = set() | ||
# Adding keys to some already down. | ||
self.current_active_notes.add(note) | ||
# Because it's a set, can't get more than one instance of a base note. | ||
self.arpeggiate_base_notes.add(note) | ||
self._update_full_sequence() | ||
|
||
def note_off(self, note): | ||
if not self.active or note >= self.split_note: | ||
return self.synth.note_off(note) | ||
#print(self.current_active_notes, self.arpeggiate_base_notes) | ||
# Update our internal record of keys currently held down. | ||
self.current_active_notes.remove(note) | ||
if not self.hold: | ||
# If not hold, remove notes from active set when released. | ||
self.arpeggiate_base_notes.remove(note) | ||
self._update_full_sequence() | ||
|
||
def _update_full_sequence(self): | ||
"""The full note loop given base_notes, octaves, and direction.""" | ||
# Basic notes, ascending. | ||
basic_notes = sorted(self.arpeggiate_base_notes) | ||
# Apply octaves | ||
notes = [] | ||
for o in range(self.octaves): | ||
notes = notes + [n + 12 * o for n in basic_notes] | ||
# Apply direction | ||
if self.direction == "down": | ||
notes = notes[::-1] | ||
elif self.direction == "updown": | ||
notes = notes + notes[-2:0:-1] | ||
self.full_sequence = notes | ||
if self.full_sequence and not self.running: | ||
# Prepare to start a new sequence at the first note. | ||
self.current_step = -1 | ||
# Semaphore to the run loop to start going. | ||
self.running = True | ||
|
||
def next_note(self): | ||
if self.current_note: | ||
self.synth.note_off(self.current_note) | ||
self.current_note = None | ||
if self.full_sequence: | ||
if self.direction == "rand": | ||
self.current_step = random.randint(0, len(self.full_sequence) - 1) | ||
else: | ||
self.current_step = (self.current_step + 1) % len(self.full_sequence) | ||
self.current_note = self.full_sequence[self.current_step] | ||
self.synth.note_on(self.current_note, self.velocity) | ||
else: | ||
self.running = False | ||
|
||
def run(self): | ||
# Endless function that will emit sequencer notes when there are arpeggiate_base_notes. | ||
while True: | ||
if not self.running: | ||
time.sleep_ms(10) # Break up the loop a little | ||
else: | ||
# self.running started sequence. | ||
# Another brief pause to let all keys go down | ||
time.sleep_ms(10) | ||
# Cycle the notes as long as we have them. | ||
while self.running: | ||
self.next_note() | ||
time.sleep_ms(self.period_ms) | ||
|
||
def control_change(self, control, value): | ||
#if not self.active: | ||
# return self.synth.control_change(control, value) | ||
if control == self.rate_control_num: | ||
self.period_ms = 25 + 5 * value # 25 to 665 ms | ||
elif control == self.octaves_control_num: | ||
self.cycle_octaves() | ||
elif control == self.direction_control_num: | ||
self.cycle_direction() | ||
else: | ||
self.synth.control_change(control, value) | ||
self._update_full_sequence() | ||
|
||
def program_change(self, patch_number): | ||
self.synth.program_change(patch_number) | ||
|
||
@property | ||
def num_voices(self): | ||
return self.synth.num_voices | ||
|
||
@property | ||
def patch_number(self): | ||
return self.synth.patch_number | ||
|
||
def _cycle_octaves(self): | ||
self.octaves = 1 + (self.octaves % 3) | ||
|
||
def _cycle_direction(self): | ||
if self.direction == 'up': | ||
self.direction = 'down' | ||
elif self.direction == 'down': | ||
self.direction = 'updown' | ||
elif self.direction == 'updown': | ||
self.direction = 'rand' | ||
else: | ||
self.direction = 'up' | ||
|
||
def set(self, arg, val=None): | ||
"""Callback for external control.""" | ||
#print("arp set", arg, val) | ||
#if self.active: | ||
# return self.synth.set(arg, val) | ||
if arg == 'on': | ||
self.active = val | ||
# Reset hold state when on/off changes. | ||
self.arpeggiate_base_notes = set() | ||
elif arg == 'hold': | ||
self.hold = val | ||
# Copy across the current_active_notes after a change in hold. | ||
self.arpeggiate_base_notes = set(self.current_active_notes) | ||
elif arg == 'arp_rate': | ||
self.period_ms = int(1000 / (2.0 ** (5 * val))) # 1 Hz to 32 Hz | ||
elif arg == 'octaves': | ||
self.octaves = val | ||
elif arg == 'direction': | ||
self.direction = val | ||
self._update_full_sequence() | ||
|
||
def get_new_voices(self, num_voices): | ||
return self.synth.get_new_voices(num_voices) | ||
|
Oops, something went wrong.