k4/src/k3mix.c
2025-01-19 17:29:52 +02:00

396 lines
9.7 KiB
C

#include"k3mix.h"
#include<vorbis/vorbisfile.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<pthread.h>
#include<assert.h>
static uint32_t FinalSampleRate;
static uint8_t FinalChannels;
void k3MixInit(uint32_t sampleRate, uint8_t channels) {
FinalSampleRate = sampleRate;
FinalChannels = channels;
}
struct k3MixSource {
char *filepath;
OggVorbis_File vf;
int bitstream;
};
struct k3MixSource *k3MixSourceFile(const char *path) {
struct k3MixSource *ret = calloc(1, sizeof(*ret));
ret->filepath = strdup(path);
ov_fopen(path, &ret->vf);
ret->bitstream = 0;
return ret;
}
void k3MixSourceClose(struct k3MixSource *src) {
ov_clear(&src->vf);
free(src->filepath);
free(src);
}
__attribute__((optimize("Ofast"))) static intmax_t ogg_read(struct k3MixWave *this, size_t sampleCount, float *data) {
struct k3MixSource *od = this->data;
float **ni;
long totalRead = 0;
do {
long lastRead = ov_read_float(&od->vf, &ni, sampleCount, &od->bitstream);
if(this->loop && lastRead == 0) {
ov_pcm_seek(&od->vf, 0);
continue;
} else if(lastRead <= 0) {
this->end = true;
return totalRead;
}
for(long s = 0; s < lastRead; s++) {
for(long c = 0; c < this->channels; c++) {
*(data++) += ni[c][s] * this->volume;
}
}
totalRead += lastRead;
sampleCount -= lastRead;
} while(sampleCount);
return totalRead;
}
static void ogg_close(struct k3MixWave *this) {
if(this->refs-- == 1) {
k3MixSourceClose(this->data);
}
}
static void ogg_clone(struct k3MixWave *this, struct k3MixWave *new) {
new->refs = 1;
new->sampleRate = this->sampleRate;
new->channels = this->channels;
new->data = k3MixSourceFile(((struct k3MixSource*) this->data)->filepath);
new->read = ogg_read;
new->clone = ogg_clone;
new->close = ogg_close;
new->loop = this->loop;
new->dam = this->dam;
new->volume = this->volume;
}
struct k3MixWave *k3MixSimple(struct k3MixSource *src) {
vorbis_info *vi = ov_info(&src->vf, -1);
struct k3MixWave *ret = calloc(1, sizeof(*ret));
ret->refs = 1;
ret->sampleRate = vi->rate;
ret->channels = vi->channels;
ret->data = k3MixSourceFile(src->filepath);
ret->read = ogg_read;
ret->clone = ogg_clone;
ret->close = ogg_close;
ret->loop = 0;
ret->dam = 0;
ret->volume = 1;
return ret;
}
void k3MixSimpleSeek(struct k3MixWave *this, double t) {
struct k3MixSource *od = this->data;
ov_time_seek(&od->vf, t);
}
struct qdata {
size_t ref;
size_t count;
struct k3MixWave *children;
pthread_mutex_t damootecks;
};
static intmax_t queue_read(struct k3MixWave *this, size_t sampleCount, float *data) {
struct qdata *qdat = this->data;
pthread_mutex_lock(&qdat->damootecks);
size_t totalRead = 0;
while(sampleCount && qdat->count > 0) {
intmax_t i = qdat->children[0].read(&qdat->children[0], sampleCount, data);
if(i == 0) {
qdat->children[0].close(&qdat->children[0]);
memmove(&qdat->children[0], &qdat->children[1], sizeof(*qdat->children) * (qdat->count - 1));
qdat->count--;
} else {
data += this->channels * i;
totalRead += i;
sampleCount -= i;
}
}
pthread_mutex_unlock(&qdat->damootecks);
return totalRead;
}
static void queue_close(struct k3MixWave *this) {
if(this->refs-- == 1) {
struct qdata *qdat = this->data;
if(--qdat->ref == 0) {
for(size_t i = 0; i < qdat->count; i++) {
qdat->children[i].close(&qdat->children[i]);
}
free(qdat);
}
}
}
static void queue_clone(struct k3MixWave *this, struct k3MixWave *new) {
struct qdata *data = this->data;
data->ref++;
new->refs = 1;
new->sampleRate = this->sampleRate;
new->channels = this->channels;
new->data = data;
new->read = queue_read;
new->clone = queue_clone;
new->close = queue_close;
new->loop = this->loop;
new->dam = this->dam;
new->volume = this->volume;
}
struct k3MixWave *k3MixQueue() {
struct k3MixWave *ret = malloc(sizeof(*ret));
ret->refs = 1;
ret->sampleRate = FinalSampleRate;
ret->channels = FinalChannels;
struct qdata *data = ret->data = calloc(1, sizeof(struct qdata));
data->ref = 1;
pthread_mutex_init(&data->damootecks, NULL);
ret->read = queue_read;
ret->clone = queue_clone;
ret->close = queue_close;
ret->loop = 0;
ret->dam = 0;
ret->volume = 1;
return ret;
}
void k3MixQueueAdd(struct k3MixWave *this, struct k3MixWave *child) {
struct qdata *qdat = this->data;
pthread_mutex_lock(&qdat->damootecks);
qdat->children = realloc(qdat->children, sizeof(*qdat->children) * (qdat->count + 1));
child->clone(child, &qdat->children[qdat->count]);
qdat->count++;
pthread_mutex_unlock(&qdat->damootecks);
}
void k3MixQueueClear(struct k3MixWave *this) {
struct qdata *qdat = this->data;
pthread_mutex_lock(&qdat->damootecks);
for(size_t i = 0; i < qdat->count; i++) {
qdat->children[i].close(&qdat->children[i]);
}
qdat->count = 0;
free(qdat->children);
qdat->children = NULL;
pthread_mutex_unlock(&qdat->damootecks);
}
struct powermeasurementdata {
struct k3MixWave *child;
size_t historySize;
float *history;
};
static intmax_t power_measurement_read(struct k3MixWave *this, size_t sampleCount, float *data) {
struct powermeasurementdata *dat = this->data;
sampleCount = dat->child->read(dat->child, sampleCount, data);
if(sampleCount * FinalChannels < dat->historySize) {
memmove(dat->history, dat->history + sampleCount * FinalChannels, (dat->historySize - sampleCount * FinalChannels) * sizeof(*dat->history));
memcpy(dat->history + (dat->historySize - sampleCount * FinalChannels), data, sampleCount * FinalChannels * sizeof(*dat->history));
} else {
memcpy(dat->history, data + sampleCount * FinalChannels - dat->historySize, dat->historySize * sizeof(*dat->history));
}
this->end = dat->child->end;
return sampleCount;
}
static void power_measurement_close(struct k3MixWave *this) {
if(this->refs-- == 1) {
struct powermeasurementdata *dat = this->data;
dat->child->close(dat->child);
free(dat);
}
}
static void power_measurement_clone(struct k3MixWave *this, struct k3MixWave *new) {
struct powermeasurementdata *src = this->data;
struct powermeasurementdata *dat = malloc(sizeof(*dat));
dat->child = calloc(1, sizeof(*dat->child));
src->child->clone(src->child, dat->child);
dat->history = calloc(dat->historySize = src->historySize, sizeof(*dat->history));
memcpy(dat->history, src->history, sizeof(*dat->history) * dat->historySize);
new->refs = 1;
new->sampleRate = FinalSampleRate;
new->channels = FinalChannels;
new->data = dat;
new->read = power_measurement_read;
new->clone = power_measurement_clone;
new->close = power_measurement_close;
new->loop = this->loop;
new->dam = this->dam;
new->volume = this->volume;
}
float k3MixPowerMeasurementGetRMS(struct k3MixWave *this) {
struct powermeasurementdata *dat = this->data;
float sum = 0;
for(int i = 0; i < dat->historySize; i++) {
sum += pow(dat->history[i], 2);
}
return sqrtf(sum / dat->historySize);
}
struct k3MixWave *k3MixPowerMeasurement(struct k3MixWave *child) {
child->refs++;
struct powermeasurementdata *dat = malloc(sizeof(*dat));
dat->child = child;
dat->historySize = 8820;
dat->history = calloc(dat->historySize, sizeof(*dat->history));
struct k3MixWave *ret = malloc(sizeof(*ret));
ret->refs = 1;
ret->sampleRate = FinalSampleRate;
ret->channels = FinalChannels;
ret->data = dat;
ret->read = power_measurement_read;
ret->clone = power_measurement_clone;
ret->close = power_measurement_close;
ret->loop = 0;
ret->dam = 0;
ret->volume = 1;
return ret;
}
static size_t playingCount, playingCapacity;
static struct k3MixWave **playings;
void k3MixStop(struct k3MixWave *wav) {
for(size_t i = 0; i < playingCount; i++) {
if(playings[i] == wav) {
playings[i]->close(playings[i]);
memmove(&playings[i], &playings[i + 1], sizeof(*playings) * (playingCount - i - 1));
playingCount--;
}
}
}
struct k3MixWave *k3MixPlay(struct k3MixWave *wav) {
for(size_t i = 0; i < playingCount; i++) {
if(playings[i] == wav) {
return NULL;
}
}
k3MixPlayDirect(wav);
return wav;
}
void k3MixPlayDirect(struct k3MixWave *w) {
w->refs++;
if(playingCount == playingCapacity) {
playings = realloc(playings, sizeof(*playings) * (playingCapacity += 8));
}
playings[playingCount++] = w;
}
#include<xmmintrin.h>
__attribute__((optimize("Ofast"))) static void k3MixDoYourThang(size_t sampleCount, float *FinalData) {
memset(FinalData, 0, sampleCount * FinalChannels * sizeof(*FinalData));
for(size_t i = 0; i < playingCount;) {
intmax_t read = playings[i]->read(playings[i], sampleCount, FinalData);
if(playings[i]->end) {
k3MixStop(playings[i]);
} else i++;
}
for(size_t i = 0; i < sampleCount * FinalChannels; i += 4) {
// Compute tanh approximation x*(27+x*x)/(27+9*x*x)
float *ptr = FinalData + i;
__m128 x = _mm_loadu_ps(ptr);
__m128 xx = _mm_mul_ps(x, x);
__m128 numer = _mm_mul_ps(x, _mm_add_ps(_mm_set1_ps(27), xx));
__m128 denom = _mm_add_ps(_mm_set1_ps(27), _mm_mul_ps(_mm_set1_ps(9), xx));
_mm_storeu_ps(ptr, _mm_div_ps(numer, denom));
}
// The accuracy isn't worth the function call per sample
/*for(size_t i = 0; i < sampleCount * FinalChannels; i++) {
*FinalData = tanhf(*FinalData);
FinalData++;
}*/
}
#include<portaudio.h>
#include<xmmintrin.h>
static PaStream *paStream;
static int pa_callback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *ud) {
float *out = output;
static int done = 0;
if(done == 0) {
done = 1;
_mm_setcsr(_mm_getcsr() | 0x8040);
}
k3MixDoYourThang(frameCount, out);
return paContinue;
}
void k3MixPortAudio() {
Pa_Initialize();
Pa_OpenDefaultStream(&paStream, 0, FinalChannels, paFloat32, FinalSampleRate, 512, pa_callback, NULL);
Pa_StartStream(paStream);
}