[RELEASE] u22-v03

This version is submitted to U22 breau.
This commit is contained in:
2020-09-14 00:00:00 +00:00
parent 360595de37
commit 84c3a02b9a
357 changed files with 29223 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
add_library(jukebox
amp.c
beep.c
composite.c
decoder.c
delay.c
effect.c
format.c
lowpass.c
mixer.c
sound.c
)
target_link_libraries(jukebox
miniaudio
math
memory
)
if (BUILD_TESTING)
add_executable(jukebox-test test.c)
target_link_libraries(jukebox-test jukebox)
endif()

83
util/jukebox/amp.c Normal file
View File

@@ -0,0 +1,83 @@
#include "./amp.h"
#include <assert.h>
#include <stdatomic.h>
#include <stddef.h>
#include <stdint.h>
#include "util/math/algorithm.h"
#include "util/math/rational.h"
#include "./effect.h"
#include "./format.h"
/* FIXME: Whether this solution works fine,
depends on how long the operation is done. */
static void jukebox_amp_affect_(
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
assert(effect != NULL);
assert(pcm != NULL);
jukebox_amp_t* amp = (typeof(amp)) effect;
const uint64_t easedur = atomic_load(&amp->ease_duration);
const uint64_t elapsed = atomic_load(&amp->elapsed);
const float prev = atomic_load(&amp->prev_amount)/1000.0f;
const float next = atomic_load(&amp->next_amount)/1000.0f;
float* ptr = pcm->ptr;
for (int32_t i = 0; i < pcm->frames; ++i) {
float a = next;
if (elapsed < easedur) {
a = (prev-next) * (easedur-elapsed) / easedur + next;
}
for (int32_t j = 0; j < amp->format.channels; ++j) {
*(ptr++) *= a;
}
}
atomic_fetch_add(&amp->elapsed, pcm->frames);
}
void jukebox_amp_initialize(
jukebox_amp_t* amp, const jukebox_format_t* format) {
assert(amp != NULL);
assert(jukebox_format_valid(format));
*amp = (typeof(*amp)) {
.super = {
.vtable = {
.affect = jukebox_amp_affect_,
},
},
.format = *format,
};
}
void jukebox_amp_deinitialize(jukebox_amp_t* amp) {
assert(amp != NULL);
}
void jukebox_amp_change_volume(
jukebox_amp_t* amp, float amount, const rational_t* duration) {
assert(amp != NULL);
assert(MATH_FLOAT_VALID(amount));
assert(amount >= 0);
const float srate = amp->format.sample_rate;
atomic_store(&amp->ease_duration, 0);
atomic_store(&amp->elapsed, 0);
atomic_store(&amp->prev_amount, amp->next_amount);
rational_t dur = rational(1, 100);
if (rational_valid(duration)) rational_addeq(&dur, duration);
rational_normalize(&dur, srate);
atomic_store(&amp->next_amount, (uint16_t) (amount*1000));
atomic_store(&amp->ease_duration, dur.num);
}

38
util/jukebox/amp.h Normal file
View File

@@ -0,0 +1,38 @@
#pragma once
#include <stdatomic.h>
#include "util/math/rational.h"
#include "./effect.h"
#include "./format.h"
typedef struct {
jukebox_effect_t super;
jukebox_format_t format;
atomic_uint_least64_t elapsed;
atomic_uint_least64_t ease_duration;
atomic_uint_least16_t prev_amount; /* [1/1000] */
atomic_uint_least16_t next_amount; /* [1/1000] */
} jukebox_amp_t;
void
jukebox_amp_initialize(
jukebox_amp_t* amp,
const jukebox_format_t* format
);
void
jukebox_amp_deinitialize(
jukebox_amp_t* amp
);
void
jukebox_amp_change_volume(
jukebox_amp_t* amp,
float amount,
const rational_t* duration /* NULLABLE */
);

77
util/jukebox/beep.c Normal file
View File

