AMY Synthesizer Tutorial

AMY Interactive Tutorial

This page will let you try out different AMY commands in live Python, running all in your browser. You can edit any of the examples and run them live.

If you're using C, Arduino or JS to control AMY, check out our API conversion table to go between Python amy.send and amy_event.

You can also run this tutorial on your computer within a Python shell by installing amy and running import amy; amy.live().

You can also try AMY in a more full featured REPL with Tulip Web.

AMY synth mode

AMY can receive MIDI messages while it is running and play just like a normal hardware synthesizer. You can send it note ons, offs, pitch bend, sustain, program changes and control change messages. Let's set up a synth to respond to MIDI channel 1. Make sure your MIDI devices are connected above at the top of this page (note: MIDI does not work on Safari, so try Chrome or Firefox.)

amy.send(patch=4, synth=1, num_voices=6)

Run that code block and play some notes on your MIDI keyboard. Try changing the patch and running again. The synth parameter tells AMY which synthesizer we are allocating (and if you give a synth number between 1 and 16, will respond to that MIDI channel), and num_voices tells AMY how much polyphony to allow per synthesizer. Keep track of num_voices is important for microcontrollers where you have limited CPU cycles. The patch can be any of AMY's built in Juno-6 (0-127), DX-7 (128-255), or partials (256) patches. (Later, we'll show you how to construct your own patches.)

No MIDI? That's ok, you can send the note on command to AMY directly:

amy.send(patch=4, synth=1, num_voices=6) amy.send(synth=1, vel=1, note=50)

Here you see we gave a synth number, and then vel for note on velocity and a MIDI note number of 50. vel in AMY is a signal to play a note on. If that note above is still playing, try `vel=0` to turn it off:

amy.send(synth=1, note=50, vel=0)

You can adjust parameters of an entire synth, like moving a knob, by addressing it. Here we'll change the filter cutoff frequency of the Juno-6 patch we loaded. Trying changing the values!

amy.send(patch=0, synth=1, num_voices=6) amy.send(synth=1, vel=1, note=50) amy.send(synth=1, filter_freq=[800], resonance=2.5)

AMY oscillators

Let's set a simple sine wave. We are simply telling oscillator 0 to be a sine wave at 220Hz and amplitude (specified as a note-on velocity) of 1. You can also try `amy.PULSE`, or `amy.SAW_DOWN`, etc.

amy.send(osc=0, wave=amy.SINE, freq=220, vel=1)

Now let's make more sine waves! We'll try using time to schedule events in the future. When you hit the play button on this page, AMY resets to time (in milliseconds) 0. In real use of AMY, you want to use amy_sysclock() to know what time it currently is. In this example we'll add a new sine wave every half second:

for i in range(16): amy.send(osc=i, wave=amy.SINE, freq=110+(i*80), vel=((16-i)/32.0), time=i*500)

A classic analog tone is the filtered saw wave. Let's make one.

amy.send(osc=0, wave=amy.SAW_DOWN, filter_freq=400, resonance=5, filter_type=amy.FILTER_LPF) amy.send(osc=0, vel=1, note=40)

Sounds nice. But we want that filter freq to go down over time, to make that classic filter sweep tone. Let's use an Envelope Generator! An Envelope Generator (EG) creates a smooth time envelope based on a breakpoint set, which is a simple list of (time-delta, target-value) pairs - you can have up to 8 of these per EG, and 2 different EGs to control different things. They're just like ADSRs, but more powerful. You can use an EG to control amplitude, oscillator frequency, filter cutoff frequency, PWM duty cycle, or stereo pan. The EG gets triggered when the note begins. So let's make an EG that turns the filter frequency down from its start at 3200 Hz to 400 Hz over 1000 milliseconds. And when the note goes off, it tapers the frequency to 50 Hz over 200 milliseconds.

amy.send(osc=0, wave=amy.SAW_DOWN, resonance=5, filter_type=amy.FILTER_LPF) amy.send(osc=0, filter_freq='50,0,0,0,1', bp1='0,6.0,1000,3.0,200,0') amy.send(osc=0, vel=1, note=40)

There are two things to note here: Firstly, the envelope is defined by the set of breakpoints in `bp1` (defining the second EG; the first is controlled by `bp0`). The `bp` strings alternate time intervals in milliseconds with target values. So `0,6.0,1000,3.0,200,0` means to move to 6.0 after 0 ms (i.e., the initial value is 6), then to decay to 3.0 over the next 1000 ms (1 second). The final pair is always taken as the "release", and does not begin until the note-off event is received. In this case, the EG decays to 0 in the 200 ms after the note-off.

