Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d3965e0
Support config.write_samples_fn as alternative to built-in I2S.
dpwe Nov 3, 2025
d69897a
Try to avoid issues with calling amy_live_start() twice.
dpwe Nov 3, 2025
9e8e9f4
amy.h: start of hardware bitmask in struct amy_config.
dpwe Nov 28, 2025
29dc119
Arduino for RPi Pico works; miniaudio broken (so macbook targets give…
dpwe Dec 1, 2025
f53f57a
Trying to fix libminiaudio so amy-example will run.
dpwe Dec 1, 2025
0cc16e2
amy-example works again; simple_fill_buffer restored for now; pyamy c…
dpwe Dec 1, 2025
158dfd5
Works against tulip/get_output_buffer (for amyboard).
dpwe Dec 5, 2025
fec40b1
Remove amy_live_start/stop; amy-example and python tests still run.
dpwe Dec 7, 2025
c10833e
Reintroduce _amy.live() to reset devices.
dpwe Dec 9, 2025
a36ff80
merge with origin/main
dpwe Dec 28, 2025
bad5c34
Merge with main & fix amy-example to work again.
dpwe Dec 29, 2025
f7cb742
Rename config.hardware to platform, make it a bitfield struct.
dpwe Dec 29, 2025
78dc5d4
Merge remote-tracking branch 'origin/main' into write_samples_fn
dpwe Dec 29, 2025
ba8743b
Fixing AMY_Arduino to work on ESP32.
dpwe Dec 30, 2025
45ba17c
i2s.c: trying to make esp32 work without background i2s.
dpwe Jan 1, 2026
232742f
Merge branch 'main' into write_samples_fn
bwhitman Jan 1, 2026
42908a6
fixing example back
bwhitman Jan 1, 2026
37a4e08
Merge branch 'write_samples_fn' of https://github.com/shorepine/amy i…
dpwe Jan 1, 2026
54d5357
fix typo
dpwe Jan 1, 2026
23e235a
Cleanup cruft; make sure cPython amy.live() works.
dpwe Jan 2, 2026
b789c5c
More cleanup; updated api.md and arduino.md for remove of 'live' etc.
dpwe Jan 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ SOURCES += src/algorithms.c src/amy.c src/envelope.c src/examples.c src/parse.c
src/libminiaudio-audio.c src/instrument.c src/amy_midi.c src/api.c src/midi_mappings.c

OBJECTS = $(patsubst %.c, %.o, $(SOURCES))

HEADERS = $(wildcard src/*.h)
HEADERS_BUILD := $(filter-out src/patches.h,$(HEADERS))

Expand Down
4 changes: 2 additions & 2 deletions amy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,9 @@ def render(seconds):
frames.append( np.array(_amy.render_to_list())/32768.0 )
return np.hstack(frames).reshape((-1, AMY_NCHANS))

def restart():
def restart(default_synths=0):
_amy.stop()
_amy.start()
_amy.start(default_synths)

def inject_midi(a, b, c, d=None):
if d is None:
Expand Down
21 changes: 9 additions & 12 deletions amy/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,12 @@ class AmyTest:
test_dir = './tests/tst'

def __init__(self):
self.config_default = False
self.default_synths = False

def test(self):
name = self.__class__.__name__
_amy.stop()
if self.config_default:
_amy.start()
else:
_amy.start_no_default()
_amy.start(1 if self.default_synths else 0)
self.run()

samples = amy.render(1.0)
Expand Down Expand Up @@ -640,7 +637,7 @@ class TestVoiceStealing(AmyTest):

def __init__(self):
super().__init__()
self.config_default = True
self.default_synths = True

def run(self):
# Default juno synth.
Expand Down Expand Up @@ -688,7 +685,7 @@ class TestMidiDrums(AmyTest):

def __init__(self):
super().__init__()
self.config_default = True
self.default_synths = True

def run(self):
# inject_midi args are (time, midi_event_chan, midi_note, midi_vel)
Expand All @@ -704,7 +701,7 @@ class TestDefaultChan1Synth(AmyTest):

def __init__(self):
super().__init__()
self.config_default = True
self.default_synths = True

def run(self):
amy.send(time=100, synth=1, note=60, vel=1)
Expand All @@ -722,7 +719,7 @@ class TestSynthProgChange(AmyTest):

def __init__(self):
super().__init__()
self.config_default = True
self.default_synths = True

def run(self):
# DX7 first patch, uses 9 oscs/voice, num_voices is inherited from previous init.
Expand All @@ -742,7 +739,7 @@ class TestSynthDrums(AmyTest):

def __init__(self):
super().__init__()
self.config_default = True
self.default_synths = True

def run(self):
amy.send(time=100, synth=10, note=35, vel=100/127) # bass
Expand Down Expand Up @@ -799,7 +796,7 @@ class TestPatchFromEvents(AmyTest):
"""Test defining a patch from events with patch_number."""
def __init__(self):
super().__init__()
self.config_default = True # So that the patch space is already partly populated.
self.default_synths = True # So that the patch space is already partly populated.

def run(self):
amy.send(time=0, patch=1039, reset=amy.RESET_PATCH)
Expand Down Expand Up @@ -838,7 +835,7 @@ class TestFileTransfer(AmyTest):

def test(self):
_amy.stop()
_amy.start_no_default()
_amy.start(0)
payload = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(2048))
with tempfile.NamedTemporaryFile(mode='w', delete=True) as f:
with tempfile.NamedTemporaryFile(mode='w+', delete=True) as g:
Expand Down
21 changes: 10 additions & 11 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,16 @@ uint32_t amy_sysclock();

Start and stop AMY:
```c
amy_stop();

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

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

// Start playing audio in real time
amy_live_start();
// Stop AMY including any live audio output
amy_stop();

// Stop playing audio
amy_live_stop();
```

Default MIDI handlers:
Expand All @@ -82,14 +78,17 @@ amy_start(amy_config);
| ------ | ------ | -------- | -------- |
| `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.audio_in` | `0=off, 1=on` | On | If audio_in gets processed via the audio interface |
| `features.default_synths` | `0=off, 1=on` | On| If AMY boots with Juno-6 on `synth` 1 and GM drums on `synth` 10 |
| `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.startup_bleep` | `0=off, 1=on` | On | If AMY plays a startup sound on boot |
| `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 |
Expand Down
5 changes: 2 additions & 3 deletions docs/arduino.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ AMY is on the Arduino Libraries repository. Simply search for "AMY" in the Libra

<img src="https://github.com/shorepine/amy/raw/main/docs/arduino1.png" width="400"/>

We recommend always using the latest released version (now `1.1.0`) in the Arduino Library Manager.
We recommend always using the latest released version (now `1.1.4`) in the Arduino Library Manager.

However, if you are directed to a bleeding edge release of AMY, you can simply copy this repository to your `Arduino/libraries` folder as `Arduino/libraries/amy`. (Make sure you delete whatever you already had in `libraries/amy`.)

Expand All @@ -18,7 +18,6 @@ void setup() {
amy_config_t amy_config = amy_default_config();
// set your pins, etc -- see the AMY_MIDI_Synth example
amy_start(amy_config);
amy_live_start(); // if you want us to handle sending audio
}

void loop() {
Expand All @@ -29,7 +28,7 @@ void loop() {

AMY in Arduino handles MIDI input and output as well as I2S and USB (where supported) audio. You do not need to set up an I2S interface, just the pins in `amy_config`.

(For experts: if you're rendering AMY audio to your own audio hardware, you can omit the call to `amy_live_start()` and call our [`amy_simple_fill_buffer()` API](api.md) in `loop()` instead. This gives you a buffer of stereo 16-bit short integers to send out to whatever interface you set up.)
(For experts: if you're rendering AMY audio to your own audio hardware, you can set `amy_config.audio = AMY_AUDIO_IS_NONE` before calling `amy_start()`, then use the return value from `amy_update()` as a pointer to the next block of `AMY_BLOCK_SIZE` 16 bit stereo sample frames, to send out to whatever interface you set up.)

[Please see our latest matrix of supported features per chip/board.](https://github.com/shorepine/amy/issues/354)

Expand Down
8 changes: 4 additions & 4 deletions examples/AMY_MIDI_Synth/AMY_MIDI_Synth.ino
Original file line number Diff line number Diff line change
Expand Up @@ -78,25 +78,25 @@ void setup() {
// Install the default_synths on synths (MIDI chans) 1, 2, and 10 (this is the default).
amy_config.features.default_synths = 1;

// If you want MIDI over UART (5-pin or 3-pin serial MIDI)
amy_config.midi = AMY_MIDI_IS_UART;

// Pins for i2s board
// Note: On the Teensy, all these settings are ignored, and blck = 21, lrc = 20, dout = 7.
amy_config.audio = AMY_AUDIO_IS_I2S;
amy_config.features.audio_in = 1;
amy_config.i2s_mclk = 7;
amy_config.i2s_bclk = 8;
// On Pi Pico (RP2040, RP2350), i2s_lrc has to be i2s_bclk + 1, otherwise code will stop on an assert.
amy_config.i2s_lrc = 9;
amy_config.i2s_dout = 10;
amy_config.i2s_din = 11;

// If you want MIDI over UART (5-pin or 3-pin serial MIDI)
amy_config.midi = AMY_MIDI_IS_UART;
// Pins for UART MIDI
// Note: On the Teensy, these are ignored and midi_out = 35, midi_in = 34.
amy_config.midi_out = 4;
amy_config.midi_in = 5;

amy_start(amy_config);
amy_live_start();

//test_polyphony();
//test_sequencer();
Expand Down
74 changes: 74 additions & 0 deletions examples/AMY_pico_PWM/AMY_pico_PWM.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include <AMY-Arduino.h>

// AMY_pico_PWM
//
// Runs AMY using arduino-pico's PWM audio output.
// This version responds to serial MIDI input.
// dpwe 2025-10-26

// If you define this, we write to PWM using AMY's write_samples_fn hook
// but leaving it undefined means we pass the sample block in our loop() fn.
//#define USE_AMY_WRITE_SAMPLES_FN

extern "C" {
extern void example_sequencer_drums_synth(uint32_t start);
}

#include <PWMAudio.h>
PWMAudio pwm(0, true); // PWM Stereo out on pins 0 and 1.

// Have to introduce this function to work around virtual overloaded write function.
size_t pwm_write(const uint8_t *buffer, size_t nbytes) {
return pwm.write(buffer, nbytes);
}

void setup() {
#ifdef LED_BUILTIN
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, 1);
#endif

// Setup PWM
pwm.setBuffers(4, AMY_BLOCK_SIZE * AMY_NCHANS * sizeof(int16_t) / sizeof(int32_t));
pwm.begin(44100);

amy_config_t amy_config = amy_default_config();
amy_config.features.startup_bleep = 1;
amy_config.features.default_synths = 1;

amy_config.midi = AMY_MIDI_IS_UART;
// Pins for UART MIDI
amy_config.midi_in = 5;

#ifdef USE_AMY_WRITE_SAMPLES_FN
// Configure to pass samples to PWM.
amy_config.write_samples_fn = pwm_write;
#endif

amy_start(amy_config);

// Set a drum loop going, as an example.
example_sequencer_drums_synth(2000);
}

static long last_millis = 0;
static const long millis_interval = 250;
static bool led_state = 0;

void loop() {
int16_t *block = amy_update();
#ifndef USE_AMY_WRITE_SAMPLES_FN
// We have opted to handle our own sample writing.
pwm_write((uint8_t *)block, AMY_BLOCK_SIZE * AMY_NCHANS * sizeof(int16_t));
#endif

// Flash on-board LED every 250ms
int now_millis = millis();
if ((now_millis - last_millis) > millis_interval) {
last_millis = now_millis;
led_state = !led_state;
#ifdef LED_BUILTIN
digitalWrite(LED_BUILTIN, led_state); // turn the LED on (HIGH is the voltage level)
#endif
}
}
54 changes: 20 additions & 34 deletions src/amy-example.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,57 +58,43 @@ int main(int argc, char ** argv) {

amy_config_t amy_config = amy_default_config();
amy_config.audio = AMY_AUDIO_IS_MINIAUDIO;
amy_config.features.audio_in = 1; // We need audio_in for miniaudio to run??
amy_config.playback_device_id = playback_device_id;
fprintf(stderr, "playback_device_id=%d\n", playback_device_id);
amy_config.capture_device_id = capture_device_id;
amy_config.features.default_synths = 0;
amy_start(amy_config);

amy_live_start();
amy_add_message("zF1024,sounds/sleepwalk.wav,60");
amy_add_message("zF1025,sounds/sleepwalk.wav,60");


amy_event e = amy_default_event();
e.wave = PCM_LEFT;
e.preset = 1024;
e.velocity=1;
e.pan_coefs[0] = 0;
e.midi_note = 60;
e.osc = 14;
amy_add_event(&e);

e.wave = PCM_RIGHT;
e.preset = 1025;
e.velocity=1;
e.pan_coefs[0] = 1;
e.midi_note = 60;
e.osc = 15;
amy_add_event(&e);
amy_config.features.default_synths = 1;

for (int tries = 0; tries < 2; ++tries) {
amy_start(amy_config);


//example_fm(0);
//example_voice_chord(0,0);
//example_synth_chord(0, /* patch */ 0);
example_synth_chord(0, /* patch */ 0);
//example_sustain_pedal(0, /* patch */ 256);
//example_sequencer_drums(0);
//example_patch_from_events();

// Check that trying to program a non-user patch doesn't crash
amy_event e = amy_default_event();
e.patch_number = 25;
e.osc = 0;
e.wave = SINE;
amy_add_event(&e);

// Now just spin for 15s
// Now just spin for a while
uint32_t start = amy_sysclock();
while(amy_sysclock() - start < 30000) {
while(amy_sysclock() - start < 5000) {
usleep(THREAD_USLEEP);
}

//show_debug(99);

amy_live_stop();
//show_debug(99);
amy_stop();

amy_stop();
// Make sure libminiaudio has time to clean up.
sleep(2);

// Make sure libminiaudio has time to clean up.
sleep(2);
}

return 0;
}
Expand Down
5 changes: 2 additions & 3 deletions src/amy-message.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,11 @@ int main(int argc, char ** argv) {

amy_config_t amy_config = amy_default_config();
amy_config.audio = AMY_AUDIO_IS_MINIAUDIO;
amy_config.features.audio_in = 1; // We need audio_in for miniaudio to run??
amy_config.features.default_synths = 0;
amy_config.playback_device_id = playback_device_id;
amy_config.capture_device_id = capture_device_id;
amy_start(amy_config);
amy_live_start();

while (1) {
char input[1024];
Expand All @@ -72,8 +73,6 @@ int main(int argc, char ** argv) {
}
}

amy_live_stop();

return 0;
}
#endif
Loading