.. and add a very basic audioeffects test, showing that it plausibly is working I had to address several build errors that occurred in the Unix build, mostly related to conversion from FP types to integral types (replaced by explicit casts) and by accidental mixing of regular & f-suffixed floating constants (replaced with the MICROPY_FLOAT_CONST macro) Particularly this change could use consideration: ```diff - self->max_echo_buffer_len = self->sample_rate / 1000.0f * max_delay_ms * (self->channel_count * sizeof(uint16_t)); // bytes + self->max_echo_buffer_len = (uint32_t)(self->sample_rate / 1000.0f * max_delay_ms) * (self->channel_count * sizeof(uint16_t)); // bytes ``` The buffer length is being calculated in floating point based on the millisecond delay & the sample rate. The result could then be a fractional number such as 529.2 for a 12ms delay at 44.1kHz. Multiplying a floating number by the size required for each echo buffer item (`(self->channel_count * sizeof(uint16_t))`) could yield a number of bytes that doesn't correspond to an integral number of buffer items. I grouped the float->int conversion so that it converts the number of echo buffer items to an integer and then multiplies by the size of the item.
534 lines
23 KiB
C
534 lines
23 KiB
C
// This file is part of the CircuitPython project: https://circuitpython.org
|
|
//
|
|
// SPDX-FileCopyrightText: Copyright (c) 2024 Mark Komus, Cooper Dalrymple
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
#include "shared-bindings/audiodelays/Echo.h"
|
|
|
|
#include <stdint.h>
|
|
#include "py/runtime.h"
|
|
|
|
void common_hal_audiodelays_echo_construct(audiodelays_echo_obj_t *self, uint32_t max_delay_ms,
|
|
mp_obj_t delay_ms, mp_obj_t decay, mp_obj_t mix,
|
|
uint32_t buffer_size, uint8_t bits_per_sample,
|
|
bool samples_signed, uint8_t channel_count, uint32_t sample_rate, bool freq_shift) {
|
|
|
|
// Set whether the echo shifts frequencies as the delay changes like a doppler effect
|
|
self->freq_shift = freq_shift;
|
|
|
|
// Basic settings every effect and audio sample has
|
|
// These are the effects values, not the source sample(s)
|
|
self->bits_per_sample = bits_per_sample; // Most common is 16, but 8 is also supported in many places
|
|
self->samples_signed = samples_signed; // Are the samples we provide signed (common is true)
|
|
self->channel_count = channel_count; // Channels can be 1 for mono or 2 for stereo
|
|
self->sample_rate = sample_rate; // Sample rate for the effect, this generally needs to match all audio objects
|
|
|
|
// To smooth things out as CircuitPython is doing other tasks most audio objects have a buffer
|
|
// A double buffer is set up here so the audio output can use DMA on buffer 1 while we
|
|
// write to and create buffer 2.
|
|
// This buffer is what is passed to the audio component that plays the effect.
|
|
// Samples are set sequentially. For stereo audio they are passed L/R/L/R/...
|
|
self->buffer_len = buffer_size; // in bytes
|
|
|
|
self->buffer[0] = m_malloc(self->buffer_len);
|
|
if (self->buffer[0] == NULL) {
|
|
common_hal_audiodelays_echo_deinit(self);
|
|
m_malloc_fail(self->buffer_len);
|
|
}
|
|
memset(self->buffer[0], 0, self->buffer_len);
|
|
|
|
self->buffer[1] = m_malloc(self->buffer_len);
|
|
if (self->buffer[1] == NULL) {
|
|
common_hal_audiodelays_echo_deinit(self);
|
|
m_malloc_fail(self->buffer_len);
|
|
}
|
|
memset(self->buffer[1], 0, self->buffer_len);
|
|
|
|
self->last_buf_idx = 1; // Which buffer to use first, toggle between 0 and 1
|
|
|
|
// Initialize other values most effects will need.
|
|
self->sample = NULL; // The current playing sample
|
|
self->sample_remaining_buffer = NULL; // Pointer to the start of the sample buffer we have not played
|
|
self->sample_buffer_length = 0; // How many samples do we have left to play (these may be 16 bit!)
|
|
self->loop = false; // When the sample is done do we loop to the start again or stop (e.g. in a wav file)
|
|
self->more_data = false; // Is there still more data to read from the sample or did we finish
|
|
|
|
// The below section sets up the echo effect's starting values. For a different effect this section will change
|
|
|
|
// If we did not receive a BlockInput we need to create a default float value
|
|
if (decay == MP_OBJ_NULL) {
|
|
decay = mp_obj_new_float(0.7);
|
|
}
|
|
synthio_block_assign_slot(decay, &self->decay, MP_QSTR_decay);
|
|
|
|
if (delay_ms == MP_OBJ_NULL) {
|
|
delay_ms = mp_obj_new_float(250.0);
|
|
}
|
|
synthio_block_assign_slot(delay_ms, &self->delay_ms, MP_QSTR_delay_ms);
|
|
|
|
if (mix == MP_OBJ_NULL) {
|
|
mix = mp_obj_new_float(0.5);
|
|
}
|
|
synthio_block_assign_slot(mix, &self->mix, MP_QSTR_mix);
|
|
|
|
// Many effects may need buffers of what was played this shows how it was done for the echo
|
|
// A maximum length buffer was created and then the current echo length can be dynamically changes
|
|
// without having to reallocate a large chunk of memory.
|
|
|
|
// Allocate the echo buffer for the max possible delay, echo is always 16-bit
|
|
self->max_delay_ms = max_delay_ms;
|
|
self->max_echo_buffer_len = (uint32_t)(self->sample_rate / 1000.0f * max_delay_ms) * (self->channel_count * sizeof(uint16_t)); // bytes
|
|
self->echo_buffer = m_malloc(self->max_echo_buffer_len);
|
|
if (self->echo_buffer == NULL) {
|
|
common_hal_audiodelays_echo_deinit(self);
|
|
m_malloc_fail(self->max_echo_buffer_len);
|
|
}
|
|
memset(self->echo_buffer, 0, self->max_echo_buffer_len);
|
|
|
|
// calculate everything needed for the current delay
|
|
mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms);
|
|
recalculate_delay(self, f_delay_ms);
|
|
|
|
// read is where we read previous echo from delay_ms ago to play back now
|
|
// write is where the store the latest playing sample to echo back later
|
|
self->echo_buffer_read_pos = self->buffer_len / sizeof(uint16_t);
|
|
self->echo_buffer_write_pos = 0;
|
|
|
|
// where we read the previous echo from delay_ms ago to play back now (for freq shift)
|
|
self->echo_buffer_left_pos = self->echo_buffer_right_pos = 0;
|
|
}
|
|
|
|
bool common_hal_audiodelays_echo_deinited(audiodelays_echo_obj_t *self) {
|
|
if (self->echo_buffer == NULL) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void common_hal_audiodelays_echo_deinit(audiodelays_echo_obj_t *self) {
|
|
if (common_hal_audiodelays_echo_deinited(self)) {
|
|
return;
|
|
}
|
|
self->echo_buffer = NULL;
|
|
self->buffer[0] = NULL;
|
|
self->buffer[1] = NULL;
|
|
}
|
|
|
|
mp_obj_t common_hal_audiodelays_echo_get_delay_ms(audiodelays_echo_obj_t *self) {
|
|
return self->delay_ms.obj;
|
|
}
|
|
|
|
void common_hal_audiodelays_echo_set_delay_ms(audiodelays_echo_obj_t *self, mp_obj_t delay_ms) {
|
|
synthio_block_assign_slot(delay_ms, &self->delay_ms, MP_QSTR_delay_ms);
|
|
|
|
mp_float_t f_delay_ms = synthio_block_slot_get(&self->delay_ms);
|
|
|
|
recalculate_delay(self, f_delay_ms);
|
|
}
|
|
|
|
void recalculate_delay(audiodelays_echo_obj_t *self, mp_float_t f_delay_ms) {
|
|
if (self->freq_shift) {
|
|
// Calculate the rate of iteration over the echo buffer with 8 sub-bits
|
|
self->echo_buffer_rate = (uint32_t)MAX(self->max_delay_ms / f_delay_ms * MICROPY_FLOAT_CONST(256.0), MICROPY_FLOAT_CONST(1.0));
|
|
self->echo_buffer_len = self->max_echo_buffer_len;
|
|
} else {
|
|
// Calculate the current echo buffer length in bytes
|
|
uint32_t new_echo_buffer_len = (uint32_t)(self->sample_rate / MICROPY_FLOAT_CONST(1000.0) * f_delay_ms) * (self->channel_count * sizeof(uint16_t));
|
|
|
|
// Check if our new echo is too long for our maximum buffer
|
|
if (new_echo_buffer_len > self->max_echo_buffer_len) {
|
|
return;
|
|
} else if (new_echo_buffer_len < 0.0) { // or too short!
|
|
return;
|
|
}
|
|
|
|
// If the echo buffer is larger then our audio buffer weird things happen
|
|
if (new_echo_buffer_len < self->buffer_len) {
|
|
return;
|
|
}
|
|
|
|
self->echo_buffer_len = new_echo_buffer_len;
|
|
|
|
// Clear the now unused part of the buffer or some weird artifacts appear
|
|
memset(self->echo_buffer + self->echo_buffer_len, 0, self->max_echo_buffer_len - self->echo_buffer_len);
|
|
}
|
|
|
|
self->current_delay_ms = (uint32_t)f_delay_ms;
|
|
}
|
|
|
|
mp_obj_t common_hal_audiodelays_echo_get_decay(audiodelays_echo_obj_t *self) {
|
|
return self->decay.obj;
|
|
}
|
|
|
|
void common_hal_audiodelays_echo_set_decay(audiodelays_echo_obj_t *self, mp_obj_t decay) {
|
|
synthio_block_assign_slot(decay, &self->decay, MP_QSTR_decay);
|
|
}
|
|
|
|
mp_obj_t common_hal_audiodelays_echo_get_mix(audiodelays_echo_obj_t *self) {
|
|
return self->mix.obj;
|
|
}
|
|
|
|
void common_hal_audiodelays_echo_set_mix(audiodelays_echo_obj_t *self, mp_obj_t arg) {
|
|
synthio_block_assign_slot(arg, &self->mix, MP_QSTR_mix);
|
|
}
|
|
|
|
bool common_hal_audiodelays_echo_get_freq_shift(audiodelays_echo_obj_t *self) {
|
|
return self->freq_shift;
|
|
}
|
|
|
|
void common_hal_audiodelays_echo_set_freq_shift(audiodelays_echo_obj_t *self, bool freq_shift) {
|
|
self->freq_shift = freq_shift;
|
|
uint32_t delay_ms = (uint32_t)synthio_block_slot_get(&self->delay_ms);
|
|
recalculate_delay(self, delay_ms);
|
|
}
|
|
|
|
uint32_t common_hal_audiodelays_echo_get_sample_rate(audiodelays_echo_obj_t *self) {
|
|
return self->sample_rate;
|
|
}
|
|
|
|
uint8_t common_hal_audiodelays_echo_get_channel_count(audiodelays_echo_obj_t *self) {
|
|
return self->channel_count;
|
|
}
|
|
|
|
uint8_t common_hal_audiodelays_echo_get_bits_per_sample(audiodelays_echo_obj_t *self) {
|
|
return self->bits_per_sample;
|
|
}
|
|
|
|
void audiodelays_echo_reset_buffer(audiodelays_echo_obj_t *self,
|
|
bool single_channel_output,
|
|
uint8_t channel) {
|
|
|
|
memset(self->buffer[0], 0, self->buffer_len);
|
|
memset(self->buffer[1], 0, self->buffer_len);
|
|
memset(self->echo_buffer, 0, self->max_echo_buffer_len);
|
|
}
|
|
|
|
bool common_hal_audiodelays_echo_get_playing(audiodelays_echo_obj_t *self) {
|
|
return self->sample != NULL;
|
|
}
|
|
|
|
void common_hal_audiodelays_echo_play(audiodelays_echo_obj_t *self, mp_obj_t sample, bool loop) {
|
|
// When a sample is to be played we must ensure the samples values matches what we expect
|
|
// Then we reset the sample and get the first buffer to play
|
|
// The get_buffer function will actually process that data
|
|
|
|
if (audiosample_sample_rate(sample) != self->sample_rate) {
|
|
mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_sample_rate);
|
|
}
|
|
if (audiosample_channel_count(sample) != self->channel_count) {
|
|
mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_channel_count);
|
|
}
|
|
if (audiosample_bits_per_sample(sample) != self->bits_per_sample) {
|
|
mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_bits_per_sample);
|
|
}
|
|
bool single_buffer;
|
|
bool samples_signed;
|
|
uint32_t max_buffer_length;
|
|
uint8_t spacing;
|
|
audiosample_get_buffer_structure(sample, false, &single_buffer, &samples_signed, &max_buffer_length, &spacing);
|
|
if (samples_signed != self->samples_signed) {
|
|
mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_signedness);
|
|
}
|
|
|
|
self->sample = sample;
|
|
self->loop = loop;
|
|
|
|
audiosample_reset_buffer(self->sample, false, 0);
|
|
audioio_get_buffer_result_t result = audiosample_get_buffer(self->sample, false, 0, (uint8_t **)&self->sample_remaining_buffer, &self->sample_buffer_length);
|
|
|
|
// Track remaining sample length in terms of bytes per sample
|
|
self->sample_buffer_length /= (self->bits_per_sample / 8);
|
|
// Store if we have more data in the sample to retrieve
|
|
self->more_data = result == GET_BUFFER_MORE_DATA;
|
|
|
|
return;
|
|
}
|
|
|
|
void common_hal_audiodelays_echo_stop(audiodelays_echo_obj_t *self) {
|
|
// When the sample is set to stop playing do any cleanup here
|
|
// For echo we clear the sample but the echo continues until the object reading our effect stops
|
|
self->sample = NULL;
|
|
return;
|
|
}
|
|
|
|
#define RANGE_LOW_16 (-28000)
|
|
#define RANGE_HIGH_16 (28000)
|
|
#define RANGE_SHIFT_16 (16)
|
|
#define RANGE_SCALE_16 (0xfffffff / (32768 * 2 - RANGE_HIGH_16)) // 2 for echo+sample
|
|
|
|
// dynamic range compression via a downward compressor with hard knee
|
|
//
|
|
// When the output value is within the range +-28000 (about 85% of full scale),
|
|
// it is unchanged. Otherwise, it undergoes a gain reduction so that the
|
|
// largest possible values, (+32768,-32767) * 2 (2 for echo and sample),
|
|
// still fit within the output range
|
|
//
|
|
// This produces a much louder overall volume with multiple voices, without
|
|
// much additional processing.
|
|
//
|
|
// https://en.wikipedia.org/wiki/Dynamic_range_compression
|
|
static
|
|
int16_t mix_down_sample(int32_t sample) {
|
|
if (sample < RANGE_LOW_16) {
|
|
sample = (((sample - RANGE_LOW_16) * RANGE_SCALE_16) >> RANGE_SHIFT_16) + RANGE_LOW_16;
|
|
} else if (sample > RANGE_HIGH_16) {
|
|
sample = (((sample - RANGE_HIGH_16) * RANGE_SCALE_16) >> RANGE_SHIFT_16) + RANGE_HIGH_16;
|
|
}
|
|
return sample;
|
|
}
|
|
|
|
audioio_get_buffer_result_t audiodelays_echo_get_buffer(audiodelays_echo_obj_t *self, bool single_channel_output, uint8_t channel,
|
|
uint8_t **buffer, uint32_t *buffer_length) {
|
|
|
|
if (!single_channel_output) {
|
|
channel = 0;
|
|
}
|
|
|
|
// get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required
|
|
mp_float_t mix = MIN(1.0, MAX(synthio_block_slot_get(&self->mix), 0.0));
|
|
mp_float_t decay = MIN(1.0, MAX(synthio_block_slot_get(&self->decay), 0.0));
|
|
|
|
uint32_t delay_ms = (uint32_t)synthio_block_slot_get(&self->delay_ms);
|
|
if (self->current_delay_ms != delay_ms) {
|
|
recalculate_delay(self, delay_ms);
|
|
}
|
|
|
|
// Switch our buffers to the other buffer
|
|
self->last_buf_idx = !self->last_buf_idx;
|
|
|
|
// If we are using 16 bit samples we need a 16 bit pointer, 8 bit needs an 8 bit pointer
|
|
int16_t *word_buffer = (int16_t *)self->buffer[self->last_buf_idx];
|
|
int8_t *hword_buffer = self->buffer[self->last_buf_idx];
|
|
uint32_t length = self->buffer_len / (self->bits_per_sample / 8);
|
|
|
|
// The echo buffer is always stored as a 16-bit value internally
|
|
int16_t *echo_buffer = (int16_t *)self->echo_buffer;
|
|
uint32_t echo_buf_len = self->echo_buffer_len / sizeof(uint16_t);
|
|
|
|
// Set our echo buffer position accounting for stereo
|
|
uint32_t echo_buffer_pos = 0;
|
|
if (self->freq_shift) {
|
|
echo_buffer_pos = self->echo_buffer_left_pos;
|
|
if (channel == 1) {
|
|
echo_buffer_pos = self->echo_buffer_right_pos;
|
|
}
|
|
}
|
|
|
|
// Loop over the entire length of our buffer to fill it, this may require several calls to get data from the sample
|
|
while (length != 0) {
|
|
// Check if there is no more sample to play, we will either load more data, reset the sample if loop is on or clear the sample
|
|
if (self->sample_buffer_length == 0) {
|
|
if (!self->more_data) { // The sample has indicated it has no more data to play
|
|
if (self->loop && self->sample) { // If we are supposed to loop reset the sample to the start
|
|
audiosample_reset_buffer(self->sample, false, 0);
|
|
} else { // If we were not supposed to loop the sample, stop playing it but we still need to play the echo
|
|
self->sample = NULL;
|
|
}
|
|
}
|
|
if (self->sample) {
|
|
// Load another sample buffer to play
|
|
audioio_get_buffer_result_t result = audiosample_get_buffer(self->sample, false, 0, (uint8_t **)&self->sample_remaining_buffer, &self->sample_buffer_length);
|
|
// Track length in terms of words.
|
|
self->sample_buffer_length /= (self->bits_per_sample / 8);
|
|
self->more_data = result == GET_BUFFER_MORE_DATA;
|
|
}
|
|
}
|
|
|
|
// If we have no sample keep the echo echoing
|
|
if (self->sample == NULL) {
|
|
if (mix <= 0.01) { // Mix of 0 is pure sample sound. We have no sample so no sound
|
|
if (self->samples_signed) {
|
|
memset(word_buffer, 0, length * (self->bits_per_sample / 8));
|
|
} else {
|
|
// For unsigned samples set to the middle which is "quiet"
|
|
if (MP_LIKELY(self->bits_per_sample == 16)) {
|
|
uint16_t *uword_buffer = (uint16_t *)word_buffer;
|
|
while (length--) {
|
|
*uword_buffer++ = 32768;
|
|
}
|
|
} else {
|
|
memset(hword_buffer, 128, length * (self->bits_per_sample / 8));
|
|
}
|
|
}
|
|
} else {
|
|
// Since we have no sample we can just iterate over the our entire remaining buffer and finish
|
|
for (uint32_t i = 0; i < length; i++) {
|
|
int16_t echo, word = 0;
|
|
uint32_t next_buffer_pos = 0;
|
|
|
|
if (self->freq_shift) {
|
|
echo = echo_buffer[echo_buffer_pos >> 8];
|
|
next_buffer_pos = echo_buffer_pos + self->echo_buffer_rate;
|
|
|
|
word = (int16_t)(echo * decay);
|
|
for (uint32_t j = echo_buffer_pos >> 8; j < next_buffer_pos >> 8; j++) {
|
|
echo_buffer[j % echo_buf_len] = word;
|
|
}
|
|
} else {
|
|
echo = echo_buffer[self->echo_buffer_read_pos++];
|
|
word = (int16_t)(echo * decay);
|
|
echo_buffer[self->echo_buffer_write_pos++] = word;
|
|
}
|
|
|
|
word = (int16_t)(echo * mix);
|
|
|
|
if (MP_LIKELY(self->bits_per_sample == 16)) {
|
|
word_buffer[i] = word;
|
|
if (!self->samples_signed) {
|
|
word_buffer[i] ^= 0x8000;
|
|
}
|
|
} else {
|
|
hword_buffer[i] = (int8_t)word;
|
|
if (!self->samples_signed) {
|
|
hword_buffer[i] ^= 0x80;
|
|
}
|
|
}
|
|
|
|
if (self->freq_shift) {
|
|
echo_buffer_pos = next_buffer_pos % (echo_buf_len << 8);
|
|
} else {
|
|
if (self->echo_buffer_read_pos >= echo_buf_len) {
|
|
self->echo_buffer_read_pos = 0;
|
|
}
|
|
if (self->echo_buffer_write_pos >= echo_buf_len) {
|
|
self->echo_buffer_write_pos = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
length = 0;
|
|
} else {
|
|
// we have a sample to play and echo
|
|
// Determine how many bytes we can process to our buffer, the less of the sample we have left and our buffer remaining
|
|
uint32_t n = MIN(self->sample_buffer_length, length);
|
|
|
|
int16_t *sample_src = (int16_t *)self->sample_remaining_buffer; // for 16-bit samples
|
|
int8_t *sample_hsrc = (int8_t *)self->sample_remaining_buffer; // for 8-bit samples
|
|
|
|
if (mix <= 0.01) { // if mix is zero pure sample only
|
|
for (uint32_t i = 0; i < n; i++) {
|
|
if (MP_LIKELY(self->bits_per_sample == 16)) {
|
|
word_buffer[i] = sample_src[i];
|
|
} else {
|
|
hword_buffer[i] = sample_hsrc[i];
|
|
}
|
|
}
|
|
} else {
|
|
for (uint32_t i = 0; i < n; i++) {
|
|
int32_t sample_word = 0;
|
|
if (MP_LIKELY(self->bits_per_sample == 16)) {
|
|
sample_word = sample_src[i];
|
|
} else {
|
|
if (self->samples_signed) {
|
|
sample_word = sample_hsrc[i];
|
|
} else {
|
|
// Be careful here changing from an 8 bit unsigned to signed into a 32-bit signed
|
|
sample_word = (int8_t)(((uint8_t)sample_hsrc[i]) ^ 0x80);
|
|
}
|
|
}
|
|
|
|
int32_t echo, word = 0;
|
|
uint32_t next_buffer_pos = 0;
|
|
if (self->freq_shift) {
|
|
echo = echo_buffer[echo_buffer_pos >> 8];
|
|
next_buffer_pos = echo_buffer_pos + self->echo_buffer_rate;
|
|
word = (int32_t)(echo * decay + sample_word);
|
|
} else {
|
|
echo = echo_buffer[self->echo_buffer_read_pos++];
|
|
word = (int32_t)(echo * decay + sample_word);
|
|
}
|
|
|
|
if (MP_LIKELY(self->bits_per_sample == 16)) {
|
|
word = mix_down_sample(word);
|
|
if (self->freq_shift) {
|
|
for (uint32_t j = echo_buffer_pos >> 8; j < next_buffer_pos >> 8; j++) {
|
|
echo_buffer[j % echo_buf_len] = (int16_t)word;
|
|
}
|
|
} else {
|
|
echo_buffer[self->echo_buffer_write_pos++] = (int16_t)word;
|
|
}
|
|
} else {
|
|
// Do not have mix_down for 8 bit so just hard cap samples into 1 byte
|
|
if (word > 127) {
|
|
word = 127;
|
|
} else if (word < -128) {
|
|
word = -128;
|
|
}
|
|
if (self->freq_shift) {
|
|
for (uint32_t j = echo_buffer_pos >> 8; j < next_buffer_pos >> 8; j++) {
|
|
echo_buffer[j % echo_buf_len] = (int8_t)word;
|
|
}
|
|
} else {
|
|
echo_buffer[self->echo_buffer_write_pos++] = (int8_t)word;
|
|
}
|
|
}
|
|
|
|
word = echo + sample_word;
|
|
|
|
if (MP_LIKELY(self->bits_per_sample == 16)) {
|
|
word_buffer[i] = (int16_t)((sample_word * (1.0 - mix)) + (word * mix));
|
|
if (!self->samples_signed) {
|
|
word_buffer[i] ^= 0x8000;
|
|
}
|
|
} else {
|
|
int8_t mixed = (int16_t)((sample_word * (1.0 - mix)) + (word * mix));
|
|
if (self->samples_signed) {
|
|
hword_buffer[i] = mixed;
|
|
} else {
|
|
hword_buffer[i] = (uint8_t)mixed ^ 0x80;
|
|
}
|
|
}
|
|
|
|
if (self->freq_shift) {
|
|
echo_buffer_pos = next_buffer_pos % (echo_buf_len << 8);
|
|
} else {
|
|
if (self->echo_buffer_read_pos >= echo_buf_len) {
|
|
self->echo_buffer_read_pos = 0;
|
|
}
|
|
if (self->echo_buffer_write_pos >= echo_buf_len) {
|
|
self->echo_buffer_write_pos = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the remaining length and the buffer positions based on how much we wrote into our buffer
|
|
length -= n;
|
|
word_buffer += n;
|
|
hword_buffer += n;
|
|
self->sample_remaining_buffer += (n * (self->bits_per_sample / 8));
|
|
self->sample_buffer_length -= n;
|
|
}
|
|
}
|
|
|
|
if (self->freq_shift) {
|
|
if (channel == 0) {
|
|
self->echo_buffer_left_pos = echo_buffer_pos;
|
|
} else if (channel == 1) {
|
|
self->echo_buffer_right_pos = echo_buffer_pos;
|
|
}
|
|
}
|
|
|
|
// Finally pass our buffer and length to the calling audio function
|
|
*buffer = (uint8_t *)self->buffer[self->last_buf_idx];
|
|
*buffer_length = self->buffer_len;
|
|
|
|
// Echo always returns more data but some effects may return GET_BUFFER_DONE or GET_BUFFER_ERROR (see audiocore/__init__.h)
|
|
return GET_BUFFER_MORE_DATA;
|
|
}
|
|
|
|
void audiodelays_echo_get_buffer_structure(audiodelays_echo_obj_t *self, bool single_channel_output,
|
|
bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing) {
|
|
|
|
// Return information about the effect's buffer (not the sample's)
|
|
// These are used by calling audio objects to determine how to handle the effect's buffer
|
|
*single_buffer = false;
|
|
*samples_signed = self->samples_signed;
|
|
*max_buffer_length = self->buffer_len;
|
|
if (single_channel_output) {
|
|
*spacing = self->channel_count;
|
|
} else {
|
|
*spacing = 1;
|
|
}
|
|
}
|