AMY synthesizer API

April 22, 2026 · View on GitHub

This page collects the current API for AMY.

Please see our interactive AMY tutorial for more tips on using AMY

C / Arduino API

Parsing, creating and adding events to AMY:

// returns default empty amy_event
amy_event amy_default_event();

// clears an existing AMY event
amy_clear_event(amy_event *e);

// given an event play / schedule the event directly
amy_add_event(amy_event *e);

// given a wire message string play / schedule the event directly
amy_add_message(char *message);

Get and set external audio buffers:


// get AUDIO_IN0 and AUDIO_IN1
amy_get_input_buffer(output_sample_type * samples);

// set AUDIO_EXT0 and AUDIO_EXT1
amy_set_external_input_buffer(output_sample_type * samples);

If not running live, render a new block of AMY audio into a int16_t buffer:

output_sample_type * amy_simple_fill_buffer();

Get the AMY time:

// on all platforms, sysclock is based on total samples played, using audio out (i2s or etc) as system clock
uint32_t amy_sysclock();

Start and stop AMY:

// Emscripten web start
amy_start_web();
amy_start_web_no_synths();

// Start AMY with a config.  If c.audio is set, will attempt to start live audio
amy_start(amy_config_t c);

// Stop AMY including any live audio output
amy_stop();

Default MIDI handlers:

void amy_enable_juno_filter_midi_handler(); // assigns the Juno-6 MIDI CC handler

JavaScript API

AMY provides a high-level JavaScript API (amy_send) that mirrors the Python amy.send() interface. It is auto-generated from the same source of truth (amy/__init__.py and amy/constants.py) so parameter names are always identical to Python. The connector and API are bundled into amy.js, so you only need two includes:

<script src="enable-threads.js"></script>
<script src="amy.js"></script>

amy_send(params)

Build and send an AMY wire message from a JS object. Parameter names and types are the same as Python amy.send().

// Play a sine wave at 440 Hz
amy_send({osc: 0, wave: AMY.SINE, freq: 440, vel: 1})

// Load a Juno-6 patch
amy_send({patch: 0, synth: 1, num_voices: 6})
amy_send({synth: 1, vel: 1, note: 60})

// FM synthesis with CtrlCoef dict
amy_send({osc: 0, amp: {const: 1, vel: 0, eg0: 2}, bp0: '0,0,5000,1,0,0'})

amy_message(params)

Like amy_send but returns the wire string without sending it. Useful for debugging or building messages to send later.

amy_message({osc: 0, wave: AMY.SINE, freq: 440})  // => "v0w0f440Z"

AMY constants

All constants from amy/constants.py are available on the global AMY object:

AMY.SINE         // 0
AMY.SAW_DOWN     // 2
AMY.PULSE        // 1
AMY.FILTER_LPF   // 1
AMY.ALGO         // 8
// ... all constants from amy/constants.py

Regenerating

The JS API is generated during make web and bundled into docs/amy.js by make deploy-web. To regenerate after changing amy/__init__.py or amy/constants.py:

make deploy-web

Try it live

AMY JavaScript REPL — write and run amy_send() commands in your browser.

amy_config_t

Use like:

amy_config_t amy_config = amy_default_config()
amy_config.max_sequencer_tags = 128;
amy_start(amy_config);
FieldValuesDefaultNotes
features.chorus0=off, 1=onOnIf chorus is enabled (uses RAM)
features.reverb0=off, 1=onOnIf reverb is enabled (uses RAM)
features.echo0=off, 1=onOnIf echo is enabled (uses RAM)
features.partials0=off, 1=onOnIf partials are enabled
features.custom0=off, 1=onOnIf custom C oscillators are enabled
features.audio_in0=off, 1=onOffIf audio_in gets processed via the audio interface. Must be 1 for AUDIO_IS_MINIAUDIO
features.default_synths0=off, 1=onOffIf AMY boots with Juno-6 on synth 1 and GM drums on synth 10
features.startup_bleep0=off, 1=onOffIf AMY plays a startup sound on boot
platform.multicore0=off, 1=onOnAttempts to use 2nd core if available
platform.multithread0=off, 1=onOnAttempts to multithreading if available (ESP/RTOS)
midiAMY_MIDI_IS_NONE, AMY_MIDI_IS_UART, AMY_MIDI_IS_USB_GADGET, AMY_MIDI_IS_WEBMIDIAMY_MIDI_IS_NONEWhich MIDI interface(s) are active
audioAMY_AUDIO_IS_NONE, AMY_AUDIO_IS_I2S, AMY_AUDIO_IS_USB_GADGET, AMY_AUDIO_IS_MINIAUDIOI2S or miniaudioWhich audio interface(s) are active
write_samples_fnfn ptrNULLIf provided, amy_update will call this with each new block of samples
max_oscsInt180How many oscillators to support
max_sequencer_tagsInt256How many sequencer items to handle
max_voicesInt64How many voices
max_synthsInt64How many synths
max_memory_patchesInt32How many in memory patches to supprot
i2s_lrc, i2s_dout, i2s_din, i2s_bclk, i2s_mclkInt-1Pin numbers for the I2S interface
midi_out, midi_inInt-1Pin number for the MIDI UART pins
midi_uart0,1,[2]-1UART device index for MCU. Default 1 (UART1) on Pi Pico and ESP. Teensy is always 8
capture_device_id, playback_device_idInt-1Which miniaudio device to use, -1 is auto