@@ -0,0 +1,77 @@
#include "./beep.h"
#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <stddef.h>
#include "util/math/algorithm.h"
#include "util/math/constant.h"
#include "./effect.h"
#include "./format.h"
static void jukebox_beep_affect_(
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
assert(effect != NULL);
assert(pcm != NULL);
jukebox_beep_t* b = (typeof(b)) effect;
if (!atomic_load(&b->playing)) return;
const int32_t sample_rate = b->format.sample_rate;
const int32_t channels = b->format.channels;
const float omega = 2 * MATH_PI * b->hz;
float* ptr = pcm->ptr;
for (int32_t i = 0; i < pcm->frames; ++i) {
const float a = fmod(i*omega/sample_rate, 2*MATH_PI);
const float x = sin(a + b->phase) * b->amp;
for (int32_t j = 0; j < channels; ++j) {
*(ptr++) += x;
}
}
b->phase += pcm->frames*1.0f/sample_rate * omega;
b->phase = fmod(b->phase, 2*MATH_PI);
}
void jukebox_beep_initialize(jukebox_beep_t* beep, const jukebox_format_t* format) {
assert(beep != NULL);
assert(jukebox_format_valid(format));
*beep = (typeof(*beep)) {
.super = {
.vtable = {
.affect = jukebox_beep_affect_,
},
},
.format = *format,
};
}
void jukebox_beep_deinitialize(jukebox_beep_t* beep) {
assert(beep != NULL);
}
void jukebox_beep_play(jukebox_beep_t* beep, float hz, float amp) {
assert(beep != NULL);
assert(MATH_FLOAT_VALID(hz) && hz > 0);
assert(MATH_FLOAT_VALID(amp) && amp > 0);
if (beep->playing) return;
beep->hz = hz;
beep->amp = amp;
beep->phase = 0;
atomic_store(&beep->playing, true);
}
void jukebox_beep_stop(jukebox_beep_t* beep) {
assert(beep != NULL);
if (!beep->playing) return;
atomic_store(&beep->playing, false);
}

42
util/jukebox/beep.h Normal file
View File

@@ -0,0 +1,42 @@
#pragma once
#include <stdatomic.h>
#include "./effect.h"
#include "./format.h"
typedef struct {
jukebox_effect_t super;
jukebox_format_t format;
atomic_bool playing;
float hz;
float amp;
float phase;
} jukebox_beep_t;
void
jukebox_beep_initialize(
jukebox_beep_t* beep,
const jukebox_format_t* format
);
void
jukebox_beep_deinitialize(
jukebox_beep_t* beep
);
/* not thread-safe function */
void
jukebox_beep_play(
jukebox_beep_t* beep,
float hz,
float amp
);
/* not thread-safe function */
void
jukebox_beep_stop(
jukebox_beep_t* beep
);

113
util/jukebox/composite.c Normal file
View File

@@ -0,0 +1,113 @@
#include "./composite.h"
#include <assert.h>
#include <stdatomic.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include "util/math/algorithm.h"
#include "util/memory/memory.h"
#include "./effect.h"
#include "./format.h"
struct jukebox_composite_t {
jukebox_effect_t super;
jukebox_format_t format;
atomic_bool playing;
size_t effects_reserved;
size_t effects_length;
jukebox_effect_t* effects[1];
};
static void jukebox_composite_affect_(
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
assert(effect != NULL);
assert(pcm != NULL);
# define CHUNK_SIZE 256
jukebox_composite_t* c = (typeof(c)) effect;
if (!atomic_load(&c->playing)) return;
const int32_t channels = c->format.channels;
const size_t chunk_frames = CHUNK_SIZE / channels;
uint64_t read = 0;
while (read < (uint64_t) pcm->frames) {
float chunk[CHUNK_SIZE] = {0};
jukebox_effect_pcm_t chunk_pcm = {
.ptr = chunk,
.frames = MATH_MIN(chunk_frames, pcm->frames - read),
};
for (size_t i = 0; i < c->effects_length; ++i) {
jukebox_effect_affect(c->effects[i], &chunk_pcm);
}
const size_t len = chunk_pcm.frames * channels;
const float* src = chunk;
float* dst = pcm->ptr + read * channels;
for (uint64_t i = 0; i < len; ++i) {
*(dst++) += *(src++);
}
read += chunk_pcm.frames;
}
# undef CHUNK_SIZE
}
jukebox_composite_t* jukebox_composite_new(
const jukebox_format_t* format, size_t reserve) {
assert(jukebox_format_valid(format));
assert(reserve > 0);
jukebox_composite_t* compo =
memory_new(sizeof(*compo) + (reserve-1)*sizeof(compo->effects[0]));
*compo = (typeof(*compo)) {
.super = {
.vtable = {
.affect = jukebox_composite_affect_,
},
},
.format = *format,
.effects_reserved = reserve,
};
return compo;
}
void jukebox_composite_delete(jukebox_composite_t* compo) {
if (compo == NULL) return;
memory_delete(compo);
}
void jukebox_composite_add_effect(
jukebox_composite_t* compo, jukebox_effect_t* effect) {
assert(compo != NULL);
assert(effect != NULL);
assert(!atomic_load(&compo->playing));
if (compo->effects_length >= compo->effects_reserved) {
fprintf(stderr, "jukebox: composite effect overflow\n");
abort();
}
compo->effects[compo->effects_length++] = effect;
}
void jukebox_composite_play(jukebox_composite_t* compo) {
assert(compo != NULL);
atomic_store(&compo->playing, true);
}
void jukebox_composite_stop(jukebox_composite_t* compo) {
assert(compo != NULL);
atomic_store(&compo->playing, false);
}

