Skip to content

Commit

Permalink
Merge pull request #466 from shorepine/synthpy
Browse files Browse the repository at this point in the history
Separate synth.py from midi.py
  • Loading branch information
bwhitman authored Feb 4, 2025
2 parents 15309c9 + 4be2864 commit 1b0b1e9
Show file tree
Hide file tree
Showing 8 changed files with 466 additions and 456 deletions.
40 changes: 20 additions & 20 deletions docs/music.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,29 +120,29 @@ tulip.seq_bpm(120)

(Make sure to read below about the higher-accuracy sequencer API, `amy.send(sequence)`. The Tulip `seq_X` commands are simple and easy to use, but if you're making a music app that requires rock-solid timing, you'll want to use the AMY sequencer directly.)

## Making new Synths
## Making new synths

We're using `midi.config.get_synth(channel=1)` to "borrow" the synth booted with Tulip. But if you're going to want to share your ideas with others, you should make your own `Synth` that doesn't conflict with anything already running on Tulip. That's easy, you can just run:
We're using `midi.config.get_synth(channel=1)` to "borrow" the synth booted with Tulip. But if you're going to want to share your ideas with others, you should make your own `synth` that doesn't conflict with anything already running on Tulip. That's easy, you can just run:

```python
synth = midi.Synth(num_voices=2, patch_number=143) # two note polyphony, patch 143 is DX7 BASS 2
synth = synth.PatchSynth(num_voices=2, patch_number=143) # two note polyphony, patch 143 is DX7 BASS 2
```

And if you want to play multimbral tones, like a Juno-6 bass alongside a DX7 pad:

```python
synth1 = midi.Synth(num_voices=1, patch_number=0) # Juno
synth2 = midi.Synth(num_voices=1, patch_number=128) # DX7
synth1 = synth.PatchSynth(num_voices=1, patch_number=0) # Juno
synth2 = synth.PatchSynth(num_voices=1, patch_number=128) # DX7
synth1.note_on(50, 1)
synth2.note_on(50, 0.5)
```

You can also "schedule" notes. This is useful for sequencing fast parameter changes. `Synth`s accept a `time` parameter, and it's in milliseconds. For example:
You can also "schedule" notes. This is useful for sequencing fast parameter changes. `synth`s accept a `time` parameter, and it's in milliseconds. For example:

```python
# play a chord all at once
import music, midi, tulip
synth4 = midi.Synth(num_voices=4, patch_number=1)
synth4 = synth.PatchSynth(num_voices=4, patch_number=1)
chord = music.Chord("F:min7").midinotes()
for i,note in enumerate(chord):
synth4.note_on(note, 0.5, time=tulip.ticks_ms() + (i * 1000)) # time is i seconds from now
Expand All @@ -155,14 +155,14 @@ You can send `all_notes_off()` to your synth to stop playing notes:
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
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()
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.
As you learn more about AMY (the underlying synth engine) you may be interested in making your own `synth`s in Python. See `synth.py`'s `OscSynth` for an example.

## Modifying the default synth or other MIDI channel assignments in code

Expand All @@ -171,10 +171,10 @@ You may want to programatically change the MIDI to synth mapping. One example wo
You can change the parameters of channel synths like this:

```python
midi.config.add_synth(channel=c, synth=midi.Synth(patch_number=p, num_voices=n))
midi.config.add_synth(channel=c, synth=synth.PatchSynth(patch_number=p, num_voices=n))
```

Note that `add_synth` will stop any running Synth on that channel and boot a new one in its place.
Note that `add_synth` will stop any running synth on that channel and boot a new one in its place.

## The editor

Expand All @@ -194,7 +194,7 @@ Type `edit('jam.py')` (or whatever you want to call it.) You'll see a black scre
import tulip, midi, music, random

chord = music.Chord("F:min7").midinotes()
synth = midi.Synth(num_voices=1, patch_number=143) # DX7 BASS 2
synth = synth.PatchSynth(num_voices=1, patch_number=143) # DX7 BASS 2
slot = None

def note(t):
Expand Down Expand Up @@ -248,7 +248,7 @@ def run(screen):
app = screen
app.slot = None
app.chord = music.Chord("F:min7").midinotes()
app.synth = midi.Synth(num_voices=1, patch_number=143) # DX7 BASS 2
app.synth = synth.PatchSynth(num_voices=1, patch_number=143) # DX7 BASS 2
app.present()
app.quit_callback = stop
start(app)
Expand All @@ -264,7 +264,7 @@ def run(screen):
app = screen
app.slot = None
app.chord = music.Chord("F:min7").midinotes()
app.synth = midi.Synth(num_voices=1, patch_number=143) # DX7 BASS 2
app.synth = synth.PatchSynth(num_voices=1, patch_number=143) # DX7 BASS 2
bpm_slider = tulip.UISlider(tulip.seq_bpm()/2.4, w=300, h=25,
callback=bpm_change, bar_color=123, handle_color=23)
app.add(bpm_slider, x=300,y=200)
Expand All @@ -287,10 +287,10 @@ Now quit the `jam2` app if it was already running and re-`run` it. You should se

## Sampler, OscSynth

Tulip defines a few different `Synth` classes, including `OscSynth` which directly uses one oscillator per voice of polyphony, as in this simple sine wave synth:
Tulip defines a few different `synth` classes, including `OscSynth` which directly uses one oscillator per voice of polyphony, as in this simple sine wave synth:

```python
s = midi.OscSynth(wave=amy.SINE)
s = synth.OscSynth(wave=amy.SINE)
s.note_on(60,1)
s.note_off(60)
```
Expand All @@ -299,7 +299,7 @@ Let's try it as a sampler. There are 29 samples of drum-like and instrument soun

```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 (808 Cowbell)
s = synth.OscSynth(wave=amy.PCM, patch=10) # PCM wave type, patch=10 (808 Cowbell)

