diff --git a/Makefile b/Makefile
index d661a6a..5c48e35 100644
--- a/Makefile
+++ b/Makefile
@@ -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))
diff --git a/amy/__init__.py b/amy/__init__.py
index afafa88..2f8e1ab 100644
--- a/amy/__init__.py
+++ b/amy/__init__.py
@@ -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:
diff --git a/amy/test.py b/amy/test.py
index 9d1d57c..aeb24ea 100644
--- a/amy/test.py
+++ b/amy/test.py
@@ -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)
@@ -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.
@@ -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)
@@ -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)
@@ -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.
@@ -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
@@ -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)
@@ -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:
diff --git a/docs/api.md b/docs/api.md
index 2351b0c..85b7c00 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -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:
@@ -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 |
diff --git a/docs/arduino.md b/docs/arduino.md
index b2a30ef..d441f54 100644
--- a/docs/arduino.md
+++ b/docs/arduino.md
@@ -4,7 +4,7 @@ AMY is on the Arduino Libraries repository. Simply search for "AMY" in the Libra
-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`.)
@@ -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() {
@@ -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)
diff --git a/examples/AMY_MIDI_Synth/AMY_MIDI_Synth.ino b/examples/AMY_MIDI_Synth/AMY_MIDI_Synth.ino
index bef12f8..6024de1 100644
--- a/examples/AMY_MIDI_Synth/AMY_MIDI_Synth.ino
+++ b/examples/AMY_MIDI_Synth/AMY_MIDI_Synth.ino
@@ -78,11 +78,10 @@ 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.
@@ -90,13 +89,14 @@ void setup() {
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();
diff --git a/examples/AMY_pico_PWM/AMY_pico_PWM.ino b/examples/AMY_pico_PWM/AMY_pico_PWM.ino
new file mode 100644
index 0000000..62150ce
--- /dev/null
+++ b/examples/AMY_pico_PWM/AMY_pico_PWM.ino
@@ -0,0 +1,74 @@
+#include
+
+// 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 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
+ }
+}
diff --git a/src/amy-example.c b/src/amy-example.c
index 8843ca2..af713fe 100644
--- a/src/amy-example.c
+++ b/src/amy-example.c
@@ -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;
}
diff --git a/src/amy-message.c b/src/amy-message.c
index 38801cf..8151d51 100644
--- a/src/amy-message.c
+++ b/src/amy-message.c
@@ -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];
@@ -72,8 +73,6 @@ int main(int argc, char ** argv) {
}
}
- amy_live_stop();
-
return 0;
}
#endif
diff --git a/src/amy-piano.c b/src/amy-piano.c
index 118299d..fcc6056 100644
--- a/src/amy-piano.c
+++ b/src/amy-piano.c
@@ -14,10 +14,11 @@ void delay_ms(uint32_t ms) {
int main(int argc, char ** argv) {
amy_config_t amy_config = amy_default_config();
amy_config.audio = AMY_AUDIO_IS_MINIAUDIO;
+ //amy_config.playback_device_id = -1;
+ //amy_config.capture_device_id = -1;
+ amy_config.features.audio_in = 1; // Needed to make libminiaudio work?
amy_config.features.default_synths = 0;
amy_start(amy_config);
-
- amy_live_start();
amy_add_message("S16384Z");
amy_add_message("t0V5Z");
@@ -183,8 +184,6 @@ int main(int argc, char ** argv) {
show_debug(99);
- amy_live_stop();
-
return 0;
}
diff --git a/src/amy.c b/src/amy.c
index 0dc2404..d02c532 100644
--- a/src/amy.c
+++ b/src/amy.c
@@ -155,6 +155,9 @@ SAMPLE *fbl[AMY_MAX_CORES];
SAMPLE *per_osc_fb[AMY_MAX_CORES];
SAMPLE core_max[AMY_MAX_CORES];
+// Public pointer to recently-emitted waveform block. No sync for the moment. Cleared to NULL when read by amy_get_output_buffer.
+output_sample_type * amy_out_block;
+output_sample_type * amy_out_block_copy;
// Audio input blocks. Filled by the audio implementation before rendering.
// For live audio input from a codec, AUDIO_IN0 / 1
output_sample_type * amy_in_block;
@@ -358,6 +361,7 @@ int peek_stack(char *tag) { return 0; }
int8_t global_init(amy_config_t c) {
peek_stack("init");
amy_global.config = c;
+ amy_global.i2s_is_in_background = 0;
amy_global.delta_queue = NULL;
amy_global.delta_qsize = 0;
amy_global.volume = 1.0f;
@@ -840,6 +844,7 @@ int8_t oscs_init() {
bzero(synth, sizeof(struct synthinfo *) * (AMY_OSCS+1));
msynth = (struct mod_synthinfo **) malloc_caps(sizeof(struct mod_synthinfo *) * (AMY_OSCS+1), amy_global.config.ram_caps_synth);
block = (output_sample_type *) malloc_caps(sizeof(output_sample_type) * AMY_BLOCK_SIZE * AMY_NCHANS, amy_global.config.ram_caps_block);
+ amy_out_block_copy = (output_sample_type *) malloc_caps(sizeof(output_sample_type) * AMY_BLOCK_SIZE * AMY_NCHANS, amy_global.config.ram_caps_block);
amy_in_block = (output_sample_type*)malloc_caps(sizeof(output_sample_type)*AMY_BLOCK_SIZE*AMY_NCHANS, amy_global.config.ram_caps_block);
amy_external_in_block = (output_sample_type*)malloc_caps(sizeof(output_sample_type)*AMY_BLOCK_SIZE*AMY_NCHANS, amy_global.config.ram_caps_block);
// set all oscillators to their default values
@@ -1750,6 +1755,7 @@ int16_t * amy_fill_buffer() {
amy_global.total_blocks++;
AMY_PROFILE_STOP(AMY_FILL_BUFFER)
+ amy_out_block = block;
return block;
}
diff --git a/src/amy.h b/src/amy.h
index f727dee..beef61b 100644
--- a/src/amy.h
+++ b/src/amy.h
@@ -101,6 +101,7 @@ extern const uint16_t pcm_samples;
#define AMY_HAS_STARTUP_BLEEP (amy_global.config.features.startup_bleep)
#define AMY_HAS_REVERB (amy_global.config.features.reverb)
#define AMY_HAS_AUDIO_IN (amy_global.config.features.audio_in)
+#define AMY_HAS_I2S (amy_global.config.audio == AMY_AUDIO_IS_I2S)
#define AMY_HAS_DEFAULT_SYNTHS (amy_global.config.features.default_synths)
#define AMY_HAS_CHORUS (amy_global.config.features.chorus)
#define AMY_HAS_ECHO (amy_global.config.features.echo)
@@ -608,7 +609,12 @@ typedef struct {
uint8_t custom : 1;
uint8_t startup_bleep : 1;
} features;
- uint8_t midi;
+ struct {
+ uint8_t multicore : 1; // Use secondary cores if available.
+ uint8_t multithread : 1; // Use multitasking (e.g. RTOS threads) if available.
+ } platform;
+ // midi and audio are potentially bitmasks indicating data routing.
+ uint8_t midi;
uint8_t audio;
// variables
@@ -619,6 +625,9 @@ typedef struct {
uint32_t max_synths;
uint32_t max_memory_patches;
+ // alternative audio output function
+ size_t (*write_samples_fn)(const uint8_t *buffer, size_t buffer_size);
+
// pins for MCU platforms
int8_t i2s_lrc;
int8_t i2s_dout;
@@ -672,6 +681,7 @@ typedef struct echo_config {
struct state {
amy_config_t config;
uint8_t running;
+ uint8_t i2s_is_in_background; // Flag not to handle I2S in amy_update.
float volume;
float pitch_bend;
// State of fixed dc-blocking HPF
@@ -723,6 +733,7 @@ extern struct synthinfo** synth;
extern struct mod_synthinfo** msynth;
extern struct state amy_global;
+extern output_sample_type * amy_out_block;
extern output_sample_type * amy_in_block;
extern output_sample_type * amy_external_in_block;
@@ -766,6 +777,7 @@ void hold_and_modify(uint16_t osc) ;
void amy_execute_delta();
void amy_execute_deltas();
int16_t * amy_fill_buffer();
+int16_t * amy_simple_fill_buffer(); // excute_deltas + render + fill_buffer
uint32_t ms_to_samples(uint32_t ms) ;
@@ -776,17 +788,19 @@ void amy_add_event(amy_event *e);
void amy_parse_message(char * message, int length, amy_event *e);
void amy_start(amy_config_t);
void amy_stop();
-void amy_live_start();
-void amy_live_stop();
-int16_t * amy_simple_fill_buffer() ;
-void amy_update();
-int16_t *amy_render_audio();
-void amy_pass_to_i2s(const int16_t *block);
+
+int16_t *amy_update(); // in api.c
+void amy_platform_init(); // in i2s.c
+void amy_update_tasks(); // in i2s.c
+int16_t *amy_render_audio(); // in i2s.c
+size_t amy_i2s_write(const uint8_t *buffer, size_t nbytes); // in i2s.c
+
amy_config_t amy_default_config();
void amy_clear_event(amy_event *e);
amy_event amy_default_event();
uint32_t amy_sysclock();
-void amy_get_input_buffer(output_sample_type * samples);
+int amy_get_output_buffer(output_sample_type * samples);
+int amy_get_input_buffer(output_sample_type * samples);
void amy_set_external_input_buffer(output_sample_type * samples);
extern uint8_t (*amy_external_render_hook)(uint16_t, SAMPLE*, uint16_t);
extern float (*amy_external_coef_hook)(uint16_t);
diff --git a/src/amy_midi.c b/src/amy_midi.c
index 5d408a3..c878ed4 100644
--- a/src/amy_midi.c
+++ b/src/amy_midi.c
@@ -346,8 +346,8 @@ void check_tusb_midi() {
}
}
#endif
-void run_midi_task() {
- const int uart_num = esp_get_uart(amy_global.config.midi_uart);
+
+void esp_init_midi(void) {
uart_config_t uart_config = {
.baud_rate = 31250,
.data_bits = UART_DATA_8_BITS,
@@ -356,6 +356,7 @@ void run_midi_task() {
};
// Configure UART parameters
+ const int uart_num = esp_get_uart(amy_global.config.midi_uart);
ESP_ERROR_CHECK(uart_param_config(uart_num, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(uart_num, amy_global.config.midi_out, amy_global.config.midi_in, UART_PIN_NO_CHANGE , UART_PIN_NO_CHANGE ));
@@ -376,14 +377,21 @@ void run_midi_task() {
};
uart_intr_config(uart_num, &uart_intr);
+}
+void esp_poll_midi(void) {
+ const int uart_num = esp_get_uart(amy_global.config.midi_uart);
uint8_t data[MAX_MIDI_BYTES_TO_PARSE];
- size_t length = 0;
+ int length = uart_read_bytes(uart_num, data, MAX_MIDI_BYTES_TO_PARSE /*MAX_MIDI_BYTES_PER_MESSAGE*MIDI_QUEUE_DEPTH*/, 1/portTICK_PERIOD_MS);
+ if(length > 0) {
+ convert_midi_bytes_to_messages(data,length,0);
+ }
+}
+
+void run_midi_task() {
+
while(1) {
- length = uart_read_bytes(uart_num, data, MAX_MIDI_BYTES_TO_PARSE /*MAX_MIDI_BYTES_PER_MESSAGE*MIDI_QUEUE_DEPTH*/, 1/portTICK_PERIOD_MS);
- if(length > 0) {
- convert_midi_bytes_to_messages(data,length,0);
- }
+ esp_poll_midi();
#ifdef AMYBOARD
check_tusb_midi();
#endif
@@ -392,7 +400,12 @@ void run_midi_task() {
void run_midi() {
sysex_buffer = malloc_caps(MAX_SYSEX_BYTES, amy_global.config.ram_caps_sysex);
- xTaskCreatePinnedToCore(run_midi_task, MIDI_TASK_NAME, (MIDI_TASK_STACK_SIZE) / sizeof(StackType_t), NULL, MIDI_TASK_PRIORITY, &midi_handle, MIDI_TASK_COREID);
+ if(amy_global.config.midi & AMY_MIDI_IS_UART) {
+ esp_init_midi();
+ if (amy_global.config.platform.multithread) {
+ xTaskCreatePinnedToCore(run_midi_task, MIDI_TASK_NAME, (MIDI_TASK_STACK_SIZE) / sizeof(StackType_t), NULL, MIDI_TASK_PRIORITY, &midi_handle, MIDI_TASK_COREID);
+ } // otherwise esp_poll_midi is called from amy_update_tasks()
+ }
}
@@ -439,14 +452,6 @@ void run_midi() {
}
-void check_tusb_midi() {
- while ( tud_midi_available() ) {
- uint8_t packet[4];
- tud_midi_packet_read(packet);
- convert_midi_bytes_to_messages(packet+1, 3, 1);
- }
-}
-
#endif
#ifdef __IMXRT1062__
diff --git a/src/api.c b/src/api.c
index 6d1a357..9b49663 100644
--- a/src/api.c
+++ b/src/api.c
@@ -39,10 +39,16 @@ amy_config_t amy_default_config() {
c.features.chorus = 1;
c.features.partials = 1;
c.features.custom = 1;
- c.features.audio_in = 1;
- c.features.default_synths = 1;
+ c.features.default_synths = 0;
+ c.features.audio_in = 0;
c.features.startup_bleep = 0;
+ // Use all platform features by default.
+ c.platform.multicore = 1;
+ c.platform.multithread = 1;
+
+ c.write_samples_fn = NULL;
+
c.midi = AMY_MIDI_IS_NONE;
#ifndef AMY_MCU
c.audio = AMY_AUDIO_IS_MINIAUDIO;
@@ -167,9 +173,18 @@ void amy_clear_event(amy_event *e) {
}
-// get AUDIO_IN0 and AUDIO_IN1
-void amy_get_input_buffer(output_sample_type * samples) {
+// get last-written output, returns number of bytes written.
+int amy_get_output_buffer(output_sample_type * samples) {
+ if (amy_out_block == NULL) return 0;
+ for(uint16_t i=0;ifree_list == NULL) {
+void amy_update_tasks() {
amy_execute_deltas();
if(amy_global.config.midi & AMY_MIDI_IS_UART) on_pico_uart_rx();
#ifdef TUD_USB_GADGET
if(amy_global.config.midi & AMY_MIDI_IS_USB_GADGET) on_pico_uart_rx();
#endif
- //}
}
-int16_t *amy_render_audio() {
- //if (ap->free_list != NULL) {
#define USE_SECOND_CORE
+
+int32_t render_other_core(int32_t data) {
+ amy_render(AMY_OSCS/2, AMY_OSCS, 1);
+ return AMY_OK;
+}
+
+int16_t *amy_render_audio() {
#ifdef USE_SECOND_CORE
- int32_t res;
- queue_entry_t entry = {render_other_core, AMY_OK};
- queue_add_blocking(&call_queue, &entry);
- amy_render(0, AMY_OSCS/2, 0);
- queue_remove_blocking(&results_queue, &res);
-#else
- amy_render(0, AMY_OSCS, 0);
+ if (amy_global.config.platform.multicore) {
+ int32_t res;
+ queue_entry_t entry = {render_other_core, AMY_OK};
+ queue_add_blocking(&call_queue, &entry);
+ amy_render(0, AMY_OSCS/2, 0);
+ queue_remove_blocking(&results_queue, &res);
+ } else
#endif
+ amy_render(0, AMY_OSCS, 0);
int16_t *block = amy_fill_buffer();
return block;
}
-void amy_update() {
+size_t amy_i2s_write(const uint8_t *buffer, size_t nbytes) {
// Single function to update buffers.
- amy_poll_tasks();
- int16_t *block = amy_render_audio();
- amy_pass_to_i2s(block);
+ // len is the number of int16 sample frames.
+ pico_i2s_read_write_buffer(amy_in_block, (const int16_t *)buffer, AMY_BLOCK_SIZE);
+ return nbytes;
}
@@ -331,26 +373,19 @@ void core1_main() {
}
}
-// Provided by pico_support.cpp
-extern void pico_setup_i2s(amy_config_t *config);
-extern void pico_i2s_read_write_buffer(int16_t *in_samples, const int16_t *out_samples, int nframes);
-
-void amy_pass_to_i2s(const int16_t *block) {
- // len is the number of int16 sample frames.
- pico_i2s_read_write_buffer(amy_in_block, block, AMY_BLOCK_SIZE);
-}
-
-amy_err_t i2s_amy_init() {
- pico_setup_i2s(&amy_global.config);
-
+void amy_platform_init() {
+ if (AMY_HAS_I2S) {
+ pico_setup_i2s(&amy_global.config);
+ }
#ifdef USE_SECOND_CORE
- queue_init(&call_queue, sizeof(queue_entry_t), 2);
- queue_init(&results_queue, sizeof(int32_t), 2);
- uint32_t * core1_separate_stack_address = (uint32_t*)malloc(0x2000);
- multicore_launch_core1_with_stack(core1_main, core1_separate_stack_address, 0x2000);
- sleep_ms(500);
+ if (amy_global.config.platform.multicore) {
+ queue_init(&call_queue, sizeof(queue_entry_t), 2);
+ queue_init(&results_queue, sizeof(int32_t), 2);
+ uint32_t * core1_separate_stack_address = (uint32_t*)malloc(0x2000);
+ multicore_launch_core1_with_stack(core1_main, core1_separate_stack_address, 0x2000);
+ sleep_ms(500);
+ }
#endif
- return AMY_OK;
}
@@ -358,16 +393,17 @@ amy_err_t i2s_amy_init() {
extern void teensy_setup_i2s();
-extern void teensy_i2s_send(int16_t *samples);
+extern size_t teensy_i2s_write(const uint8_t *buffer, size_t nbytes);
extern int16_t teensy_get_serial_byte();
-amy_err_t i2s_amy_init() {
- teensy_setup_i2s();
- return AMY_OK;
+void amy_platform_init() {
+ if (AMY_HAS_I2S) {
+ teensy_setup_i2s();
+ }
}
-void amy_update() {
+void amy_update_tasks() {
if(amy_config.global.midi & AMY_MIDI_IS_UART) {
// do midi in here
uint8_t bytes[1];
@@ -378,9 +414,16 @@ void amy_update() {
}
}
amy_execute_deltas();
+}
+
+int16_t *amy_render_audio() {
amy_render(0, AMY_OSCS, 0);
int16_t *block = amy_fill_buffer();
- teensy_i2s_send(block);
+ return block;
+}
+
+size_t amy_i2s_write(const uint8_t *buffer, size_t nbytes) {
+ return teensy_i2s_send(buffer, nbytes);
}
#else
@@ -390,17 +433,4 @@ void amy_update() {
#endif
-
-void amy_live_start() {
- amy_global.running = 1;
- i2s_amy_init();
-}
-
-
-void amy_live_stop() {
- amy_global.running = 0;
- // Not sure we do anythign on mcus for stopping amy live
-}
-
-
#endif
diff --git a/src/libminiaudio-audio.c b/src/libminiaudio-audio.c
index a55038a..1668d89 100644
--- a/src/libminiaudio-audio.c
+++ b/src/libminiaudio-audio.c
@@ -29,7 +29,7 @@ extern SAMPLE ** fbl;
int16_t * leftover_buf;
uint16_t leftover_samples = 0;
-pthread_t amy_live_thread;
+//pthread_t amy_live_thread;
#ifdef __EMSCRIPTEN__
#include
@@ -49,8 +49,27 @@ void main_loop__em()
}
#endif
-void amy_update() {
- // does nothing on miniaudio
+// Semaphore for passing most recent audio buffer to amy_update.
+// It's the pointer that's volatile, not the data it points to.
+int16_t *volatile last_audio_buffer = NULL;
+
+void amy_update_tasks() {
+}
+
+void amy_platform_init() {
+}
+
+size_t amy_i2s_write(const uint8_t *buffer, size_t nbytes) {
+ return 0;
+}
+
+int16_t *amy_render_audio() {
+ // For miniaudio, we just return a semaphore buffer.
+ while (last_audio_buffer == NULL)
+ ;
+ int16_t *buf = last_audio_buffer;
+ last_audio_buffer = NULL;
+ return buf;
}
void amy_print_devices() {
@@ -99,12 +118,16 @@ static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput,
short int *peek = (short *)pInput;
// We lag a AMY block behind input here because our frame size is never a multiple of AMY block size
for(uint16_t frame=0;frame
void amy_print_devices();
-void amy_live_start();
-void amy_live_stop();
-#endif
\ No newline at end of file
+#endif
diff --git a/src/pico_support.cpp b/src/pico_support.cpp
index d907885..14e0a36 100644
--- a/src/pico_support.cpp
+++ b/src/pico_support.cpp
@@ -62,8 +62,8 @@ extern "C" {
#define PLL_PD1 4
#define PLL_PD2 2
#elif F_CPU == 150000000
-// bootup tone works, MIDI doesn't work. LRCK is 44.1 kHz. MCLK is sync'd
- #warning "MIDI doesn't work for F_CPU == 150 MHz. Use 133 or 176 instead."
+// works as long as CPU clock is modified *before* MIDI UART is initialized. LRCK is 44.1 kHz. MCLK is sync'd
+// If MIDI UART is initialized *before* sys_set_clock_pll in pico_setup_i2s, baud rates are wrong and MIDI doesn't work.
#define F_CPU_MOD_KHZ 158000
#define F_SAMP 44084
#define VCO_FREQ 948000000
diff --git a/src/pyamy.c b/src/pyamy.c
index ff409fa..5655326 100644
--- a/src/pyamy.c
+++ b/src/pyamy.c
@@ -19,21 +19,20 @@ static PyObject * send_wrapper(PyObject *self, PyObject *args) {
static PyObject * live_wrapper(PyObject *self, PyObject *args) {
+ amy_stop();
+ amy_config_t amy_config = amy_global.config;
int arg1 = -1; int arg2 = -1;
if(PyTuple_Size(args) == 2) {
PyArg_ParseTuple(args, "ii", &arg1, &arg2);
- amy_global.config.playback_device_id = arg1;
- amy_global.config.capture_device_id = arg2;
+ amy_config.playback_device_id = arg1;
+ amy_config.capture_device_id = arg2;
} else {
- amy_global.config.playback_device_id = -1;
- amy_global.config.capture_device_id = -1;
+ amy_config.playback_device_id = -1;
+ amy_config.capture_device_id = -1;
}
- amy_live_start();
- return Py_None;
-}
-
-static PyObject * pause_wrapper(PyObject *self, PyObject *args) {
- amy_live_stop();
+ amy_config.audio = AMY_AUDIO_IS_MINIAUDIO;
+ amy_config.features.audio_in = 1; // Needed to make miniaudio run
+ amy_start(amy_config); // initializes amy
return Py_None;
}
@@ -43,14 +42,12 @@ static PyObject * amystop_wrapper(PyObject *self, PyObject *args) {
}
static PyObject * amystart_wrapper(PyObject *self, PyObject *args) {
+ int default_synths = 0;
+ if(PyTuple_Size(args) == 1) {
+ PyArg_ParseTuple(args, "i", &default_synths);
+ }
amy_config_t amy_config = amy_default_config();
- amy_start(amy_config); // initializes amy
- return Py_None;
-}
-
-static PyObject * amystart_no_default_wrapper(PyObject *self, PyObject *args) {
- amy_config_t amy_config = amy_default_config();
- amy_config.features.default_synths = 0;
+ amy_config.features.default_synths = default_synths;
amy_start(amy_config); // initializes amy
return Py_None;
}
@@ -101,8 +98,6 @@ static PyMethodDef c_amyMethods[] = {
{"render_to_list", render_wrapper, METH_VARARGS, "Render audio"},
{"send_wire", send_wrapper, METH_VARARGS, "Send a message"},
{"live", live_wrapper, METH_VARARGS, "Live AMY"},
- {"pause", pause_wrapper, METH_VARARGS, "Pause AMY"},
- {"start_no_default", amystart_no_default_wrapper, METH_VARARGS, "Start AMY"},
{"start", amystart_wrapper, METH_VARARGS, "Start AMY"},
{"stop", amystop_wrapper, METH_VARARGS, "Stop AMY"},
{"config", config_wrapper, METH_VARARGS, "Return config"},
diff --git a/src/sequencer.c b/src/sequencer.c
index c86d5bd..8c9390b 100644
--- a/src/sequencer.c
+++ b/src/sequencer.c
@@ -199,6 +199,10 @@ void _sequencer_start() {
add_repeating_timer_us(-500, sequencer_timer_callback, NULL, &pico_sequencer_timer);
}
+void _sequencer_stop() {
+ cancel_repeating_timer(&pico_sequencer_timer);
+}
+
#elif defined _POSIX_THREADS
#include
diff --git a/src/teensy_support.cpp b/src/teensy_support.cpp
index 6455855..7771c65 100644
--- a/src/teensy_support.cpp
+++ b/src/teensy_support.cpp
@@ -54,13 +54,15 @@ extern "C" {
}
- void teensy_i2s_send(int16_t * samples) {
- for(int16_t i=0;i