36
util/jukebox/composite.h Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
#include <stddef.h>
#include "./effect.h"
#include "./format.h"
struct jukebox_composite_t;
typedef struct jukebox_composite_t jukebox_composite_t;
jukebox_composite_t* /* OWNERSHIP */
jukebox_composite_new(
const jukebox_format_t* format,
size_t reserve
);
void
jukebox_composite_delete(
jukebox_composite_t* compo /* OWNERSHIP */
);
void
jukebox_composite_add_effect(
jukebox_composite_t* compo, /* must be stopped */
jukebox_effect_t* effect /* must be alive while the comp is alive */
);
void
jukebox_composite_play(
jukebox_composite_t* compo
);
void
jukebox_composite_stop(
jukebox_composite_t* compo
);

217
util/jukebox/decoder.c Normal file
View File

@@ -0,0 +1,217 @@
#include "./decoder.h"
#include <assert.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "thirdparty/miniaudio/miniaudio.h"
#include "util/math/algorithm.h"
#include "util/math/rational.h"
#include "util/memory/memory.h"
#include "./effect.h"
#include "./format.h"
# define JUKEBOX_DECODER_AFFECTION_CHUNK_SIZE 256
struct jukebox_decoder_t {
jukebox_effect_t super;
jukebox_format_t format;
ma_decoder ma;
atomic_bool playing;
atomic_int_least64_t duration; /* negative = 0 */
atomic_uint_least64_t current; /* main thread doesn't modify when playing*/
bool loop;
bool request_seek;
};
static void jukebox_decoder_seek_(jukebox_decoder_t* dec, uint64_t f) {
assert(dec != NULL);
assert(atomic_load(&dec->playing));
if (ma_decoder_seek_to_pcm_frame(&dec->ma, f) != MA_SUCCESS) {
fprintf(stderr, "jukebox: failed to seek ma_decoder\n");
abort();
}
atomic_store(&dec->current, f);
dec->request_seek = false;
}
static void jukebox_decoder_affect_(
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
assert(effect != NULL);
assert(pcm != NULL);
jukebox_decoder_t* dec = (typeof(dec)) effect;
if (!atomic_load(&dec->playing)) return;
if (dec->request_seek) {
jukebox_decoder_seek_(dec, atomic_load(&dec->current));
}
const int32_t ch = dec->format.channels;
uint64_t read_frames = 0;
for (;;) {
const int64_t duration = atomic_load(&dec->duration);
const uint64_t requested_frames = pcm->frames - read_frames;
if (duration <= 0) {
atomic_store(&dec->playing, false);
break;
}
if (read_frames >= (uint64_t) pcm->frames) break;
uint64_t chunk_frames = JUKEBOX_DECODER_AFFECTION_CHUNK_SIZE / ch;
if (chunk_frames > (uint64_t) duration) chunk_frames = duration;
if (chunk_frames > requested_frames) chunk_frames = requested_frames;
float chunk[JUKEBOX_DECODER_AFFECTION_CHUNK_SIZE];
const uint64_t decoded_frames =
ma_decoder_read_pcm_frames(&dec->ma, chunk, chunk_frames);
if (decoded_frames != chunk_frames) {
jukebox_decoder_seek_(dec, 0);
}
assert(read_frames+decoded_frames <= (uint64_t) pcm->frames);
for (size_t i = 0; i < decoded_frames*ch; ++i) {
pcm->ptr[read_frames*ch+i] += chunk[i];
}
read_frames += decoded_frames;
atomic_fetch_add(&dec->duration, -decoded_frames);
atomic_fetch_add(&dec->current, decoded_frames);
}
}
static jukebox_decoder_t* jukebox_decoder_new_(
const jukebox_format_t* format) {
assert(jukebox_format_valid(format));
jukebox_decoder_t* decoder = memory_new(sizeof(*decoder));
*decoder = (typeof(*decoder)) {
.super = {
.vtable = {
.affect = jukebox_decoder_affect_,
},
},
.format = *format,
};
return decoder;
}
jukebox_decoder_t* jukebox_decoder_new_from_file(
const jukebox_format_t* format, const char* path) {
assert(jukebox_format_valid(format));
jukebox_decoder_t* decoder = jukebox_decoder_new_(format);
const ma_decoder_config cfg = ma_decoder_config_init(
ma_format_f32, decoder->format.channels, decoder->format.sample_rate);
if (ma_decoder_init_file(path, &cfg, &decoder->ma) != MA_SUCCESS) {
fprintf(stderr, "jukebox: invalid audio file '%s'\n", path);
abort();
}
return decoder;
}
jukebox_decoder_t* jukebox_decoder_new_from_memory_mp3(
const jukebox_format_t* format, const void* buf, size_t len) {
assert(jukebox_format_valid(format));
assert(buf != NULL || len == 0);
jukebox_decoder_t* decoder = jukebox_decoder_new_(format);
const ma_decoder_config cfg = ma_decoder_config_init(
ma_format_f32, decoder->format.channels, decoder->format.sample_rate);
if (ma_decoder_init_memory_mp3(buf, len, &cfg, &decoder->ma) != MA_SUCCESS) {
fprintf(stderr, "jukebox: invalid mp3 buffer\n");
abort();
}
return decoder;
}
void jukebox_decoder_delete(jukebox_decoder_t* decoder) {
if (decoder == NULL) return;
ma_decoder_uninit(&decoder->ma);
memory_delete(decoder);
}
void jukebox_decoder_play(
jukebox_decoder_t* decoder, const rational_t* st, bool loop) {
assert(decoder != NULL);
assert(rational_valid(st));
if (atomic_load(&decoder->playing)) return;
rational_t t = *st;
rational_normalize(&t, decoder->format.sample_rate);
atomic_store(&decoder->duration, INT64_MAX);
decoder->loop = loop;
decoder->request_seek = true;
atomic_store(&decoder->current, t.num);
atomic_store(&decoder->playing, true);
}
void jukebox_decoder_resume(jukebox_decoder_t* decoder, bool loop) {
assert(decoder != NULL);
if (atomic_load(&decoder->playing)) return;
atomic_store(&decoder->duration, INT64_MAX);
decoder->loop = loop;
atomic_store(&decoder->playing, true);
}
void jukebox_decoder_stop_after(
jukebox_decoder_t* decoder, const rational_t* dur) {
assert(decoder != NULL);
assert(rational_valid(dur));
if (!atomic_load(&decoder->playing)) return;
rational_t d = *dur;
rational_normalize(&d, decoder->format.sample_rate);
atomic_store(&decoder->duration, d.num);
}
void jukebox_decoder_get_seek_position(
const jukebox_decoder_t* decoder, rational_t* time) {
assert(decoder != NULL);
assert(time != NULL);
*time = (typeof(*time)) {
.num = atomic_load(&decoder->current),
.den = decoder->format.sample_rate,
};
}
void jukebox_decoder_get_duration(
const jukebox_decoder_t* decoder, rational_t* time) {
assert(decoder != NULL);
assert(time != NULL);
*time = rational(
ma_decoder_get_length_in_pcm_frames((ma_decoder*) &decoder->ma),
decoder->format.sample_rate);
}