Hooks

Hooks are configured on amy_config_t before calling amy_start:

amy_config_t amy_config = amy_default_config();
amy_config.amy_external_midi_input_hook = my_midi_hook;
amy_config.amy_external_render_hook = my_render_hook;
amy_start(amy_config);

Hook fields in amy_config_t:

HookSignatureUsed byDescription
amy_external_render_hookuint8_t (uint16_t osc, SAMPLE *buf, uint16_t len)Custom oscillator renderer for redirecting the output waveforms on an osc-by-osc level. Return 1 if handled, in which case that osc does not contribute to the normal output.
amy_external_coef_hookfloat (uint16_t channel)Provide external coefficient values (e.g. CV input).
amy_external_block_done_hookvoid (void)Called after each audio block is rendered.
amy_external_midi_input_hookvoid (uint8_t *bytes, uint16_t len, uint8_t is_sysex)Called when MIDI bytes are received.
amy_external_sequencer_hookvoid (uint32_t tick_count)Called on each sequencer tick.
amy_external_fopen_hookuint32_t (char *filename, const char *mode)zT, zD, zFOpen a file on host disk. Returns opaque handle.
amy_external_fwrite_hookuint32_t (uint32_t fptr, uint8_t *bytes, uint32_t len)zTWrite bytes to a file opened via fopen hook.
amy_external_fread_hookuint32_t (uint32_t fptr, uint8_t *bytes, uint32_t len)zDRead bytes from a file opened via fopen hook.
amy_external_fseek_hookvoid (uint32_t fptr, uint32_t pos)zDSeek to position in a file opened via fopen hook.
amy_external_fclose_hookvoid (uint32_t fptr)zT, zD, zFClose a file opened via fopen hook.
amy_external_file_transfer_done_hookvoid (const char *filename)zTCalled after a zT file transfer completes. On AMYboard, restarts sketch.py.
amy_external_update_file_hookvoid (const char *filename)zACalled by zA to update a file with current AMY state. On AMYboard, splices live knob state into sketch.py.
amy_external_exec_hookvoid (const char *code)zPCalled by zP to execute a string on the host. On AMYboard, runs the string as Python via exec().
amy_external_reboot_hookvoid (uint8_t mode)zBCalled by zB to reboot the host. mode selects which post-reboot state: 0 = bootloader (skip sketch on next boot), 1 = normal reboot (run sketch), 2 = ROM download / flash mode. Handled in pure C before mp_sched_schedule. On AMYboard, sets an RTC flag with the requested mode and calls esp_restart().

All hook fields default to NULL in amy_default_config().

amy_event, amy.send, and amy_send API:

AMY parameters can be set via three interfaces:

  • C: Set fields on amy_event structs
  • Python: amy.send(param=value, ...)
  • JavaScript: amy_send({param: value, ...})

Python and JavaScript use identical parameter names (shown in the Python / JS column below). A few parameters are not yet available via C amy_event (marked TODO).

Please see AMY synthesizer details for more explanation on the synthesizer parameters.

Please see our interactive AMY tutorial for more tips on using AMY

