aboutsummaryrefslogtreecommitdiff
path: root/libopna/s98gen.c
blob: cc2b5299da4ab9110da6064472a3e61ad910689e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
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;
}