Skip to content

Latest commit

 

History

History
309 lines (236 loc) · 23.2 KB

File metadata and controls

309 lines (236 loc) · 23.2 KB

AMY synthesizer API

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);
Field Values Default Notes
features.chorus 0=off, 1=on On If chorus is enabled (uses RAM)
features.reverb 0=off, 1=on On If reverb is enabled (uses RAM)
features.echo 0=off, 1=on On If echo is enabled (uses RAM)
features.partials 0=off, 1=on On If partials are enabled
features.custom 0=off, 1=on On If custom C oscillators are enabled
features.audio_in 0=off, 1=on Off If audio_in gets processed via the audio interface. Must be 1 for AUDIO_IS_MINIAUDIO
features.default_synths 0=off, 1=on Off If AMY boots with Juno-6 on synth 1 and GM drums on synth 10
features.startup_bleep 0=off, 1=on Off If AMY plays a startup sound on boot
platform.multicore 0=off, 1=on On Attempts to use 2nd core if available
platform.multithread 0=off, 1=on On Attempts to multithreading if available (ESP/RTOS)
midi AMY_MIDI_IS_NONE, AMY_MIDI_IS_UART, AMY_MIDI_IS_USB_GADGET, AMY_MIDI_IS_WEBMIDI AMY_MIDI_IS_NONE Which MIDI interface(s) are active
audio AMY_AUDIO_IS_NONE, AMY_AUDIO_IS_I2S, AMY_AUDIO_IS_USB_GADGET, AMY_AUDIO_IS_MINIAUDIO I2S or miniaudio Which audio interface(s) are active
write_samples_fn fn ptr NULL If provided, amy_update will call this with each new block of samples
max_oscs Int 180 How many oscillators to support
max_sequencer_tags Int 256 How many sequencer items to handle
max_voices Int 64 How many voices
max_synths Int 64 How many synths
max_memory_patches Int 32 How many in memory patches to supprot
i2s_lrc, i2s_dout, i2s_din, i2s_bclk, i2s_mclk Int -1 Pin numbers for the I2S interface
midi_out, midi_in Int -1 Pin number for the MIDI UART pins
midi_uart 0,1,[2] -1 UART device index for MCU. Default 1 (UART1) on Pi Pico and ESP. Teensy is always 8
capture_device_id, playback_device_id Int -1 Which 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:

Hook Signature Used by Description
amy_external_render_hook uint8_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_hook float (uint16_t channel) Provide external coefficient values (e.g. CV input).
amy_external_block_done_hook void (void) Called after each audio block is rendered.
amy_external_midi_input_hook void (uint8_t *bytes, uint16_t len, uint8_t is_sysex) Called when MIDI bytes are received.
amy_external_sequencer_hook void (uint32_t tick_count) Called on each sequencer tick.
amy_external_fopen_hook uint32_t (char *filename, const char *mode) zT, zD, zF Open a file on host disk. Returns opaque handle.
amy_external_fwrite_hook uint32_t (uint32_t fptr, uint8_t *bytes, uint32_t len) zT Write bytes to a file opened via fopen hook.
amy_external_fread_hook uint32_t (uint32_t fptr, uint8_t *bytes, uint32_t len) zD Read bytes from a file opened via fopen hook.
amy_external_fseek_hook void (uint32_t fptr, uint32_t pos) zD Seek to position in a file opened via fopen hook.
amy_external_fclose_hook void (uint32_t fptr) zT, zD, zF Close a file opened via fopen hook.
amy_external_file_transfer_done_hook void (const char *filename) zT Called after a zT file transfer completes. On AMYboard, restarts sketch.py.
amy_external_update_file_hook void (const char *filename) zA Called by zA to update a file with current AMY state. On AMYboard, splices live knob state into sketch.py.
amy_external_exec_hook void (const char *code) zP Called by zP to execute a string on the host. On AMYboard, runs the string as Python via exec().
amy_external_reboot_hook void (uint8_t mode) zB Called 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 code C amy_event Python / JS Type-range Notes
i synth synth 0-31 Define a set of voices for voice management.
ic TODO midi_cc C,L,N,X,O,CMD MIDI 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
if synth_flags synth_flags uint Flags for synth creation: 1 = Use MIDI drum note->preset translation; 2 = Drop note-off events.
id synth_delay_ms synth_delay uint Delay (in ms) applied to synth note-ons. Gives time for decay of 'stolen' notes.
it to_synth to_synth 0-31 New synth number, when changing the number (MIDI channel for n=1..16) of an entire synth.
iv num_voices num_voices int The 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.
in oscs_per_voice oscs_per_voice >0 Reserve 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.
im grab_midi_notes grab_midi_notes 0/1 Use amy.send(synth=CHANNEL, grab_midi_notes=0) to prevent the default direct forwarding of MIDI note-on/offs to synth CHANNEL.
ip pedal pedal int Non-zero means pedal is down (i.e., sustain). Must be used with synth.
K patch_number patch uint 0-X Apply a saved or user patch to a specified synth or voice.
r voices[] voices int[,int] Comma separated list of voices to send message to, or load patch into.
u TODO patch_string string Provide 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 code C amy_event Python / JS Type-range Notes
v osc osc uint 0 to OSCS-1 Which oscillator to control
w wave wave uint 0-21 Waveform: [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
S reset_osc reset uint Resets 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.
A eg0_times[], eg0_values[] bp0 string (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).
B eg1_times[], eg1_values[] bp1 string (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]).
b feedback feedback float 0-1 Use for the ALGO synthesis type in FM or for karplus-strong, or to indicate PCM looping (0 off, >0, on)
c chained_osc chained_osc uint 0 to OSCS-1 Chained 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.
G filter_type filter_type 0-4 Filter type: 0 = none (default.) 1 = lowpass, 2 = bandpass, 3 = highpass, 4 = double-order lowpass.
I ratio ratio float For ALGO types, ratio of modulator frequency to base note frequency
L mod_source mod_source 0 to OSCS-1 Which oscillator is used as an modulation/LFO source for this oscillator. Source oscillator will be silent.
m portamento portamento uint Time constant (in ms) for pitch changes when note is changed without intervening note-off. default 0 (immediate), 100 is good.
n midi_note note float, but typ. uint 0-127 Midi note, sets frequency. Fractional Midi notes are allowed.
o algorithm algorithm uint 1-32 DX7 FM algorithm to use for ALGO type
O algo_source[] algo_source string Which 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,,,
p preset preset int Which 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).
p preset num_partials int Alias for preset. Must be used with wave=BYO_PARTIALS. Cannot be combined with preset in the same message.
P phase phase float 0-1 Where in the oscillator's cycle to begin the waveform (also works on the PCM buffer). default 0
R resonance resonance float Q factor of variable filter, 0.5-16.0. default 0.7
T eg_type[0] eg0_type uint 0-3 Type for Envelope Generator 0 - 0: Normal (RC-like) / 1: Linear / 2: DX7-style / 3: True exponential.
X eg_type[1] eg1_type uint 0-3 Type for Envelope Generator 1 - 0: Normal (RC-like) / 1: Linear / 2: DX7-style / 3: True exponential.
l velocity vel float Note on velocity. Use to start an envelope or set amplitude

