diff --git a/docs/music.md b/docs/music.md index 93196a2c..edbd7bec 100644 --- a/docs/music.md +++ b/docs/music.md @@ -146,7 +146,13 @@ for i,note in enumerate(chord): # each note on will play precisely one second after the last ``` -Remember to `release` your synths when you're done with them +You can send `all_notes_off()` to your synth to stop playing notes: + +```python +synth4.all_notes_off() +``` + +If you are booting a new Synth in your program, remember to `release` your synths when you're done with them ```python synth1.release() # Does all note-off and then clears the voice alloc synth2.release() @@ -155,6 +161,18 @@ synth4.release() As you learn more about AMY (the underlying synth engine) you may be interested in making your own `Synth`s in Python. See `midi.py`'s `OscSynth` for an example. +## Modifying the default synth or other MIDI channel assignments in code + +You may want to programatically change the MIDI to synth mapping. One example would be to lower the polyphony of the booted by default 6-note synth on channel 1, so that notes coming in through MIDI don't impact the performance or polyphony of your app. Or if you want to set up your music app to receive different patches on different MIDI channels. + +You can change the parameters of channel synths like: + +```python +midi.config.add_synth(channel, patch, polyphony) +``` + +Note that `add_synth` will stop any running Synth on that channel and boot a new one in its place. + ## The editor You can get a lot done in the Tulip REPL just playing around. But you'll eventually want to save your creations and run them alongside other things. Let's start up the Tulip Editor and save your first program. You can go ahead and quit the drum machine if you want, and remember to run @@ -269,11 +287,18 @@ Now quit the `jam2` app if it was already running and re-`run` it. You should se ## Sampler, OscSynth -The drum machine in Tulip uses a slightly different `Synth` called `OscSynth`. You can use AMY directly with `OscSynth`, with one oscillator per voice of polyphony. Let's try it as a sampler. There are 29 samples of drum-like and some instrument sounds in Tulip, and it can adjust the pitch and pan and loop of each one. You can try it out by just +The drum machine in Tulip uses a slightly different `Synth` called `OscSynth`. You can use AMY directly with `OscSynth`, with one oscillator per voice of polyphony. Like this simple sine wave synth: ```python +s = midi.OscSynth(wave=amy.SINE) +s.note_on(60,1) +s.note_off(60) +``` + +Let's try it as a sampler. There are 29 samples of drum-like and some instrument sounds in Tulip, and it can adjust the pitch and pan and loop of each one. You can try it out by just -# You can pass any AMY arguments to the setup of OscSynth, after you specify how many voices you want +```python +# You can pass any AMY arguments to the setup of OscSynth s = midi.OscSynth(wave=amy.PCM, patch=10) # PCM wave type, patch=10 s.note_on(50, 1.0) diff --git a/docs/tulip_api.md b/docs/tulip_api.md index 8d8e2b65..47f5f759 100644 --- a/docs/tulip_api.md +++ b/docs/tulip_api.md @@ -491,11 +491,11 @@ By default, Tulip boots into a live MIDI synthesizer mode. Any note-ons, note-of By default, MIDI notes on channel 1 will map to Juno-6 patch 0. And MIDI notes on channel 10 will play the PCM samples (like a drum machine). -You can adjust which voices are sent with `tulip.music_map(channel, patch_number, voice_count)`. For example, you can have Tulip play DX7 patch 129 on channel 2 with `tulip.music_map(2, 129)`. The channel is a MIDI channel (we use 1-16 indexing), the patch_number is an AMY patch number, and voice_count is the optional number of voices (polyphony) you want to support for that channel and patch. +You can adjust which voices are sent with `midi.config.add_synth(channel, patch, polyphony)`. For example, you can have Tulip play DX7 patch 129 on channel 2 with `midi.config.add_synth(channel=2, patch=129, polyphony=1)`. The `2`, channel, is a MIDI channel (we use 1-16 indexing), the patch `129` is an AMY patch number, `1` is the number of voices (polyphony) you want to support for that channel and patch. -(A good rule of thumb is Tulip CC can support about 8 simultaneous total voices for Juno-6 and DX7, and 20-30 total voices for PCM and more for other simpler oscillator patches.) +(A good rule of thumb is Tulip CC can support about 6 simultaneous total voices for Juno-6, 8-10 for DX7, and 20-30 total voices for PCM and more for other simpler oscillator patches.) -These mappings will get reset to default on boot. If you want to save them, put tulip.music_map() commands in your boot.py. +These mappings will get reset to default on boot. If you want to save them, put `add_synth` commands in your boot.py. You can set up your own MIDI callbacks in your own programs. You can call `midi.add_callback(function)`, which will call your `function` with a list of a (2 or 3-byte) MIDI message. These callbacks will get called alongside the default MIDI callback (that plays synth notes on MIDI in). You can stop the default MIDI callback with `midi.stop_default_callback()` and start it again with `midi.start_default_callback()`. @@ -504,8 +504,6 @@ On Tulip Desktop, MIDI works on macOS 11.0 (Big Sur, released 2020) and later po You can also send MIDI messages "locally", e.g. to a running Tulip program that is expecting hardware MIDI input, via `tulip.midi_local()` ```python -tulip.music_map(1,129) # change MIDI channel 1 to patch 129. - def callback(m): if(m[0]==144): print("Note on, note # %d velocity # %d" % (m[1], m[2])) diff --git a/tulip/shared/py/juno6.py b/tulip/shared/py/juno6.py index b06828f5..33484a81 100644 --- a/tulip/shared/py/juno6.py +++ b/tulip/shared/py/juno6.py @@ -465,7 +465,6 @@ def setup_from_patch_number(patch_number): # Use no fewer than 4. #num_amy_voices = 0 if amy_voices == None else len(amy_voices) #polyphony = max(4, num_amy_voices) - #tulip.music_map(midi_channel, patch_number, polyphony) midi.config.program_change(midi_channel, patch_number) _, amy_voices = midi.config.channel_info(midi_channel) #jp = juno.JunoPatch() #.from_patch_number(patch_number) diff --git a/tulip/shared/py/midi.py b/tulip/shared/py/midi.py index 97193757..8e90e33b 100644 --- a/tulip/shared/py/midi.py +++ b/tulip/shared/py/midi.py @@ -53,16 +53,6 @@ def program_change(self, channel, patch): # update the map self.synth_per_channel[channel].program_change(patch) - def music_map(self, channel, patch_number=0, voice_count=None): - """Implement the tulip music_map API.""" - if (not voice_count - or (channel in self.synth_per_channel - and self.synth_per_channel[channel].num_voices == voice_count)): - # Simply changing patch. - self.program_change(channel, patch_number) - else: - # Setting up a new channel. - self.add_synth(channel, patch_number, voice_count) def get_active_channels(self): """Return numbers of MIDI channels with allocated synths.""" return list(self.synth_per_channel.keys()) @@ -670,25 +660,6 @@ def c_fired_midi_event(x): m = tulip.midi_in() -# Keep this -- this is a tulip API -def music_map(channel, patch_number=None, voice_count=None): - """API to set a patch and polyphony for a given MIDI channel.""" - config.music_map(channel, patch_number, voice_count) - try: - # Update voices UI if it is running. - # (But watch out for circularity - voices calls music_map too). - voices_app = tulip.running_apps.get("voices", None) - #voices_app.refresh_with_new_music_map() # Not yet implemented! - except: - pass - try: - # Update juno6 UI if it is running. - juno6_app = tulip.running_apps.get("juno6", None) - juno6_app.refresh_with_new_music_map() - except: - pass - - def deferred_midi_config(t): setup_midi_codes() setup_global_midi_cc_bindings() diff --git a/tulip/shared/py/tulip.py b/tulip/shared/py/tulip.py index 288afbc9..d0eda4a5 100644 --- a/tulip/shared/py/tulip.py +++ b/tulip/shared/py/tulip.py @@ -30,8 +30,6 @@ def sys(): import midi -from midi import music_map - # convert tulip RGB332 pal_idx to 3 rgb 0-255 values def rgb(px0, wide=False): r = px0 & 0xe0; diff --git a/tulip/shared/py/voices.py b/tulip/shared/py/voices.py index 66ab10f5..a7fc25f2 100644 --- a/tulip/shared/py/voices.py +++ b/tulip/shared/py/voices.py @@ -265,8 +265,7 @@ def update_map(): channel_patch, amy_voices = midi.config.channel_info(channel) channel_polyphony = 0 if amy_voices is None else len(amy_voices) if (channel_patch, channel_polyphony) != (patch_no, polyphony): - tulip.music_map(channel, patch_number=patch_no, - voice_count=polyphony) + midi.config.add_synth(channel=channel, patch=patch_no, polyphony=polyphony) # populate the patches dialog from patches.py