71
util/jukebox/decoder.h Normal file
View File

@@ -0,0 +1,71 @@
#pragma once
#include <stddef.h>
#include "util/math/rational.h"
#include "./format.h"
struct jukebox_decoder_t;
typedef struct jukebox_decoder_t jukebox_decoder_t;
jukebox_decoder_t* /* OWNERSHIP */
jukebox_decoder_new_from_file(
const jukebox_format_t* format,
const char* path
);
jukebox_decoder_t* /* OWNERSHIP */
jukebox_decoder_new_from_memory_mp3(
const jukebox_format_t* format,
const void* buf,
size_t len
);
void
jukebox_decoder_delete(
jukebox_decoder_t* decoder /* OWNERSHIP */
);
void
jukebox_decoder_play(
jukebox_decoder_t* decoder,
const rational_t* st,
bool loop
);
void
jukebox_decoder_resume(
jukebox_decoder_t* decoder,
bool loop
);
void
jukebox_decoder_stop_after(
jukebox_decoder_t* decoder,
const rational_t* dur
);
void
jukebox_decoder_set_loop(
jukebox_decoder_t* decoder,
const rational_t* start, /* NULLABLE */
const rational_t* end /* NULLABLE */
);
void
jukebox_decoder_unset_loop(
jukebox_decoder_t* decoder
);
void
jukebox_decoder_get_seek_position(
const jukebox_decoder_t* decoder,
rational_t* time
);
void
jukebox_decoder_get_duration(
const jukebox_decoder_t* decoder,
rational_t* time
);

94
util/jukebox/delay.c Normal file
View File

