summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/xas/audio.h2
-rw-r--r--include/xas/bank.h34
-rw-r--r--src/audio.c4
-rw-r--r--src/bank.c141
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; 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;