A note on list parameters: When an argument is a list of parameters, you can in general set any subset of those parameters by omitting the values you don't want to change - either by leaving them in their initial AMY_UNSET value in C, or by having missing values in Python lists. For instance, you can set up an envelope that moves immediately to 1, then decase to a sutain level of 0.5 over 200ms, then has a 300ms decay to zero on note-off, with bp0='0,1,200,0.5,300,0'. Subsequently, you could change just the sustain level (the 4th value in the list) to 0.2 with bp0=,,,0.2`. However, there's at present no way to say ".. and the list should now only be 4 items long. This only affects breakpoint sets which are variable length, but the net result is that once you have a certain number of breakpoints in a list, you cannot shorten it except by resetting the whole osc and building it all up again.

synths and voices:

Wire codeC amy_eventPython / JSType-rangeNotes
isynthsynth0-31Define a set of voices for voice management.
icTODOmidi_ccC,L,N,X,O,CMDMIDI Control Code command for this synth (1-16). C=MIDI CC (0-127), L=log mapping (0/1), N=min val, X=max val, O=offset, CMD=wire command to execute, where %i is replaced by the channel number and %v is replaced by the value after min/max/offset/log mapping. Providing C with no further args deletes that CC. C=255 deletes all CC mappings for the specified synth. See #524
ifsynth_flagssynth_flagsuintFlags for synth creation: 1 = Use MIDI drum note->preset translation; 2 = Drop note-off events.
idsynth_delay_mssynth_delayuintDelay (in ms) applied to synth note-ons. Gives time for decay of 'stolen' notes.
itto_synthto_synth0-31New synth number, when changing the number (MIDI channel for n=1..16) of an entire synth.
ivnum_voicesnum_voicesintThe number of voices to allocate when defining a synth, alternative to directly specifying voice numbers with voices=. Only valid with synth=X, patch[_number]=Y.
inoscs_per_voiceoscs_per_voice>0Reserve this many oscs for each voice. Needed when initializing a synth (or voice) withouth an initial patch. Setting oscs_per_voice on an existing synth resets all oscs to their default state.
imgrab_midi_notesgrab_midi_notes0/1Use amy.send(synth=CHANNEL, grab_midi_notes=0) to prevent the default direct forwarding of MIDI note-on/offs to synth CHANNEL.
ippedalpedalintNon-zero means pedal is down (i.e., sustain). Must be used with synth.
Kpatch_numberpatchuint 0-XApply a saved or user patch to a specified synth or voice.
rvoices[]voicesint[,int]Comma separated list of voices to send message to, or load patch into.
uTODOpatch_stringstringProvide AMY message to define up to 32 patches in RAM with ID numbers (1024-1055) provided via patch_number, or directly configure a synth.

Oscillator control