@@ -0,0 +1,94 @@
#include "./delay.h"
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include "util/math/algorithm.h"
#include "util/math/rational.h"
#include "util/memory/memory.h"
#include "./effect.h"
#include "./format.h"
struct jukebox_delay_t {
jukebox_effect_t super;
jukebox_format_t format;
float source;
float feedback;
size_t length;
size_t offset;
size_t cursor;
float buffer[1];
};
static void jukebox_delay_affect_(
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
assert(effect != NULL);
assert(pcm != NULL);
jukebox_delay_t* d = (typeof(d)) effect;
const int32_t ch = d->format.channels;
const size_t pcmlen = pcm->frames*ch;
for (size_t i = 0; i < pcmlen; ++i) {
float* bufoff = &d->buffer[d->offset];
float* bufcur = &d->buffer[d->cursor];
float* pcmptr = &pcm->ptr[i];
*bufoff *= d->feedback;
*bufoff += *pcmptr * d->source;
*pcmptr += *bufcur;
if (MATH_ABS(*pcmptr) >= 1.0f) {
*pcmptr = MATH_SIGN(*pcmptr) * 0.99f;
}
if (++d->offset >= d->length) d->offset = 0;
if (++d->cursor >= d->length) d->cursor = 0;
}
}
jukebox_delay_t* jukebox_delay_new(
const jukebox_format_t* format,
const rational_t* duration,
float source_attenuation,
float feedback_attenuation) {
assert(jukebox_format_valid(format));
assert(rational_valid(duration));
assert(MATH_FLOAT_VALID(source_attenuation));
assert(MATH_FLOAT_VALID(feedback_attenuation));
rational_t dur_r = rational(format->channels*duration->num, duration->den);
rational_normalize(&dur_r, format->sample_rate);
const size_t length = dur_r.num;
jukebox_delay_t* d =
memory_new(sizeof(*d) + (length-1)*sizeof(d->buffer[0]));
*d = (typeof(*d)) {
.super = {
.vtable = {
.affect = jukebox_delay_affect_,
},
},
.format = *format,
.source = source_attenuation,
.feedback = feedback_attenuation,
.length = length,
.offset = dur_r.num,
.cursor = 0,
};
for (size_t i = 0; i < d->length; ++i) d->buffer[i] = 0;
return d;
}
void jukebox_delay_delete(jukebox_delay_t* delay) {
if (delay == NULL) return;
memory_delete(delay);
}

23
util/jukebox/delay.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <stddef.h>
#include "util/math/rational.h"
#include "./format.h"
struct jukebox_delay_t;
typedef struct jukebox_delay_t jukebox_delay_t;
jukebox_delay_t*
jukebox_delay_new(
const jukebox_format_t* format,
const rational_t* duration,
float source_attenuation,
float feedback_attenuation
);
void
jukebox_delay_delete(
jukebox_delay_t* delay
);

13
util/jukebox/effect.c Normal file
View File

@@ -0,0 +1,13 @@
#include "./effect.h"
#include <assert.h>
#include <stddef.h>
void jukebox_effect_affect(
jukebox_effect_t* snd, const jukebox_effect_pcm_t* pcm) {
assert(snd != NULL);
assert(pcm != NULL);
assert(snd->vtable.affect != NULL);
snd->vtable.affect(snd, pcm);
}

31
util/jukebox/effect.h Normal file
View File

@@ -0,0 +1,31 @@
#pragma once
#include <stdint.h>
#include "./format.h"
struct jukebox_effect_t;
typedef struct jukebox_effect_t jukebox_effect_t;
typedef struct {
float* ptr;
int32_t frames;
} jukebox_effect_pcm_t;
typedef struct {
void
(*affect)(
jukebox_effect_t* snd,
const jukebox_effect_pcm_t* pcm
);
} jukebox_effect_vtable_t;
struct jukebox_effect_t {
jukebox_effect_vtable_t vtable;
};
void
jukebox_effect_affect(
jukebox_effect_t* snd,
const jukebox_effect_pcm_t* pcm
);

9
util/jukebox/format.c Normal file
View File

@@ -0,0 +1,9 @@
#include "./format.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
bool jukebox_format_valid(const jukebox_format_t* format) {
return format != NULL && format->sample_rate > 0 && format->channels > 0;
}

14
util/jukebox/format.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
typedef struct {
int32_t sample_rate;
int32_t channels;
} jukebox_format_t;
bool
jukebox_format_valid(
const jukebox_format_t* format
);

57
util/jukebox/lowpass.c Normal file
View File