Secondly, EG1 is coupled to the filter frequency with `filter_freq='50,0,0,0,1'`. `filter_freq` is an example of a set of **ControlCoefficients** where the control value is calculated on-the-fly by combining a set of inputs scaled by the coefficients. This is explained fully below, but for now the first coefficient (here 50) is taken as a constant, and the 5th coefficient (here 1) is applied to the output of EG1. To get good "musical" behavior, the filter frequency is controlled using a "unit per octave" rule. So if the envelope is zero, the filter is at its base frequency of 50 Hz. But the envelope starts at 6.0, which, after scaling by the control coefficient of 1, drives the filter frequency 6 octaves higher, or 2^6 = 64x the frequency -- 3200 Hz. As the envelope decays to 3.0 over the first 1000 ms, the filter moves to 2^3 = 8x the default frequency, giving 400 Hz. It's only during the final release of 200 ms that it falls back to 0, giving a final filter frequency of (2^0 = 1x) 50 Hz.

AMY sequencer

AMY is always running a musical sequencer. You can use it to schedule events using note lengths, or have a repeating (pattern) sequencer. Very useful for things like drum machines or MIDI playback.

amy.send(osc=0, vel=1, wave=amy.PCM, preset=0, sequence=",24,1") # play a PCM drum every eighth note. amy.send(osc=1, vel=1, wave=amy.PCM, preset=1, sequence=",48,2") # play a different PCM drum every quarter note.

You can remove or update sequence events by addressing their tag number

amy.send(sequence=",,1") # remove the eighth note sequence amy.send(osc=1, vel=1, wave=amy.PCM, preset=1, note=70, sequence=",48,2") # change the quarter note event

For patterns you want to also address their "slots", which is the offset within the pattern, like this

amy.send(osc=0, vel=1, wave=amy.PCM, preset=0, sequence="0,384,1") # first slot of a 16 1/8th note drum machine amy.send(osc=1, vel=1, wave=amy.PCM, preset=1, sequence="216,384,2") # ninth slot of a 16 1/8th note drum machine

Constructing FM tones

TODO

amy.send(osc=2, wave=amy.SINE, ratio=1, amp={'const': 1, 'vel': 0, 'eg0': 0}) amy.send(osc=1, wave=amy.SINE, ratio=0.2, amp={'const': 1, 'vel': 0, 'eg0': 1}, bp0='0,1,1000,0,0,0') amy.send(osc=0, wave=amy.ALGO, algorithm=1, algo_source=',,,,2,1') amy.send(osc=0, note=60, vel=1)

Let's unpack that last line: we're setting up a ALGO "oscillator" that controls up to 6 other oscillators. We only need two, so we set the `algo_source` to mostly not used and have oscillator 2 modulate oscillator 1. You can have the operators work with each other in all sorts of crazy ways. For this simple example, we just use the DX7 algorithm #1. And we'll use only operators 2 and 1. Therefore our `algo_source` lists the oscillators involved, counting backwards from 6. We're saying only have operator 2 (osc 2 in this case) and operator 1 (osc 1). From the picture, we see DX7 algorithm 1 has operator 2 feeding operator 1, so we have osc 2 providing the frequency-modulation input to osc 1.

What's going on with `ratio`? And `amp`? Ratio, for FM synthesis operators, means the ratio of the frequency for that operator relative to the base note. So oscillator 1 will be played at 20% of the base note frequency, and oscillator 2 will take the frequency of the base note. In FM synthesis, the `amp` of a modulator input is called "beta", which describes the strength of the modulation. Here, osc 2 is providing the modulation with a constant beta of 1, which will result in a range of sinusoids with frequencies around the carrier at multiples of the modulator. We set osc 2's amp ControlCoefficients for velocity and envelope generator 0 to 0 because they default to 1, but we don't want them for this example (FM sines don't receive the parent note's velocity, so we need to disable its influence). Osc 1 has `bp0` decaying its amplitude to 0 over 1000 ms, but because beta is fixed there's no other change to the sound over that time.

FM gets much more exciting when we vary beta, which just means varying the amplitide envelope of the modulator. The spectral effects of the frequency modulation depend on beta in a rich, nonlinear way, leading to the glistening FM sounds. Let's try fading in the modulator over 5 seconds. Just a refresher on envelope generators; here we are saying to set the beta parameter (amplitude of the modulating tone) to 2x envelope generator 0's output, which starts at 0 at time 0 (actually, this is the default), then grows to 1.0 at time 5000ms - so beta grows to 2.0. At the release of the note, beta immediately drops back to 0.

amy.send(osc=2, wave=amy.SINE, ratio=0.2, amp={'const': 1, 'vel': 0, 'eg0': 2}, bp0='0,0,5000,1,0,0') # Op 2, modulator amy.send(osc=1, wave=amy.SINE, ratio=1, amp={'const': 1, 'vel': 0, 'eg0': 0}) # Op 1, carrier amy.send(osc=0, wave=amy.ALGO, algorithm=1, algo_source=',,,,2,1') amy.send(osc=0, note=60, vel=1)

Want more? Try Tulip

Run more AMY experiments in a REPL with Tulip for the Web. Try the piano there!

Discord

Join the shore pine sound systems Discord to chat about Tulip, AMY and Alles. A fun small community!

Github

Check out the AMY Github page for issues, discussions and the code.

Join our email list

We'll send you very rare updates about Tulip, Alles, AMY and other projects we're working on.

<