Skip to content

Commit

Permalink
Merge pull request #379 from shorepine/musicmd2
Browse files Browse the repository at this point in the history
fixing up music.md for best practices on changing midi channel synths
  • Loading branch information
bwhitman authored Sep 12, 2024
2 parents 71b9e5d + 6840c67 commit c7bce79
Show file tree
Hide file tree
Showing 6 changed files with 32 additions and 42 deletions.
31 changes: 28 additions & 3 deletions docs/music.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
8 changes: 3 additions & 5 deletions docs/tulip_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()`.

Expand All @@ -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]))
Expand Down
1 change: 0 additions & 1 deletion tulip/shared/py/juno6.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
29 changes: 0 additions & 29 deletions tulip/shared/py/midi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 0 additions & 2 deletions tulip/shared/py/tulip.py
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 1 addition & 2 deletions tulip/shared/py/voices.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit c7bce79

Please sign in to comment.