@@ -0,0 +1,57 @@
#include "./lowpass.h"
#include <assert.h>
#include <stdatomic.h>
#include <stddef.h>
#include "util/math/algorithm.h"
#include "./effect.h"
#include "./format.h"
static void jukebox_lowpass_affect_(
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
assert(effect != NULL);
assert(pcm != NULL);
jukebox_lowpass_t* lp = (typeof(lp)) effect;
const size_t ch = lp->format.channels;
const float f = atomic_load(&lp->factor) * JUKEBOX_LOWPASS_FACTOR_UNIT;
assert(ch <= JUKEBOX_LOWPASS_SUPPORT_MAX_CHANNELS);
const size_t pcmlen = pcm->frames * ch;
for (size_t i = 0; i < pcmlen; ++i) {
const float p = (ch+1 <= i? pcm->ptr[i-1-ch]: lp->prev[i]);
pcm->ptr[i] = (1.0f-f)*pcm->ptr[i] + f*p;
}
if (pcmlen < ch) return;
for (size_t i = 0; i < ch; ++i) {
lp->prev[i] = pcm->ptr[pcmlen-ch+i];
}
}
void jukebox_lowpass_initialize(
jukebox_lowpass_t* lowpass, const jukebox_format_t* format, float factor) {
assert(lowpass != NULL);
assert(jukebox_format_valid(format));
assert(MATH_FLOAT_VALID(factor));
*lowpass = (typeof(*lowpass)) {
.super = {
.vtable = {
.affect = jukebox_lowpass_affect_,
},
},
.format = *format,
.factor = factor*10000,
.prev = {0},
};
}
void jukebox_lowpass_deinitialize(jukebox_lowpass_t* lowpass) {
assert(lowpass != NULL);
}

31
util/jukebox/lowpass.h Normal file
View File

@@ -0,0 +1,31 @@
#pragma once
#include <stdatomic.h>
#include "./effect.h"
#include "./format.h"
#define JUKEBOX_LOWPASS_SUPPORT_MAX_CHANNELS 16
#define JUKEBOX_LOWPASS_FACTOR_UNIT (1.0f/10000)
typedef struct {
jukebox_effect_t super;
jukebox_format_t format;
atomic_uint_least32_t factor;
float prev[JUKEBOX_LOWPASS_SUPPORT_MAX_CHANNELS];
} jukebox_lowpass_t;
void
jukebox_lowpass_initialize(
jukebox_lowpass_t* lowpass,
const jukebox_format_t* format,
float factor
);
void
jukebox_lowpass_deinitialize(
jukebox_lowpass_t* lowpass
);

98
util/jukebox/mixer.c Normal file
View File

@@ -0,0 +1,98 @@
#include "./mixer.h"
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include "thirdparty/miniaudio/miniaudio.h"
#include "util/container/array.h"
#include "util/memory/memory.h"
#include "./effect.h"
struct jukebox_mixer_t {
ma_device device;
jukebox_format_t format;
size_t effects_reserved;
size_t effects_length;
jukebox_effect_t* effects[1];
};
static void jukebox_mixer_device_callback_(
ma_device* device, void* out, const void* in, ma_uint32 frames) {
assert(device != NULL);
assert(out != NULL);
assert(frames > 0);
jukebox_mixer_t* mixer = (typeof(mixer)) device->pUserData;
assert(mixer != NULL);
const jukebox_effect_pcm_t pcm = {
.ptr = out,
.frames = frames,
};
for (size_t i = 0; i < mixer->effects_length; ++i) {
jukebox_effect_affect(mixer->effects[i], &pcm);
}
(void) in;
}
jukebox_mixer_t* jukebox_mixer_new(
const jukebox_format_t* format, size_t reserve) {
assert(jukebox_format_valid(format));
assert(reserve > 0);
jukebox_mixer_t* mixer = memory_new(
sizeof(*mixer) + (reserve-1)*sizeof(mixer->effects[0]));
*mixer = (typeof(*mixer)) {
.format = *format,
.effects_reserved = reserve,
};
ma_device_config config = ma_device_config_init(ma_device_type_playback);
config.playback.format = ma_format_f32;
config.playback.channels = mixer->format.channels;
config.sampleRate = mixer->format.sample_rate;
config.dataCallback = jukebox_mixer_device_callback_;
config.pUserData = mixer;
if (ma_device_init(NULL, &config, &mixer->device) != MA_SUCCESS) {
fprintf(stderr, "failed to open audio device\n");
abort();
}
if (ma_device_start(&mixer->device) != MA_SUCCESS) {
fprintf(stderr, "failed to start audio device\n");
ma_device_uninit(&mixer->device);
abort();
}
return mixer;
}
void jukebox_mixer_delete(jukebox_mixer_t* mixer) {
if (mixer == NULL) return;
ma_device_uninit(&mixer->device);
memory_delete(mixer);
}
void jukebox_mixer_add_effect(
jukebox_mixer_t* mixer, jukebox_effect_t* effect) {
assert(mixer != NULL);
assert(effect != NULL);
ma_mutex_lock(&mixer->device.lock);
if (mixer->effects_length >= mixer->effects_reserved) {
fprintf(stderr, "mixer effects overflow\n");
abort();
}
mixer->effects[mixer->effects_length++] = effect;
ma_mutex_unlock(&mixer->device.lock);
}