Wire codeC amy_eventPython / JSType-rangeNotes
voscoscuint 0 to OSCS-1Which oscillator to control
wwavewaveuint 0-21Waveform: [0=SINE, PULSE, SAW_DOWN, SAW_UP, TRIANGLE, NOISE, KS, PCM, ALGO, PARTIAL, BYO_PARTIALS, INTERP_PARTIALS, AUDIO_IN0, AUDIO_IN1, AUDIO_EXT0, AUDIO_EXT1, AMY_MIDI, PCM_LEFT, PCM_RIGHT, WAVETABLE, CUSTOM, OFF]. default: 0/SINE
Sreset_oscresetuintResets given oscillator. set to RESET_ALL_OSCS to reset all oscillators, gain and EQ. RESET_TIMEBASE resets the clock (immediately, ignoring time). RESET_AMY restarts AMY. RESET_SEQUENCER clears the sequencer.
Aeg0_times[], eg0_values[]bp0string (wire) / arrays (amy_event)Envelope Generator 0 breakpoints as time(ms),value pairs. Wire/Python format remains comma-separated, e.g. 100,0.5,50,0.25,200,0. In C amy_event, use typed arrays (eg0_times[i], eg0_values[i]). The last pair is release (triggers on note off).
Beg1_times[], eg1_values[]bp1string (wire) / arrays (amy_event)Envelope Generator 1 breakpoints. Wire/Python format remains comma-separated; in C amy_event, use typed arrays (eg1_times[i], eg1_values[i]).
bfeedbackfeedbackfloat 0-1Use for the ALGO synthesis type in FM or for karplus-strong, or to indicate PCM looping (0 off, >0, on)
cchained_oscchained_oscuint 0 to OSCS-1Chained oscillator. Note/velocity events to this oscillator will propagate to chained oscillators. VCF is run only for first osc in chain, but applies to all oscs in chain.
Gfilter_typefilter_type0-4Filter type: 0 = none (default.) 1 = lowpass, 2 = bandpass, 3 = highpass, 4 = double-order lowpass.
IratioratiofloatFor ALGO types, ratio of modulator frequency to base note frequency
Lmod_sourcemod_source0 to OSCS-1Which oscillator is used as an modulation/LFO source for this oscillator. Source oscillator will be silent.
mportamentoportamentouintTime constant (in ms) for pitch changes when note is changed without intervening note-off. default 0 (immediate), 100 is good.
nmidi_notenotefloat, but typ. uint 0-127Midi note, sets frequency. Fractional Midi notes are allowed.
oalgorithmalgorithmuint 1-32DX7 FM algorithm to use for ALGO type
Oalgo_source[]algo_sourcestringWhich oscillators to use for the FM algorithm. list of six (starting with op 6), use empty for not used, e.g 0,1,2 or 0,1,2,,,
ppresetpresetintWhich predefined PCM or wavetable preset patch to use, or number of partials if < 0. For wave=WAVETABLE, use the wavetable presets appended to PCM. (Juno/DX7 patches are different - see patch_number).
ppresetnum_partialsintAlias for preset. Must be used with wave=BYO_PARTIALS. Cannot be combined with preset in the same message.
Pphasephasefloat 0-1Where in the oscillator's cycle to begin the waveform (also works on the PCM buffer). default 0
RresonanceresonancefloatQ factor of variable filter, 0.5-16.0. default 0.7
Teg_type[0]eg0_typeuint 0-3Type for Envelope Generator 0 - 0: Normal (RC-like) / 1: Linear / 2: DX7-style / 3: True exponential.
Xeg_type[1]eg1_typeuint 0-3Type for Envelope Generator 1 - 0: Normal (RC-like) / 1: Linear / 2: DX7-style / 3: True exponential.
lvelocityvelfloatNote on velocity. Use to start an envelope or set amplitude

CtrlCoefs

These per-oscillator parameters use CtrlCoefs notation

Wire codeC amy_eventPython / JSType-rangeNotes
Qpan_coefs[]panfloat[,float...]Panning index ControlCoefficients (for stereo output), 0.0=left, 1.0=right. default 0.5.
aamp_coefs[]ampfloat[,float...]Control the amplitude of a note; a set of ControlCoefficients. Default is 0,0,1,1 (i.e. the amplitude comes from the note velocity multiplied by by Envelope Generator 0.)
dduty_coefs[]dutyfloat[,float...]Duty cycle for pulse wave, ControlCoefficients, defaults to 0.5
ffreq_coefs[]freqfloat[,float...]Frequency of oscillator, set of ControlCoefficients. Default is 0,1,0,0,0,0,1 (from note pitch plus pitch_bend)
Ffilter_freq_coefs[]filter_freqfloat[,float...]Center/break frequency for variable filter, set of ControlCoefficients

PCM sampling

Wire codeC amy_eventPython / JSType-rangeNotes
zTODOload_sampleuint x 6Signal to start loading sample. preset number, length(frames), samplerate, channels, midinote, loopstart, loopend. All subsequent messages are base64 encoded WAVE-style frames of audio until length is reached. Set preset and length=0 to unload a sample from RAM.
zFTODOdisk_sampleuint,string,uintSet a PCM preset to play live from a WAV filename on AMY host disk. Params: preset number, filename, midinote. See hooks for reading files on host disk. Only one file sample can be played at once per preset number. Use multiple presets if you want polyphony from a single sample.
zSTODOstart_sampleuint x 6Start sampling to a stereo PCM preset from bus. Params: preset number, bus, max length in frames, midinote, loopstart, loopend. bus = 1 is AMY mixed output. bus = 2 is AUDIO_IN0 + 1. Will sample until max length is reached, stop_sample is issued, or a new start_sample is issued.
zOTODOstop_sampleuintStop sampling. Does nothing if no sampling active. param ignored.

WAVETABLE wave type