CtrlCoefs

These per-oscillator parameters use CtrlCoefs notation

Wire code C amy_event Python / JS Type-range Notes
Q pan_coefs[] pan float[,float...] Panning index ControlCoefficients (for stereo output), 0.0=left, 1.0=right. default 0.5.
a amp_coefs[] amp float[,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.)
d duty_coefs[] duty float[,float...] Duty cycle for pulse wave, ControlCoefficients, defaults to 0.5
f freq_coefs[] freq float[,float...] Frequency of oscillator, set of ControlCoefficients. Default is 0,1,0,0,0,0,1 (from note pitch plus pitch_bend)
F filter_freq_coefs[] filter_freq float[,float...] Center/break frequency for variable filter, set of ControlCoefficients

PCM sampling

Wire code C amy_event Python / JS Type-range Notes
z TODO load_sample uint x 6 Signal 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.
zF TODO disk_sample uint,string,uint Set 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.
zS TODO start_sample uint x 6 Start 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.
zO TODO stop_sample uint Stop 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 code C amy_event Python / JS Type-range Notes
H sequence[3] sequence int,int,int Tick offset, period, tag for sequencing
h reverb_level, reverb_liveness, reverb_damping, reverb_xover_hz reverb float[,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.
j tempo tempo float The tempo (BPM, quarter notes) of the sequencer. Defaults to 108.0.
k chorus_level, chorus_max_delay, chorus_lfo_freq, chorus_depth chorus float[,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).
M echo_level, echo_delay_ms, echo_max_delay_ms, echo_feedback, echo_filter_coef echo float[,int,int,float,float] Echo parameters -- level, delay_ms, max_delay_ms, feedback, filter_coef (-1 is HPF, 0 is flat, +1 is LPF).
N latency_ms latency_ms uint Sets latency in ms. default 0 (see LATENCY)
s pitch_bend pitch_bend float Sets the global pitch bend, by default modifying all note frequencies by (fractional) octaves up or down
t time time uint Request playback time relative to some fixed start point on your host, in ms. Allows precise future scheduling.
V volume volume float 0-10 Volume knob for entire synth, default 1.0
x eq_l, eq_m, eq_h eq float,float,float Equalization in dB low (~800Hz) / med (~2500Hz) / high (~7500Gz) -15 to 15. 0 is off. default 0.
g client client uint Client number for Alles distributed synthesis.
W external_channel external_channel uint External channel routing (used by Tulip for CV output).
D TODO debug uint, 2-4 2 shows queue sample, 3 shows oscillator data, 4 shows modified oscillator. Will interrupt audio!
zT TODO transfer_file string,uint Transfer a file to the host. Params: destination filename, file size. See hooks for writing files on host disk.
zA TODO update_file string (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.
zD TODO dump_sysex string (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.
zP TODO exec string Execute 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.
zI TODO ping (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).
zB TODO reboot uint (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().