There's a fair amount of code in this project! Let's look at the highlights.
You can reconfigure the pins used for triggers by updating this block. The number of triggers can be fewer or more than the 16 shown here.
# Configure the pins to use -- earlier in list = higher priority pads = [ board.GP0, board.GP1, board.GP2, board.GP3, board.GP4, board.GP5, board.GP6, board.GP7, board.GP8, board.GP9, board.GP10, board.GP11, board.GP12, board.GP13, board.GP14, board.GP15 ]
You can select a greater number of simultaneous voices (polyphony). Further down there's an additional limit of at most 4 simultaneous MP3s.
# Configure max voices to play at once # (No matter what, at most 4 MP3 decoders) # If set this number too high, playback will stutter. use lower bit rates or fewer voices # # when the number of active samples being played back exceeds the number of voices, # the top numbered playing sample is stopped. There is no logic to restore a sample that # got stopped in this way. # # (this may not be the same as the old FX board logic) max_simultaneous_voices = 2
You can create an I2SOut on different pins, or change to a different kind of audio output altogether (such as PWMAudioOut
)
audiodev = audiobusio.I2SOut( bit_clock=board.GP16, word_select=board.GP17, data=board.GP18 )
Each kind of trigger derives from TriggerBase and defines several items:
- The filename fragments associated with this kind of trigger. This is the part of the filename immediately after the T## trigger number. It should be a list with at least one string in it. The kind of trigger is chosen based on matching the first stem in the list, so each trigger needs to have a unique stem compared to all the others.
- The action to take when the associated trigger is pressed
- The action to take when the associated trigger is released
Usually one trigger will call self.play. If the play is looping, then the other trigger will probably call self.force_off.
If no action is called for in a press or release, provide a method that just says "pass".
Each kind of trigger also needs to be added to the list of trigger_classes.
The basic stem doesn't loop, so it only needs to do something on press:
class BasicTrigger(TriggerBase): """Plays a file each time the button is pressed down""" stems = [""] def on_press(self): self.play(self.filenames[0]) def on_release(self): pass
The HoldLoopingTrigger demonstrates that samples can be looped and shows use of force_off in the on_release method:
class HoldLoopingTrigger(TriggerBase): """Plays a file as long as a button is held down""" stems = ["HOLDL"] def on_press(self): self.play(self.filenames[0], loop=True) def on_release(self): self.force_off()
The PlayRandomTrigger class shows how to select one out of a list of different files. Notice how stems will be a list of the strings RAND0, RAND1, ..., RAND9. It uses random_choice, a function that works similar to standard Python's random.choice function:
class PlayRandomTrigger(TriggerBase): stems = [f"RAND{i}" for i in range(10)] def on_press(self): self.play(random_choice(self.filenames)) def on_release(self): pass
In the TriggerBase class, the play function starts playing the new sample, after stopping another channel from playing if necessary (inside ensure_available_voice; similarly, an MP3 channel is freed if necessary by ensure_available_decoder):
class TriggerBase: ... def get_sample(self, path): if path.endswith(".mp3"): self._decoder = ensure_available_decoder() self._decoder.open(path) return self._decoder else: return audiocore.WaveFile(path) def play(self, path, loop=False): self.force_off() free_stopped_channels() sample = self.get_sample(path) self.voice = ensure_available_voice() self.voice.play(sample, loop=loop)
The main loop just checks for key events and calls on_press/on_release as needed (it also spews some debugging information to the serial console):
while True: if e := keys.events.get(): print("event", e) print("available decoders", *(id(i) for i in available_decoders)) print("available voices", *(id(i) for i in available_voices)) trigger = triggers[e.key_number] if e.pressed: trigger.on_press() else: trigger.on_release() print(triggers)
Page last edited October 09, 2024
Text editor powered by tinymce.