summaryrefslogtreecommitdiff
path: root/src/modules/organya.c
diff options
context:
space:
mode:
authorzlago2025-02-11 13:36:04 +0100
committerzlago2025-02-11 13:36:04 +0100
commitc3579a3f8784ad49d081dc8347bbcadacc0829a2 (patch)
tree1a2a91daaac17d95484f092f81cf5c4d0ebb1269 /src/modules/organya.c
parent4efa9f571e84cda3741e524fb21a3a22cdbc13fb (diff)
add support for organya (.org) modules
by porting https://github.com/alula/organya-js/ to c
Diffstat (limited to 'src/modules/organya.c')
-rw-r--r--src/modules/organya.c413
1 files changed, 413 insertions, 0 deletions
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 <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include <stdio.h>
+
+#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;
+}