From c3579a3f8784ad49d081dc8347bbcadacc0829a2 Mon Sep 17 00:00:00 2001 From: zlago Date: Tue, 11 Feb 2025 13:36:04 +0100 Subject: add support for organya (.org) modules by porting https://github.com/alula/organya-js/ to c --- src/common/common.c | 2 + src/common/common.h | 1 + src/modules/organya.bin | Bin 0 -> 66152 bytes src/modules/organya.c | 413 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 416 insertions(+) create mode 100644 src/modules/organya.bin create mode 100644 src/modules/organya.c (limited to 'src') diff --git a/src/common/common.c b/src/common/common.c index 8628cd2..840df50 100644 --- a/src/common/common.c +++ b/src/common/common.c @@ -12,6 +12,8 @@ int (*file_ext(char *file))(struct blob *, struct userdata *) { return module_openmpt; } else if ((ext(".mid") && ext(".MID") && ext(".midi")) == 0) { return module_fluidsynth; + } else if ((ext(".org") && ext(".ORG")) == 0) { + return module_organya; } #undef ext return NULL; diff --git a/src/common/common.h b/src/common/common.h index 806212a..7089c17 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -5,3 +5,4 @@ struct blob load_file(char const *const name); int module_openmpt(struct blob *file, struct userdata *userdata); int module_fluidsynth(struct blob *file, struct userdata *userdata); +int module_organya(struct blob *file, struct userdata *userdata); diff --git a/src/modules/organya.bin b/src/modules/organya.bin new file mode 100644 index 0000000..3f68ac6 Binary files /dev/null and b/src/modules/organya.bin differ diff --git a/src/modules/organya.c b/src/modules/organya.c new file mode 100644 index 0000000..9d7bb67 --- /dev/null +++ b/src/modules/organya.c @@ -0,0 +1,413 @@ +#include +#include +#include +#include +#include + +#include + +#include "../include.h" + +// assumes little endian architechture, sorry +// C port of the 250 SLoC of JS organya player https://github.com/alula/organya-js + +struct song { + uint16_t wait; + uint8_t meas[2]; + int32_t start; + int32_t end; + struct instrument { + uint16_t freq; + uint8_t wave; + uint8_t pipi; + uint16_t notes; + } instruments[16]; + struct track { + int32_t pos; + uint8_t key; + uint8_t len; + uint8_t vol; + uint8_t pan; + } *tracks[16]; +}; + +struct organya { + struct song *song; + float t; + int playPos; + int samplesPerTick; + int samplesThisTick; + struct state { + float t; // may be float? + int key; + int frequency; + int octave; + float pan; + float vol; + int length; + int num_loops; + bool playing; + bool looping; + } state[16]; +}; + +void organya_synth(struct organya *self, float *const buf, size_t bufsize); + +static void organya_callback(void *userdata, unsigned char *stream, int const length) { + struct organya *user = userdata; + organya_synth(user, (float *) stream, length / sizeof (float) / 2); +} + +static signed char const waveTable[] = { +#embed "organya.bin" +}; +static struct drums { + int filePos; + int bits; + int channels; + int samples; +} *drums = NULL; + +void freefunc(void *ptr) { + struct organya *self = ptr; + for (int i = 0; i < 16; i++) { + free(self->song->tracks[i]); + } + free(self->song); + free(self); +} + +struct song *song_constructor(struct blob *file) { + char const *p = file->data; + + // header check + uint32_t const org1 = *(uint32_t *) p; p += sizeof (uint32_t); + uint16_t const orgVersion = *(uint16_t *) p; p += sizeof (uint16_t); + if (org1 != 0x2d67724f || orgVersion != 0x3230) { + return NULL; + } + + struct song *self = malloc(sizeof (struct song)); + if (self == NULL) { + return NULL; + } + + self->wait = *(uint16_t *) p; p += sizeof (uint16_t); + self->meas[0] = *(uint8_t *) p; p += sizeof (uint8_t); + self->meas[1] = *(uint8_t *) p; p += sizeof (uint8_t); + self->start = *(int32_t *) p; p += sizeof (int32_t); + self->end = *(int32_t *) p; p += sizeof (int32_t); + + for (int i = 0; i < 16; i++) { + self->instruments[i].freq = *(uint16_t *) p; p+= sizeof (uint16_t); + self->instruments[i].wave = *(uint8_t *) p; p += sizeof (uint8_t); + self->instruments[i].pipi = *(uint8_t *) p; p += sizeof (uint8_t); + self->instruments[i].notes = *(uint16_t *) p; p += sizeof (uint16_t); + } + + for (int i = 0; i < 16; i++) { + self->tracks[i] = NULL; + } + + for (int i = 0; i < 16; i++) { + size_t const length = self->instruments[i].notes; + struct track *track = malloc(sizeof (track) * length); + if (track == NULL && length != 0) { + //freefunc(self); // TODO: this looks really wrong + return NULL; + } + + for (unsigned j = 0; j < length; j++) { + track[j].pos = *(int32_t *) p; p += sizeof (int32_t); + } + + for (unsigned j = 0; j < length; j++) { + track[j].key = *(uint8_t *) p; p += sizeof (uint8_t); + } + + for (unsigned j = 0; j < length; j++) { + track[j].len = *(uint8_t *) p; p += sizeof (uint8_t); + } + + for (unsigned j = 0; j < length; j++) { + track[j].vol = *(uint8_t *) p; p += sizeof (uint8_t); + } + + for (unsigned j = 0; j < length; j++) { + track[j].pan = *(uint8_t *) p; p += sizeof (uint8_t); + } + + self->tracks[i] = track; + } + //p == ((char *) file->data) + file->size + return self; +} + +static int const freqTable[] = {261, 278, 294, 311, 329, 349, 371, 391, 414, 440, 466, 494}; +static int const panTable[] = {0, 43, 86, 129, 172, 215, 256, 297, 340, 383, 426, 469, 512}; +static int const advTable[] = {1, 1, 2, 2, 4, 8, 16, 32}; +static int const octTable[] = {32, 64, 64, 128, 128, 128, 128, 128}; + +int window_initOrganya(void); + +int module_organya(struct blob *file, struct userdata *user) { + if (drums == NULL) { + if (window_initOrganya()) { + return 1; + } + } + + struct organya *self = malloc(sizeof (struct organya)); + if (self == NULL) { + return 1; + } + self->song = song_constructor(file); + if (self->song == NULL) { + free(self); + return 1; + } + + self->t = 0; + self->playPos = 0; + self->samplesPerTick = (SAMPLE_RATE / 1000.0) * self->song->wait; + self->samplesThisTick = 0; + for (int i = 0; i < 16; i++) { + self->state[i] = (struct state) { + .t = 0, + .key = 0, + .frequency = 0, + .octave = 0, + .pan = 0.0, + .vol = 1.0, + .length = 0, + .num_loops = 0, + .playing = false, + .looping = false, + }; + } + + user->user = self; + user->callback = organya_callback; + user->freefunc = freefunc; + + return 0; +} + +void organya_update(struct organya *self); + +void organya_synth(struct organya *self, float *const buf, size_t bufsize) { + float *const leftBuffer = buf + 0; + float *const rightBuffer = buf + 1; + for (int sample = 0; sample < bufsize * 2; sample += 2) { + if (self->samplesThisTick == 0) { + organya_update(self); + } + + leftBuffer[sample] = 0; + rightBuffer[sample] = 0; + for (int i = 0; i < 16; i++) { + if (self->state[i].playing) { + int const samples = (i < 8)? 256: drums[i - 8].samples; + + self->state[i].t += ((float) self->state[i].frequency / (float) SAMPLE_RATE) * advTable[self->state[i].octave]; + + if (((int) self->state[i].t) >= samples) { + if (self->state[i].looping && self->state[i].num_loops != 1) { + self->state[i].t = fmod(self->state[i].t, samples); + if (self->state[i].num_loops != 1) { + self->state[i].num_loops -= 1; + } + } else { + self->state[i].t = 0; + self->state[i].playing = false; + continue; + } + } + + int const t = (int) self->state[i].t & ~(advTable[self->state[i].octave] - 1); + int pos = t % samples; + int pos2 = !self->state[i].looping && t == samples? + pos: + ((int) (self->state[i].t + advTable[self->state[i].octave]) & ~(advTable[self->state[i].octave] - 1)) % samples; + float const s1 = i < 8? + (waveTable[256 * self->song->instruments[i].wave + pos] / 256.0): + (((waveTable[drums[i - 8].filePos + pos] & 0xff) - 0x80) / 256.0); + float const s2 = i < 8? + (waveTable[256 * self->song->instruments[i].wave + pos2] / 256.0): + (((waveTable[drums[i - 8].filePos + pos2] & 0xff) - 0x80) / 256.0); + float const fract = (float) (self->state[i].t - pos) / (float) advTable[self->state[i].octave]; + + // lerp + float s = s1 + (s2 - s1) * fract; + + s *= pow(10, (float) ((self->state[i].vol - 255) * 8) / 2000.0); + + float const pan = (panTable[(int) self->state[i].pan] - 256) * 10; + float left = 1, right = 1; + + if (pan < 0) { + right = pow(10, (float) (pan / 2000.0)); + } else if (pan > 0) { + left = pow(10, (float) (-pan / 2000.0)); + } + + leftBuffer[sample] += s * left; + rightBuffer[sample] += s * right; + } + } + + if (++self->samplesThisTick == self->samplesPerTick) { + self->playPos += 1; + self->samplesThisTick = 0; + + if (self->playPos == self->song->end) { + self->playPos = self->song->start; + } + } + } +} + +struct track *find(struct track *array, int value, size_t members) { + for (int i = 0; i < members; i++) { + if (array[i].pos == value) { + return array + i; + } + } + return NULL; +} + +void organya_update(struct organya *self) { + for (int track = 0; track < 8; track++) { + size_t const length = self->song->instruments[track].notes; + struct track *const note = find(self->song->tracks[track], self->playPos, length); + if (note) { + if (note->key != 255) { + int const octave = note->key / 12; + int const key = note->key % 12; + + if (self->state[track].key == 255) { + self->state[track].key = note->key; + + self->state[track].frequency = freqTable[key] * octTable[octave] + (self->song->instruments[track].freq - 1000); + + if (self->song->instruments[track].pipi != 0 && !self->state[track].playing) { + self->state[track].num_loops = ((octave + 1) * 4); + } + } else if (self->state[track].key != note->key) { + self->state[track].key = note->key; + self->state[track].frequency = freqTable[key] * octTable[octave] + (self->song->instruments[track].freq - 1000); + } + + if (self->song->instruments[track].pipi != 0 && !self->state[track].playing) { + self->state[track].num_loops = ((octave + 1) * 4); + } + + self->state[track].octave = octave; + self->state[track].playing = true; + self->state[track].looping = true; + self->state[track].length = note->len; + } + + if (self->state[track].key != 255) { + if (note->vol != 255) { + self->state[track].vol = note->vol; + } + if (note->pan != 255) { + self->state[track].pan = note->pan; + } + } + } + + if (self->state[track].length == 0) { + if (self->state[track].key != 255) { + if (self->song->instruments[track].pipi == 0) { + self->state[track].looping = false; + } + + self->state[track].playing = false; + self->state[track].key = 255; + } + } else { + self->state[track].length--; + } + } + + for (int track = 8; track < 16; track++) { + size_t const length = self->song->instruments[track].notes; + struct track *const note = find(self->song->tracks[track], self->playPos, length); + if (!note) { + continue; + } + + if (note->key != 255) { + self->state[track].frequency = note->key * 800 + 100; + self->state[track].t = 0; + self->state[track].playing = true; + } + + if (note->vol != 255) { + self->state[track].vol = note->vol; + } + if (note->pan != 255) { + self->state[track].pan = note->pan; + } + } +} + +int window_initOrganya(void) { + size_t drumsLen = 0; + + for (size_t i = 256 * 100; i < sizeof (waveTable) - 4; i++) { + signed char const *const p = waveTable; + if (*(uint32_t *) (p + i) == 0x45564157) { + i += sizeof (uint32_t); + uint32_t const riffId = *(uint32_t *) (p + i); i += sizeof (uint32_t); + uint32_t const riffLen = *(uint32_t *) (p + i); i += sizeof (uint32_t); + if (riffId != 0x20746d66) { + fputs("invalid RIFF chunk id", stderr); + continue; + } + + size_t const startPos = i; + int const aFormat = *(uint16_t *) (p + i); i += sizeof (uint16_t); + if (aFormat != 1) { + fputs("invalid audio format", stderr); + i = startPos + riffLen; + continue; + } + + int const channels = *(uint16_t *) (p + i); i += sizeof (uint16_t); + if (channels != 1) { + fputs("only 1 channel files are supported", stderr); + i = startPos + riffLen; + continue; + } + + int const samples = *(uint32_t *) (p + i); i += sizeof (uint32_t) + 6; // skip rate + padding + int const bits = *(uint16_t *) (p + i); i += sizeof (uint16_t); + int const wavData = *(uint32_t *) (p + i); i += sizeof (uint32_t); + int const wavLen = *(uint32_t *) (p + i); i += sizeof (uint32_t); + + if (wavData != 0x61746164) { + i = startPos + riffLen; + continue; + } + + void *newDrums = realloc(drums, sizeof (struct drums) * (drumsLen + 1)); + if (newDrums == NULL) { + free(drums); + drums = NULL; + return 1; + } + drums = newDrums; + drums[drumsLen].filePos = i; + drums[drumsLen].bits = bits; + drums[drumsLen].channels = channels; + drums[drumsLen].samples = wavLen; + drumsLen++; + + i += wavLen; + } + } + return 0; +} -- cgit v1.2.3