wave=WAVETABLE is available when AMY is built with -DAMY_WAVETABLE.

  • Wavetable samples are baked into pcm_tiny and exposed as contiguous PCM presets.
  • The preset range is dynamic at runtime:
    • start: pcm_wavetable_base
    • count: pcm_wavetable_samples
    • valid presets: pcm_wavetable_base ... pcm_wavetable_base + pcm_wavetable_samples - 1
  • Each wavetable preset is expected to be one 64-cycle table (normally 16384 samples total, 256 samples per cycle).
  • duty crossfades across the 64 cycles inside the selected wavetable preset.

Other

Wire codeC amy_eventPython / JSType-rangeNotes
Hsequence[3]sequenceint,int,intTick offset, period, tag for sequencing
hreverb_level, reverb_liveness, reverb_damping, reverb_xover_hzreverbfloat[,float,float,float]Reverb parameters -- level, liveness, damping, xover: Level is for output mix; liveness controls decay time, 1 = longest, default 0.85; damping is extra decay of high frequencies, default 0.5; xover is damping crossover frequency, default 3000 Hz.
jtempotempofloatThe tempo (BPM, quarter notes) of the sequencer. Defaults to 108.0.
kchorus_level, chorus_max_delay, chorus_lfo_freq, chorus_depthchorusfloat[,float,float,float]Chorus parameters -- level, delay, freq, depth: Level is for output mix (0 to turn off); delay is max in samples (320); freq is LFO rate in Hz (0.5); depth is proportion of max delay (0.5).
Mecho_level, echo_delay_ms, echo_max_delay_ms, echo_feedback, echo_filter_coefechofloat[,int,int,float,float]Echo parameters -- level, delay_ms, max_delay_ms, feedback, filter_coef (-1 is HPF, 0 is flat, +1 is LPF).
Nlatency_mslatency_msuintSets latency in ms. default 0 (see LATENCY)
spitch_bendpitch_bendfloatSets the global pitch bend, by default modifying all note frequencies by (fractional) octaves up or down
ttimetimeuintRequest playback time relative to some fixed start point on your host, in ms. Allows precise future scheduling.
Vvolumevolumefloat 0-10Volume knob for entire synth, default 1.0
xeq_l, eq_m, eq_heqfloat,float,floatEqualization in dB low (~800Hz) / med (~2500Hz) / high (~7500Gz) -15 to 15. 0 is off. default 0.
gclientclientuintClient number for Alles distributed synthesis.
Wexternal_channelexternal_channeluintExternal channel routing (used by Tulip for CV output).
DTODOdebuguint, 2-42 shows queue sample, 3 shows oscillator data, 4 shows modified oscillator. Will interrupt audio!
zTTODOtransfer_filestring,uintTransfer a file to the host. Params: destination filename, file size. See hooks for writing files on host disk.
zATODOupdate_filestring (optional)Update a file on disk with current AMY state via update_file_hook. Default path: /user/current/sketch.py. On AMYboard, splices _auto_generated_knobs section with live state. The filename is "rest of message": a trailing Z end-of-message marker is stripped, so interior capital-Z characters (e.g. /user/ZFILE.py) are preserved. Filenames whose last character is Z are not addressable.
zDTODOdump_sysexstring (optional)Dump data over MIDI sysex (base64-encoded, wrapped with SPSS manufacturer ID 00 03 45). With no params (zDZ): dumps all active instrument state. With a filename (zD/user/current/sketch.pyZ): reads file and sends it. The filename is "rest of message": a trailing Z end-of-message marker is stripped, so interior capital-Z characters (e.g. /user/ZFILE.py) are preserved. Filenames whose last character is Z are not addressable.
zPTODOexecstringExecute code on the host via amy_external_exec_hook. On AMYboard, runs the string as Python (e.g. zPimport amyboard; amyboard.restart_sketch()). Max 255 chars. The code string is "rest of message": a trailing Z end-of-message marker is stripped, so interior capital-Z characters in the code are preserved. Code whose last character is Z is not expressible.
zITODOping(none)Ping the host. Replies with a short sysex frame F0 00 03 45 'O' 'K' F7 so the caller can confirm the board is alive and sysex is flowing. Handled entirely in pure C (no scheduler needed).
zBTODOrebootuint (optional)Reboot the host via amy_external_reboot_hook. Optional mode argument selects the post-reboot state: zBZ / zB0Z = bootloader mode (skip sketch on next boot), zB1Z = normal reboot (run sketch), zB2Z = ROM download / flash mode. Handled in pure C (no scheduler needed). On AMYboard, sets an RTC flag with the mode and calls esp_restart().