-
Notifications
You must be signed in to change notification settings - Fork 26
Description
EDIT: OK I've just dramatically simplified my vult code and I can bring VOICE_COUNT up to 8 without shenanigans. So I guess I have indeed encountered a hardware limit. Unless I can make the I2S callback leaner by moving the Processor_process(processors[instance]); into the main function...
I am using Vult on a PicoADK. I have posted an issue there as well as it's kind of relevant to both sides :)
I am trying to implement a multi midi channel instrument. Following the recommendations at https://vult-dsp.github.io/vult/tutorials/patterns/ I have modified the firmware template to define a VOICE_COUNT make all occurrences of Processor_process_type arrays of size VOICE_COUNT. I am using the channel in the callbacks as an array index to call the relevant instance. I am not bound checking but I know that I am not sending to channels > 3. In fact I am only sending to channel 0 and 1.
Then in the __not_in_flash_func callback I am iterating through the voices, calling process and adding the return divided by the total amount of voices.
// We are filling the buffer with 32-bit samples (2 channels)
for (uint i = 0; i < buffer->max_sample_count; i++) {
fix16_t left_out = 0;
fix16_t right_out = 0;
for (uint8_t instance = 0; instance < VOICES_COUNT; instance++) {
// In case of the Vult Example, this is Processor_process(processors);
Processor_process(processors[instance]);
left_out += Processor_process_ret_0(processors[instance]) / VOICES_COUNT;
right_out += Processor_process_ret_1(processors[instance]) / VOICES_COUNT;
}
samples[i * 2 + 0] = fix16_to_int32(left_out); // LEFT
samples[i * 2 + 1] = fix16_to_int32(right_out); // RIGHT
}
This works with a single voice, I have fiddled with the for loop and I can get one or the other playing individually. But as soon as I set VOICE_COUNT to more than 1 I lose sound (and serial so I'm guessing the PicoADK is crashing). I have got it working with i < buffer->max_sample_count / VOICE_COUNT; which I tried, assuming that loop was taking too long. But obviously I get decimated sound.
Have I just hit processing limits or am I doing this the wrong way?
Below is the whole main.cpp in case it's useful.
I am a bit ashamed to show my Vult code as it's quite messy. But it is functional and I am basically generating a kick with Kick.do and a simple gated dual sawtooth with Sawcore.process.
Thank you for your time.
#include <stdio.h>
#include "project_config.h"
#if __has_include("bsp/board_api.h")
#include "bsp/board_api.h"
#else
#include "bsp/board.h"
#endif
#include "midi_input.h"
#include "midi_input.cpp"
#include "midi_input_usb.h"
#include "audio_subsystem.h"
#include "vult.h"
#include "picoadk_hw.h"
#include "FreeRTOS.h"
#include <task.h>
#include <queue.h>
#include "arduino_compat.h"
#define USE_DIN_MIDI 1
#define DEBUG_MIDI
#define VOICES_COUNT 4
audio_buffer_pool_t *ap;
Processor_process_type processors[VOICES_COUNT];
MIDIInputUSB usbmidi;
MIDIInput* midi_input = nullptr;
#ifdef __cplusplus
extern "C" {
//--------------------------------------------------------------------+
// CDC Callbacks
//--------------------------------------------------------------------+
// Invoked when cdc when line state changed e.g connected/disconnected
void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) {
(void) itf;
(void) rts;
}
// Invoked when CDC interface received data from host
void tud_cdc_rx_cb(uint8_t itf) {
(void) itf;
}
#endif
volatile long dsp_start;
volatile long dsp_end;
// This task prints the statistics about the running FreeRTOS tasks
// and how long it takes for the I2S callback to run.
void print_task(void *p) {
char ptrTaskList[2048];
while (1) {
vTaskList(ptrTaskList);
printf("Task\t\tState\tPrio\tStack\tNum\n%s\n", ptrTaskList);
printf("======================================================\n");
printf("B = Blocked, R = Ready, D = Deleted, S = Suspended\n");
printf("Milliseconds since boot: %d\n", xTaskGetTickCount() * portTICK_PERIOD_MS);
printf("dsp task took %d uS\n", dsp_end - dsp_start);
watchdog_update();
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
// MIDI callbacks
void note_on_callback(uint8_t note, uint8_t level, uint8_t channel) {
printf("channel: %d\n", channel);
if (level > 0) {
Processor_noteOn(processors[channel], note, level, channel);
}
else {
Processor_noteOff(processors[channel], note, channel);
}
}
void note_off_callback(uint8_t note, uint8_t level, uint8_t channel) {
Processor_noteOff(processors[channel], note, channel);
}
void cc_callback(uint8_t cc, uint8_t value, uint8_t channel) {
Processor_controlChange(processors[channel], cc, value, channel);
}
// This task processes the USB MIDI input
void usb_midi_task(void *pvParameters) {
usbmidi.setCCCallback(cc_callback);
usbmidi.setNoteOnCallback(note_on_callback);
usbmidi.setNoteOffCallback(note_off_callback);
while (1)
{
tud_task();
usbmidi.process();
}
}
// This task processes the UART MIDI input
void uart_midi_task(void *pvParameters) {
// printf("uart_midi_task started\n");
if (midi_input) {
// printf("midi_input object exists, setting callbacks\n");
midi_input->setCCCallback(cc_callback);
midi_input->setNoteOnCallback(note_on_callback);
midi_input->setNoteOffCallback(note_off_callback);
} else {
// printf("ERROR: midi_input is NULL!\n");
}
while (1) {
// Check if UART has data available
if (midi_input) {
if (uart_is_readable(uart0)) {
// printf("UART readable - processing MIDI\n");
midi_input->process();
}
}
// Small delay to prevent excessive CPU usage
vTaskDelay(pdMS_TO_TICKS(1));
}
}
int main(void) {
// initialize the hardware FIRST (sets clock to 402MHz)
picoadk_init();
// printf("Hardware initialized, creating MIDI input\n");
// NOW initialize MIDI input with correct clock frequency
midi_input = new MIDIInput(uart0); // Changed to UART0 to test
for (uint8_t instance = 0; instance < VOICES_COUNT; instance++) {
// Initialize Vult DSP context.
Processor_process_init(processors[instance]);
Processor_default_init(processors[instance]);
Processor_default(processors[instance]);
}
// Initialize the audio subsystem
ap = init_audio();
// Create FreeRTOS Tasks for USB MIDI and printing statistics
xTaskCreate(usb_midi_task, "USBMIDI", 4096, NULL, configMAX_PRIORITIES, NULL);
xTaskCreate(uart_midi_task, "UARTMIDI", 4096, NULL, configMAX_PRIORITIES, NULL);
// xTaskCreate(print_task, "TASKLIST", 1024, NULL, configMAX_PRIORITIES - 1, NULL);
// Start the scheduler.
vTaskStartScheduler();
}
// This is the I2S callback function. It is called when the I2S subsystem
// needs more audio data. It is called at a fixed rate of 48kHz.
// The audio data is stored in the audio_buffer_t struct.
void __not_in_flash_func(i2s_callback_func()) {
audio_buffer_t *buffer = take_audio_buffer(ap, false);
if (buffer == NULL) {
return;
}
int32_t *samples = (int32_t *)buffer->buffer->bytes;
dsp_start = to_us_since_boot(get_absolute_time());
// We are filling the buffer with 32-bit samples (2 channels)
for (uint i = 0; i < buffer->max_sample_count / VOICES_COUNT; i++) {
fix16_t left_out = 0;
fix16_t right_out = 0;
for (uint8_t instance = 0; instance < VOICES_COUNT; instance++) {
// In case of the Vult Example, this is Processor_process(processors);
Processor_process(processors[instance]);
left_out += Processor_process_ret_0(processors[instance]) / VOICES_COUNT;
right_out += Processor_process_ret_1(processors[instance]) / VOICES_COUNT;
}
samples[i * 2 + 0] = fix16_to_int32(left_out); // LEFT
samples[i * 2 + 1] = fix16_to_int32(right_out); // RIGHT
}
dsp_end = to_us_since_boot(get_absolute_time());
buffer->sample_count = buffer->max_sample_count;
give_audio_buffer(ap, buffer);
return;
}
#ifdef __cplusplus
}
#endif