From bc638dc117bfe3bc2d078290a558283ce23845a0 Mon Sep 17 00:00:00 2001 From: Brian Whitman Date: Thu, 12 Sep 2024 05:59:26 -0700 Subject: [PATCH 1/5] fixing up music.md for best practices on changing midi channel synths --- amy | 2 +- docs/music.md | 29 +++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/amy b/amy index 78ae7c81..8a56b26d 160000 --- a/amy +++ b/amy @@ -1 +1 @@ -Subproject commit 78ae7c81a27a76d1443a63941dd865a0131d79e0 +Subproject commit 8a56b26d019f3c1e49424df5fcfaec270187b1fe diff --git a/docs/music.md b/docs/music.md index 93196a2c..f48074e6 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=1, num_voices=2, patch=30) +``` + +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,10 +287,17 @@ 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 +```python # You can pass any AMY arguments to the setup of OscSynth, after you specify how many voices you want s = midi.OscSynth(wave=amy.PCM, patch=10) # PCM wave type, patch=10 From 2c365af647803eb65cdbcfa5da1799095c07c058 Mon Sep 17 00:00:00 2001 From: Brian Whitman Date: Thu, 12 Sep 2024 06:33:52 -0700 Subject: [PATCH 2/5] removing `music_map` --- amy | 2 +- docs/music.md | 2 +- docs/tulip_api.md | 8 +++----- tulip/shared/py/juno6.py | 1 - tulip/shared/py/midi.py | 29 ----------------------------- tulip/shared/py/tulip.py | 2 -- tulip/shared/py/voices.py | 3 +-- 7 files changed, 6 insertions(+), 41 deletions(-) diff --git a/amy b/amy index 8a56b26d..0089f71c 160000 --- a/amy +++ b/amy @@ -1 +1 @@ -Subproject commit 8a56b26d019f3c1e49424df5fcfaec270187b1fe +Subproject commit 0089f71cd638acf556f5c27ad3f52908ac82942a diff --git a/docs/music.md b/docs/music.md index f48074e6..e70308b6 100644 --- a/docs/music.md +++ b/docs/music.md @@ -168,7 +168,7 @@ You may want to programatically change the MIDI to synth mapping. One example wo You can change the parameters of channel synths like: ```python -midi.config.add_synth(channel=1, num_voices=2, patch=30) +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. diff --git a/docs/tulip_api.md b/docs/tulip_api.md index 8d8e2b65..a335cbd3 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(2,129,1)`. The `2`, channel, is a MIDI channel (we use 1-16 indexing), the patch `129` is an AMY patch number, `1` is the optional 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..c4b52ea1 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, patch_no, polyphony) # populate the patches dialog from patches.py From 5de8da62aa48295809acb8988a2694ab74721f68 Mon Sep 17 00:00:00 2001 From: Brian Whitman Date: Thu, 12 Sep 2024 06:45:53 -0700 Subject: [PATCH 3/5] kwarg in docs --- docs/tulip_api.md | 2 +- tulip/shared/py/voices.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tulip_api.md b/docs/tulip_api.md index a335cbd3..e071f44b 100644 --- a/docs/tulip_api.md +++ b/docs/tulip_api.md @@ -491,7 +491,7 @@ 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 `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(2,129,1)`. The `2`, channel, is a MIDI channel (we use 1-16 indexing), the patch `129` is an AMY patch number, `1` 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 optional number of voices (polyphony) you want to support for that channel and patch. (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.) diff --git a/tulip/shared/py/voices.py b/tulip/shared/py/voices.py index c4b52ea1..a7fc25f2 100644 --- a/tulip/shared/py/voices.py +++ b/tulip/shared/py/voices.py @@ -265,7 +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): - midi.config.add_synth(channel, patch_no, polyphony) + midi.config.add_synth(channel=channel, patch=patch_no, polyphony=polyphony) # populate the patches dialog from patches.py From efb351d353036821e43e886efe179d86c97231ef Mon Sep 17 00:00:00 2001 From: Brian Whitman Date: Thu, 12 Sep 2024 07:19:02 -0700 Subject: [PATCH 4/5] Update music.md --- docs/music.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/music.md b/docs/music.md index e70308b6..edbd7bec 100644 --- a/docs/music.md +++ b/docs/music.md @@ -298,7 +298,7 @@ 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 ```python -# You can pass any AMY arguments to the setup of OscSynth, after you specify how many voices you want +# 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) From 6840c67adb8014595466d905fc1db43ba3f4eb8a Mon Sep 17 00:00:00 2001 From: Brian Whitman Date: Thu, 12 Sep 2024 07:19:58 -0700 Subject: [PATCH 5/5] Update tulip_api.md --- docs/tulip_api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tulip_api.md b/docs/tulip_api.md index e071f44b..47f5f759 100644 --- a/docs/tulip_api.md +++ b/docs/tulip_api.md @@ -491,7 +491,7 @@ 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 `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 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 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.)