[RELEASE] u22-v03
This version is submitted to U22 breau.
This commit is contained in:
23
util/jukebox/CMakeLists.txt
Normal file
23
util/jukebox/CMakeLists.txt
Normal 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
83
util/jukebox/amp.c
Normal 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(&->ease_duration);
|
||||
const uint64_t elapsed = atomic_load(&->elapsed);
|
||||
|
||||
const float prev = atomic_load(&->prev_amount)/1000.0f;
|
||||
const float next = atomic_load(&->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(&->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(&->ease_duration, 0);
|
||||
|
||||
atomic_store(&->elapsed, 0);
|
||||
atomic_store(&->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(&->next_amount, (uint16_t) (amount*1000));
|
||||
atomic_store(&->ease_duration, dur.num);
|
||||
}
|
38
util/jukebox/amp.h
Normal file
38
util/jukebox/amp.h
Normal 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
77
util/jukebox/beep.c
Normal 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
42
util/jukebox/beep.h
Normal 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
113
util/jukebox/composite.c
Normal 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
36
util/jukebox/composite.h
Normal 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
217
util/jukebox/decoder.c
Normal 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
71
util/jukebox/decoder.h
Normal 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
94
util/jukebox/delay.c
Normal 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
23
util/jukebox/delay.h
Normal 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
13
util/jukebox/effect.c
Normal 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
31
util/jukebox/effect.h
Normal 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
9
util/jukebox/format.c
Normal 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
14
util/jukebox/format.h
Normal 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
57
util/jukebox/lowpass.c
Normal 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
31
util/jukebox/lowpass.h
Normal 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
98
util/jukebox/mixer.c
Normal 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
26
util/jukebox/mixer.h
Normal 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
180
util/jukebox/sound.c
Normal 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
53
util/jukebox/sound.h
Normal 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
53
util/jukebox/test.c
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user