summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorXANTRONIX Development2023-07-22 16:09:39 -0400
committerXANTRONIX Development2023-07-22 16:09:39 -0400
commitac7cc9194cdf85b676aea03a8998fa95d629115e (patch)
tree0780e3ccec09cdf35b3f8326a1e1b6216fc9eb69 /src
parent00d5cfc107dfadf6a3476e5e8679e1067e800cee (diff)
downloadxas-sola.tar.gz
xas-sola.tar.bz2
xas-sola.zip
Begin SOLA refactorsola
Diffstat (limited to 'src')
-rw-r--r--src/audio.c4
-rw-r--r--src/bank.c141
2 files changed, 145 insertions, 0 deletions
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; i<player->sola_overlap; i++) {
+ temp[i] = (float)(samples_prev[i] * i * (player->sola_overlap - i));
+ }
+
+ // Find best overlap offset within [0..SEEK_WINDOW]
+ for (i=0; i<player->sola_window; i++) {
+ size_t j;
+ float crosscorr = 0;
+
+ for (j=0; j<player->sola_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; i<player->sola_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;