26
util/jukebox/mixer.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include <stddef.h>
#include "./effect.h"
#include "./format.h"
struct jukebox_mixer_t;
typedef struct jukebox_mixer_t jukebox_mixer_t;
jukebox_mixer_t* /* OWNERSHIP */
jukebox_mixer_new(
const jukebox_format_t* format,
size_t reserve
);
void
jukebox_mixer_delete(
jukebox_mixer_t* mixer /* OWNERSHIP */
);
void
jukebox_mixer_add_effect(
jukebox_mixer_t* mixer,
jukebox_effect_t* effect /* must be alive while the mixer is alive */
);

180
util/jukebox/sound.c Normal file
View File

@@ -0,0 +1,180 @@
#include "./sound.h"
#include <assert.h>
#include <stdatomic.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include "thirdparty/miniaudio/miniaudio.h"
#include "util/math/rational.h"
#include "util/memory/memory.h"
#include "./effect.h"
#include "./format.h"
struct jukebox_sound_buffer_t {
jukebox_format_t format;
uint64_t frames;
float ptr[1];
};
struct jukebox_sound_t {
jukebox_effect_t super;
const jukebox_sound_buffer_t* buffer;
atomic_bool stop_all;
size_t length;
atomic_uint_least64_t frames[1];
};
static void jukebox_sound_affect_(
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
assert(effect != NULL);
assert(pcm != NULL);
jukebox_sound_t* s = (typeof(s)) effect;
if (atomic_load(&s->stop_all)) {
for (size_t i = 0; i < s->length; ++i) {
atomic_store(&s->frames[i], s->buffer->frames);
}
atomic_store(&s->stop_all, false);
return;
}
const int32_t ch = s->buffer->format.channels;
for (size_t i = 0; i < s->length; ++i) {
const uint64_t frame = atomic_load(&s->frames[i]);
if (frame >= s->buffer->frames) continue;
const float* src = s->buffer->ptr;
float* dst = pcm->ptr;
const float* src_ed = src + s->buffer->frames*ch;
const float* dst_ed = dst + pcm->frames*ch;
src += frame*ch;
while (src < src_ed && dst < dst_ed) {
*(dst++) += *(src++);
}
atomic_fetch_add(&s->frames[i], (src - s->buffer->ptr)/ch - frame);
}
}
static jukebox_sound_buffer_t* jukebox_sound_buffer_new_(
const jukebox_format_t* format, ma_decoder* decoder) {
assert(format != NULL);
assert(decoder != NULL);
const uint64_t frames = ma_decoder_get_length_in_pcm_frames(decoder);
const size_t length = frames * format->channels;
jukebox_sound_buffer_t* buf =
memory_new(sizeof(*buf) + (length-1)*sizeof(buf->ptr[0]));
*buf = (typeof(*buf)) {
.format = *format,
.frames = frames,
};
const uint64_t read_frames =
ma_decoder_read_pcm_frames(decoder, buf->ptr, frames);
/* usually read_frames should be equal to frames */
buf->frames = read_frames;
return buf;
}
jukebox_sound_buffer_t* jukebox_sound_buffer_new_from_file(
const jukebox_format_t* format, const char* path) {
assert(jukebox_format_valid(format));
const ma_decoder_config cfg = ma_decoder_config_init(
ma_format_f32, format->channels, format->sample_rate);
ma_decoder decoder;
if (ma_decoder_init_file(path, &cfg, &decoder) != MA_SUCCESS) {
fprintf(stderr, "jukebox: invalid audio file '%s'\n", path);
abort();
}
jukebox_sound_buffer_t* sndbuf = jukebox_sound_buffer_new_(format, &decoder);
ma_decoder_uninit(&decoder);
return sndbuf;
}
jukebox_sound_buffer_t* jukebox_sound_buffer_new_from_memory_mp3(
const jukebox_format_t* format, const void* buf, size_t len) {
assert(jukebox_format_valid(format));
assert(buf != NULL || len == 0);
const ma_decoder_config cfg = ma_decoder_config_init(
ma_format_f32, format->channels, format->sample_rate);
ma_decoder decoder;
if (ma_decoder_init_memory_mp3(buf, len, &cfg, &decoder) != MA_SUCCESS) {
fprintf(stderr, "jukebox: invalid mp3 buffer\n");
abort();
}
jukebox_sound_buffer_t* sndbuf = jukebox_sound_buffer_new_(format, &decoder);
ma_decoder_uninit(&decoder);
return sndbuf;
}
void jukebox_sound_buffer_delete(jukebox_sound_buffer_t* buf) {
if (buf == NULL) return;
memory_delete(buf);
}
jukebox_sound_t* jukebox_sound_new(
const jukebox_sound_buffer_t* buf, size_t max_concurrent) {
assert(buf != NULL);
assert(max_concurrent > 0);
jukebox_sound_t* sound =
memory_new(sizeof(*sound) + (max_concurrent-1)*sizeof(sound->frames[0]));
*sound = (typeof(*sound)) {
.super = {
.vtable = {
.affect = jukebox_sound_affect_,
},
},
.buffer = buf,
.length = max_concurrent,
};
for (size_t i = 0; i < sound->length; ++i) {
sound->frames[i] = sound->buffer->frames;
}
return sound;
}
void jukebox_sound_delete(jukebox_sound_t* sound) {
if (sound == NULL) return;
memory_delete(sound);
}
bool jukebox_sound_play(jukebox_sound_t* sound) {
assert(sound != NULL);
if (atomic_load(&sound->stop_all)) return false;
for (size_t i = 0; i < sound->length; ++i) {
if (atomic_load(&sound->frames[i]) >= sound->buffer->frames) {
atomic_store(&sound->frames[i], 0);
return true;
}
}
return false;
}
void jukebox_sound_stop_all(jukebox_sound_t* sound) {
assert(sound != NULL);
atomic_store(&sound->stop_all, true);
}