s.note_on(50, 1.0)
s.note_on(40, 1.0) # different pitch
Expand All @@ -316,15 +316,15 @@ You can load your own samples into Tulip. Take any .wav file and [load it onto T

```python
amy.load_sample('sample.wav', patch=50)
s = midi.OscSynth(wave=amy.PCM, patch=50)
s = synth.OscSynth(wave=amy.PCM, patch=50)
s.note_on(60, 1.0)
```

You can also load PCM patches with looped segments if you have their loopstart and loopend parameters (these are often stored in the WAVE metadata. If the .wav file has this metadata, we'll parse it. The example file `/sys/ex/vlng3.wav` has it. You can also provide the metadata directly.) To indicate looping, use `feedback=1`.

```python
amy.load_sample("/sys/ex/vlng3.wav", patch=50) # loads wave looping metadata
s = midi.OscSynth(wave=amy.CUSTOM, patch=50, feedback=1, num_voices=1)
s = synth.OscSynth(wave=amy.CUSTOM, patch=50, feedback=1, num_voices=1)
s.note_on(60, 1.0) # loops
s.note_on(55, 1.0) # loops
s.note_off(55) # stops
Expand Down Expand Up @@ -474,7 +474,7 @@ amy.stop_store_patch(1024)
Now, you're free to use this patch number like all the Juno and DX7 ones. For a polyphonic wood piano, do:

```python
s = midi.Synth(5)
s = synth.PatchSynth(5)
s.program_change(1024)
s.note_on(50, 1)
s.note_on(50, 1)
Expand Down
13 changes: 6 additions & 7 deletions tulip/fs/ex/xanadu.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import time

import tulip
import midi
import synth
import amy

try:
Expand Down Expand Up @@ -115,10 +115,10 @@ def broken_chord(base_pitch, intervals, start_time, **kwargs):
def xanadu_stage1(x):
global synths
amy.chorus(1)
synths.append(midi.Synth(num_voices=6, patch_string=note1_patch()))
synths.append(midi.Synth(num_voices=6, patch_string=note1_patch(pan=0.8)))
synths.append(midi.Synth(num_voices=6, patch_string=note2_patch(pan=0.2)))
synths.append(midi.Synth(num_voices=18, patch_string=fm_note_patch()))
synths.append(synth.PatchSynth(num_voices=6, patch_string=note1_patch()))
synths.append(synth.PatchSynth(num_voices=6, patch_string=note1_patch(pan=0.8)))
synths.append(synth.PatchSynth(num_voices=6, patch_string=note2_patch(pan=0.2)))
synths.append(synth.PatchSynth(num_voices=18, patch_string=fm_note_patch()))
for s in synths:
s.deferred_init()

Expand Down Expand Up @@ -153,8 +153,7 @@ def xanadu_stage2(x):
tulip.defer(xanadu_stage3, None, 60000)

def xanadu_stage3(x):
midi.Synth.reset()
synth.PatchSynth.reset()


midi.Synth.reset()
tulip.defer(xanadu_stage1, None, 250)
2 changes: 1 addition & 1 deletion tulip/shared/py/_boot.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import gc
import uos
import tulip, sys, midi, amy, alles
import tulip, sys, midi, synth, amy, world, alles
from upysh import *
from tulip import edit, run, board
if board()=="WEB":
Expand Down
Loading

0 comments on commit 1b0b1e9

Please sign in to comment.