From ac7cc9194cdf85b676aea03a8998fa95d629115e Mon Sep 17 00:00:00 2001 From: XANTRONIX Development Date: Sat, 22 Jul 2023 16:09:39 -0400 Subject: Begin SOLA refactor --- include/xas/audio.h | 2 + include/xas/bank.h | 34 +++++++++++-- src/audio.c | 4 ++ src/bank.c | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 178 insertions(+), 3 deletions(-) diff --git a/include/xas/audio.h b/include/xas/audio.h index 76d38ce..4989333 100644 --- a/include/xas/audio.h +++ b/include/xas/audio.h @@ -73,6 +73,8 @@ void xas_audio_apply_gain(xas_audio_format format, size_t index, size_t count); +void *xas_audio_buffer_alloc(xas_audio_format format, size_t buffer_size); + xas_audio_stream *xas_audio_stream_new_sink(xas_audio_drain drain, xas_audio_cleanup cleanup, xas_audio_format format, diff --git a/include/xas/bank.h b/include/xas/bank.h index 3ff5994..92074f6 100644 --- a/include/xas/bank.h +++ b/include/xas/bank.h @@ -9,8 +9,22 @@ #define XAS_BANK_PLAYER_DEFAULT_GAIN 1.0f -#define XAS_BANK_PLAYER_NONE 0 -#define XAS_BANK_PLAYER_LOOP (1 << 0) +#define XAS_BANK_PLAYER_DURATION_MAX 10.0f + +#define XAS_BANK_PLAYER_NONE 0 +#define XAS_BANK_PLAYER_LOOP (1 << 0) +#define XAS_BANK_PLAYER_SCALE_TIME (1 << 1) +#define XAS_BANK_PLAYER_SCALE_PITCH (1 << 2) + +#define XAS_BANK_PLAYER_SOLA_SEQUENCE 100 +#define XAS_BANK_PLAYER_SOLA_OVERLAP 20 +#define XAS_BANK_PLAYER_SOLA_WINDOW 15 + +#define XAS_BANK_PLAYER_SOLA_FLAT_DURATION \ + (XAS_BANK_PLAYER_SOLA_SEQUENCE - 2 * (XAS_BANK_PLAYER_SOLA_OVERLAP)) + +#define XAS_BANK_PLAYER_SOLA_SEQUENCE_SKIP(scale) \ + ((XAS_BANK_PLAYER_SOLA_SEQUENCE - XAS_BANK_PLAYER_SOLA_OVERLAP) * scale) enum xas_bank_player_status { XAS_BANK_PLAYER_STOPPED, @@ -35,10 +49,20 @@ typedef struct _xas_bank_player { enum xas_bank_player_status status; int flags; - float gain; + float gain, + duration, + pitch; size_t entry, index; + + void *buffer; + size_t buffer_size; + + size_t sola_sequence, + sola_overlap, + sola_window, + sola_flat; } xas_bank_player; xas_bank *xas_bank_new(xas_audio_format format, @@ -66,6 +90,10 @@ void xas_bank_player_destroy(xas_bank_player *player); void xas_bank_player_set_gain(xas_bank_player *player, float gain); +void xas_bank_player_set_duration(xas_bank_player *player, float factor); + +void xas_bank_player_set_pitch(xas_bank_player *player, float factor); + int xas_bank_player_set_entry(xas_bank_player *player, size_t entry); int xas_bank_player_start(xas_bank_player *player); diff --git a/src/audio.c b/src/audio.c index 95c727e..2a986b9 100644 --- a/src/audio.c +++ b/src/audio.c @@ -78,6 +78,10 @@ void xas_audio_apply_gain(xas_audio_format format, } } +void *xas_audio_buffer_alloc(xas_audio_format format, size_t buffer_size) { + return malloc(format.channels * format.sample_size * buffer_size); +} + static xas_audio_stream *stream_new(enum xas_audio_stream_type type, void *callback, xas_audio_cleanup cleanup, diff --git a/src/bank.c b/src/bank.c index ef3cd37..22616a1 100644 --- a/src/bank.c +++ b/src/bank.c @@ -165,6 +165,28 @@ static xas_object_call_table call_table = { .destroy = (xas_object_destroy_callback)xas_bank_player_destroy }; +static int player_sola_init(xas_bank_player *player) { + size_t rate = player->bank->format.sample_rate, + buffer_size = (size_t)(player->bank->entry_size * XAS_BANK_PLAYER_DURATION_MAX), + total = buffer_size * player->bank->format.sample_size; + + player->sola_sequence = rate / (1000 / XAS_BANK_PLAYER_SOLA_SEQUENCE); + player->sola_overlap = rate / (1000 / XAS_BANK_PLAYER_SOLA_OVERLAP); + player->sola_window = rate / (1000 / XAS_BANK_PLAYER_SOLA_WINDOW); + player->sola_flat = rate / (1000 / XAS_BANK_PLAYER_SOLA_FLAT_DURATION); + + if ((player->buffer = malloc(total)) == NULL) { + goto error_malloc_buffer; + } + + player->buffer_size = buffer_size; + + return 0; + +error_malloc_buffer: + return -1; +} + xas_bank_player *xas_bank_player_new(xas_bank *bank) { xas_bank_player *player; @@ -181,8 +203,15 @@ xas_bank_player *xas_bank_player_new(xas_bank *bank) { player->entry = 0; player->index = 0; + if (player_sola_init(player) < 0) { + goto error_sola_init; + } + return player; +error_sola_init: + free(player); + error_malloc_player: return NULL; } @@ -224,7 +253,119 @@ void xas_bank_player_set_gain(xas_bank_player *player, float gain) { player->gain = gain; } +void xas_bank_player_set_duration(xas_bank_player *player, float factor) { + if (factor > XAS_BANK_PLAYER_DURATION_MAX) { + factor = XAS_BANK_PLAYER_DURATION_MAX; + } + + player->duration = factor; +} + +void xas_bank_player_set_pitch(xas_bank_player *player, float factor) { + player->pitch = factor; +} + +static size_t sola_best_overlap(xas_bank_player *player, + int16_t *samples_prev, + int16_t *samples_cur) { + size_t ret = 0; + + float bestcorr = -1e30f; + float temp[player->sola_overlap]; + + size_t i; + + /* + * Precalculate overlapping slopes with samples_prev + */ + for (i=0; isola_overlap; i++) { + temp[i] = (float)(samples_prev[i] * i * (player->sola_overlap - i)); + } + + // Find best overlap offset within [0..SEEK_WINDOW] + for (i=0; isola_window; i++) { + size_t j; + float crosscorr = 0; + + for (j=0; jsola_overlap; j++) { + crosscorr += (float)samples_cur[i + j] * temp[j]; + } + + if (crosscorr > bestcorr) { + // found new best offset candidate + bestcorr = crosscorr; + ret = i; + } + } + + return ret; +} + +// Overlap 'input_prev' with 'input_new' by sliding the amplitudes during +// OVERLAP samples. Store result to 'output'. +static void sola_overlap(xas_bank_player *player, + int16_t *output, + int16_t *input_prev, + int16_t *input_new) { + size_t i; + + for (i=0; isola_overlap; i++) { + output[i] = (input_prev[i] * (player->sola_overlap - i) + + input_new[i] * i) / player->sola_overlap; + } +} + +static size_t sola_apply(xas_bank_player *player, + int16_t *output, + int16_t *input, + size_t count) { + size_t ret = 0; + + int16_t *seq_offset = input; + int16_t *prev_offset; + + size_t sequence_skip = XAS_BANK_PLAYER_SOLA_SEQUENCE_SKIP(player->duration), + sample_size = player->bank->format.sample_size; + + while (count > sequence_skip + player->sola_window) { + // copy flat mid-sequence from current processing sequence to output + memcpy(output, seq_offset, player->sola_flat * sample_size); + + // calculate a pointer to overlap at end of the processing sequence + prev_offset = seq_offset + player->sola_flat; + + // update input pointer to theoretical next processing sequence begin + input += sequence_skip - player->sola_overlap; + + // seek actual best matching offset using cross-correlation + seq_offset = input + sola_best_overlap(player, prev_offset, input); + + // do overlapping between previous & new sequence, copy result to output + sola_overlap(player, + output + player->sola_flat, + prev_offset, + seq_offset); + + // Update input & sequence pointers by overlapping amount + seq_offset += player->sola_overlap; + input += player->sola_overlap; + + // Update output pointer & sample counters + output += player->sola_sequence - player->sola_overlap; + ret += player->sola_sequence - player->sola_overlap; + count -= sequence_skip; + } + + return ret; +} + int xas_bank_player_start(xas_bank_player *player) { + if (player->flags & XAS_BANK_PLAYER_SCALE_TIME) { + + } else if (player->flags & XAS_BANK_PLAYER_SCALE_PITCH) { + + } + player->status = XAS_BANK_PLAYER_PLAYING; player->index = 0; -- cgit v1.2.3