aboutsummaryrefslogtreecommitdiff
path: root/libopna/s98gen.c
diff options
context:
space:
mode:
authorTakamichi Horikawa <takamichiho@gmail.com>2016-11-26 20:57:57 +0900
committerTakamichi Horikawa <takamichiho@gmail.com>2016-11-26 20:57:57 +0900
commit6fd10cdacb5cbe47a4fc339c20a733d4a9a384a1 (patch)
treec7f479a70c350dca0b73d76078e46db41d9c4133 /libopna/s98gen.c
initial
Diffstat (limited to 'libopna/s98gen.c')
-rw-r--r--libopna/s98gen.c141
1 files changed, 141 insertions, 0 deletions
diff --git a/libopna/s98gen.c b/libopna/s98gen.c
new file mode 100644
index 0000000..cc2b529
--- /dev/null
+++ b/libopna/s98gen.c
@@ -0,0 +1,141 @@
+#include "s98gen.h"
+
+static uint32_t read32le(const void *dataptr, size_t offset) {
+ const uint8_t *data = (const uint8_t *)dataptr;
+ return ((uint32_t)data[offset+0]) | (((uint32_t)data[offset+1])<<8) |
+ (((uint32_t)data[offset+2])<<16) | (((uint32_t)data[offset+3])<<24);
+}
+
+bool s98gen_init(struct s98gen *s98, void *s98dataptr, size_t s98data_size) {
+ uint8_t *s98data = (uint8_t *)s98dataptr;
+ if (s98data_size < 0x20) {
+ // size of s98 header
+ return false;
+ }
+ if (s98data[0] != 'S' || s98data[1] != '9' ||
+ s98data[2] != '8') {
+ // file magic / version check
+ return false;
+ }
+ if ((s98data[3] != '1') && (s98data[3] != '3')) {
+ return false;
+ }
+ if (s98data[0xc] != 0) {
+ // COMPRESSING flag not zero
+ return false;
+ }
+ uint32_t clock = 7987200;
+ if ((s98data[3] == '3') && s98data[0x1c]) {
+ if (s98data_size < 0x30) {
+ // size of s98 header + device info
+ return false;
+ }
+ uint32_t devicetype = read32le(s98data, 0x20);
+ if (devicetype != 0x04 && devicetype != 0x02) {
+ // OPN / OPNA only
+ return false;
+ }
+ clock = read32le(s98data, 0x24);
+ // convert OPN clock to OPNA clock
+ if (devicetype == 2) clock *= 2;
+ }
+ s98->s98data = s98data;
+ s98->s98data_size = s98data_size;
+ s98->current_offset = read32le(s98data, 0x14);
+ s98->opnaclock = clock;
+ s98->samples_to_generate = 0;
+ s98->samples_to_generate_frac = 0;
+ s98->timer_numerator = read32le(s98data, 0x04);
+ s98->timer_denominator = read32le(s98data, 0x08);
+ if (!s98->timer_numerator) s98->timer_numerator = 10;
+ if (!s98->timer_denominator) s98->timer_denominator = 1000;
+ opna_reset(&s98->opna);
+ return true;
+}
+
+// returns 0 when failed
+static size_t s98gen_getvv(struct s98gen *s98) {
+ unsigned shift = 0;
+ size_t value = 0;
+ do {
+ if (s98->s98data_size < (s98->current_offset+1)) return 0;
+ value |= (s98->s98data[s98->current_offset] & 0x7f) << ((shift++)*7);
+ } while (s98->s98data[s98->current_offset++] & 0x80);
+ return value + 2;
+}
+
+static void s98gen_set_samples_to_generate(struct s98gen *s98, size_t ticks) {
+ uint64_t s = s98->opnaclock * s98->timer_numerator * ticks;
+ s <<= 16;
+ s /= 144 * s98->timer_denominator;
+ s += s98->samples_to_generate_frac;
+ s98->samples_to_generate = s >> 16;
+ s98->samples_to_generate_frac = s & ((((size_t)1)<<16)-1);
+}
+
+static bool s98gen_parse_s98(struct s98gen *s98) {
+ for (;;) {
+ switch (s98->s98data[s98->current_offset]) {
+ case 0x00:
+ if (s98->s98data_size < (s98->current_offset + 3)) return false;
+ opna_writereg(&s98->opna,
+ s98->s98data[s98->current_offset+1],
+ s98->s98data[s98->current_offset+2]);
+ s98->current_offset += 3;
+ break;
+ case 0x01:
+ if (s98->s98data_size < (s98->current_offset + 3)) return false;
+ opna_writereg(&s98->opna,
+ s98->s98data[s98->current_offset+1] | 0x100,
+ s98->s98data[s98->current_offset+2]);
+ s98->current_offset += 3;
+ break;
+ case 0xfe:
+ if (s98->s98data_size < (s98->current_offset+1)) return false;
+ s98->current_offset++;
+ {
+ size_t vv = s98gen_getvv(s98);
+ if (!vv) return false;
+ s98gen_set_samples_to_generate(s98, vv);
+ }
+ return true;
+ case 0xff:
+ if (s98->s98data_size < (s98->current_offset + 1)) return false;
+ s98->current_offset++;
+ s98gen_set_samples_to_generate(s98, 1);
+ return true;
+ default:
+ return false;
+ }
+ }
+}
+
+bool s98gen_generate(struct s98gen *s98, int16_t *buf, size_t samples) {
+ for (size_t i = 0; i < samples; i++) {
+ buf[i*2+0] = 0;
+ buf[i*2+1] = 0;
+ }
+ if (samples <= s98->samples_to_generate) {
+ opna_mix(&s98->opna, buf, samples);
+ s98->samples_to_generate -= samples;
+ } else {
+ opna_mix(&s98->opna, buf, s98->samples_to_generate);
+ buf += s98->samples_to_generate * 2;
+ samples -= s98->samples_to_generate;
+ s98->samples_to_generate = 0;
+ while (samples) {
+ if (!s98gen_parse_s98(s98)) return false;
+ if (s98->samples_to_generate >= samples) {
+ opna_mix(&s98->opna, buf, samples);
+ s98->samples_to_generate -= samples;
+ samples = 0;
+ } else {
+ opna_mix(&s98->opna, buf, s98->samples_to_generate);
+ buf += s98->samples_to_generate*2;
+ samples -= s98->samples_to_generate;
+ s98->samples_to_generate = 0;
+ }
+ }
+ }
+ return true;
+}