53
util/jukebox/sound.h Normal file
View File

@@ -0,0 +1,53 @@
#pragma once
#include <stdatomic.h>
#include <stddef.h>
#include "util/math/rational.h"
#include "./format.h"
struct jukebox_sound_buffer_t;
typedef struct jukebox_sound_buffer_t jukebox_sound_buffer_t;
struct jukebox_sound_t;
typedef struct jukebox_sound_t jukebox_sound_t;
jukebox_sound_buffer_t*
jukebox_sound_buffer_new_from_file(
const jukebox_format_t* format,
const char* path
);
jukebox_sound_buffer_t*
jukebox_sound_buffer_new_from_memory_mp3(
const jukebox_format_t* format,
const void* buf,
size_t len
);
void
jukebox_sound_buffer_delete(
jukebox_sound_buffer_t* buf
);
jukebox_sound_t*
jukebox_sound_new(
const jukebox_sound_buffer_t* buf,
size_t max_concurrent
);
void
jukebox_sound_delete(
jukebox_sound_t* sound
);
bool
jukebox_sound_play(
jukebox_sound_t* sound
);
void
jukebox_sound_stop_all(
jukebox_sound_t* sound
);

53
util/jukebox/test.c Normal file
View File

@@ -0,0 +1,53 @@
#undef NDEBUG
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include "util/math/rational.h"
#include "./delay.h"
#include "./effect.h"
#include "./lowpass.h"
#include "./mixer.h"
#include "./sound.h"
int main(int argc, char** argv) {
assert(argc == 2);
static const jukebox_format_t format = {
.sample_rate = 48000,
.channels = 2,
};
jukebox_sound_buffer_t* sndbuf =
jukebox_sound_buffer_new_from_file(&format, argv[1]);
jukebox_sound_t* snd = jukebox_sound_new(sndbuf, 16);
static const rational_t delay_dur = rational(1, 10);
jukebox_delay_t* delay = jukebox_delay_new(&format, &delay_dur, 0.1f, 0.5f);
jukebox_lowpass_t lowpass = {0};
jukebox_lowpass_initialize(&lowpass, &format, 0.01f);
jukebox_mixer_t* mixer = jukebox_mixer_new(&format, 16);
jukebox_mixer_add_effect(mixer, (jukebox_effect_t*) snd);
jukebox_mixer_add_effect(mixer, (jukebox_effect_t*) delay);
jukebox_mixer_add_effect(mixer, &lowpass.super);
for (size_t i = 0; i < 32; ++i) {
jukebox_sound_play(snd);
printf("press enter to continue...\n");
getchar();
}
jukebox_sound_stop_all(snd);
jukebox_mixer_delete(mixer);
jukebox_lowpass_deinitialize(&lowpass);
jukebox_delay_delete(delay);
jukebox_sound_delete(snd);
jukebox_sound_buffer_delete(sndbuf);
return EXIT_SUCCESS;
}