diff options
| -rw-r--r-- | include/xas/audio.h | 2 | ||||
| -rw-r--r-- | include/xas/bank.h | 34 | ||||
| -rw-r--r-- | src/audio.c | 4 | ||||
| -rw-r--r-- | 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, @@ -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; | 
 
    