diff options
author | Solomon Peachy <pizza@shaftnet.org> | 2020-09-26 17:19:07 -0400 |
---|---|---|
committer | Solomon Peachy <pizza@shaftnet.org> | 2020-10-09 11:39:25 -0400 |
commit | 4231c2c83f2b5331e3e38b10a308ee3752315f9c (patch) | |
tree | 1e14867e9c9f0d7b778e7c4c18103a7cbd491794 /lib/rbcodec/codecs/libayumi/ayumi_render.c | |
parent | 278522f8118bd2cfce065ec95f0a93ca53e3ca44 (diff) | |
download | rockbox-4231c2c83f2b5331e3e38b10a308ee3752315f9c.tar.gz rockbox-4231c2c83f2b5331e3e38b10a308ee3752315f9c.zip |
codecs: Add support for the 'VTX' ZX Spectrum chiptunes format.
This codec requires floating point.
Original author: Peter Sovietov
Ported to Rockbox: Roman Skylarov
Further integration and bugfixes: Solomon Peachy
Change-Id: I781ecd3592dfcdbbc694063334350342534f1d6c
Diffstat (limited to 'lib/rbcodec/codecs/libayumi/ayumi_render.c')
-rw-r--r-- | lib/rbcodec/codecs/libayumi/ayumi_render.c | 328 |
1 files changed, 328 insertions, 0 deletions
diff --git a/lib/rbcodec/codecs/libayumi/ayumi_render.c b/lib/rbcodec/codecs/libayumi/ayumi_render.c new file mode 100644 index 0000000000..9bf0204597 --- /dev/null +++ b/lib/rbcodec/codecs/libayumi/ayumi_render.c | |||
@@ -0,0 +1,328 @@ | |||
1 | #include "ayumi_render.h" | ||
2 | |||
3 | #include <stdlib.h> | ||
4 | #include <string.h> | ||
5 | #include <stdint.h> | ||
6 | |||
7 | #include "ayumi.h" | ||
8 | #include "lzh.h" | ||
9 | #include "codeclib.h" | ||
10 | |||
11 | ayumi_render_t ay; | ||
12 | |||
13 | /* default panning settings, 7 stereo types */ | ||
14 | static const double default_pan[7][3] = { | ||
15 | /* A, B, C */ | ||
16 | |||
17 | {0.50, 0.50, 0.50}, /* MONO */ | ||
18 | {0.10, 0.50, 0.90}, /* ABC */ | ||
19 | {0.10, 0.90, 0.50}, /* ACB */ | ||
20 | {0.50, 0.10, 0.90}, /* BAC */ | ||
21 | {0.90, 0.10, 0.50}, /* BCA */ | ||
22 | {0.50, 0.90, 0.10}, /* CAB */ | ||
23 | {0.90, 0.50, 0.10} /* CBA */ | ||
24 | }; | ||
25 | |||
26 | static const char *chiptype_name[3] = { | ||
27 | "AY-3-8910", | ||
28 | "YM2149", | ||
29 | "Unknown" | ||
30 | }; | ||
31 | |||
32 | static const char *layout_name[9] = { | ||
33 | "Mono", | ||
34 | "ABC Stereo", | ||
35 | "ACB Stereo", | ||
36 | "BAC Stereo", | ||
37 | "BCA Stereo", | ||
38 | "CAB Stereo", | ||
39 | "CBA Stereo", | ||
40 | "Custom", | ||
41 | "Unknown" | ||
42 | }; | ||
43 | |||
44 | /* reader */ | ||
45 | |||
46 | #define VTX_STRING_MAX 254 | ||
47 | |||
48 | typedef struct { | ||
49 | uchar *ptr; | ||
50 | uint size; | ||
51 | } reader_t; | ||
52 | |||
53 | reader_t reader; | ||
54 | |||
55 | void Reader_Init(void *pBlock) { | ||
56 | reader.ptr = (uchar *) pBlock; | ||
57 | reader.size = 0; | ||
58 | } | ||
59 | |||
60 | uint Reader_ReadByte(void) { | ||
61 | uint res; | ||
62 | res = *reader.ptr++; | ||
63 | reader.size += 1; | ||
64 | return res; | ||
65 | } | ||
66 | |||
67 | uint Reader_ReadWord(void) { | ||
68 | uint res; | ||
69 | res = *reader.ptr++; | ||
70 | res += *reader.ptr++ << 8; | ||
71 | reader.size += 2; | ||
72 | return res; | ||
73 | } | ||
74 | |||
75 | uint Reader_ReadDWord(void) { | ||
76 | uint res; | ||
77 | res = *reader.ptr++; | ||
78 | res += *reader.ptr++ << 8; | ||
79 | res += *reader.ptr++ << 16; | ||
80 | res += *reader.ptr++ << 24; | ||
81 | reader.size += 4; | ||
82 | return res; | ||
83 | } | ||
84 | |||
85 | char *Reader_ReadString(void) { | ||
86 | char *res; | ||
87 | if (reader.ptr == NULL) | ||
88 | return NULL; | ||
89 | int len = strlen((const char *)reader.ptr); | ||
90 | if (len > VTX_STRING_MAX) | ||
91 | return NULL; | ||
92 | res = reader.ptr; | ||
93 | reader.ptr += len + 1; | ||
94 | reader.size += len + 1; | ||
95 | return res; | ||
96 | } | ||
97 | |||
98 | uchar *Reader_GetPtr(void) { | ||
99 | return reader.ptr; | ||
100 | } | ||
101 | |||
102 | uint Reader_GetSize(void) { | ||
103 | return reader.size; | ||
104 | } | ||
105 | |||
106 | /* ayumi_render */ | ||
107 | |||
108 | static int AyumiRender_LoadInfo(void *pBlock, uint size) | ||
109 | { | ||
110 | if (size < 20) | ||
111 | return 0; | ||
112 | |||
113 | Reader_Init(pBlock); | ||
114 | |||
115 | uint hdr = Reader_ReadWord(); | ||
116 | |||
117 | if (hdr == 0x7961) | ||
118 | ay.info.chiptype = VTX_CHIP_AY; | ||
119 | else if (hdr == 0x6d79) | ||
120 | ay.info.chiptype = VTX_CHIP_YM; | ||
121 | else { | ||
122 | return 0; | ||
123 | } | ||
124 | |||
125 | ay.info.layout = (vtx_layout_t) | ||
126 | Reader_ReadByte(); | ||
127 | ay.info.loop = Reader_ReadWord(); | ||
128 | ay.info.chipfreq = Reader_ReadDWord(); | ||
129 | ay.info.playerfreq = Reader_ReadByte(); | ||
130 | ay.info.year = Reader_ReadWord(); | ||
131 | ay.data.regdata_size = Reader_ReadDWord(); | ||
132 | ay.info.frames = ay.data.regdata_size / 14; | ||
133 | ay.info.title = Reader_ReadString(); | ||
134 | ay.info.author = Reader_ReadString(); | ||
135 | ay.info.from = Reader_ReadString(); | ||
136 | ay.info.tracker = Reader_ReadString(); | ||
137 | ay.info.comment = Reader_ReadString(); | ||
138 | |||
139 | ay.data.lzhdata_size = size - Reader_GetSize(); | ||
140 | ay.data.lzhdata = (uchar *)codec_malloc(ay.data.lzhdata_size); | ||
141 | memcpy(ay.data.lzhdata, Reader_GetPtr(), ay.data.lzhdata_size); | ||
142 | |||
143 | return 1; | ||
144 | } | ||
145 | |||
146 | int AyumiRender_LoadFile(void *pBlock, uint size) | ||
147 | { | ||
148 | if (!AyumiRender_LoadInfo(pBlock, size)) | ||
149 | return 0; | ||
150 | |||
151 | ay.data.regdata = (uchar *)codec_malloc(ay.data.regdata_size); | ||
152 | if (ay.data.regdata == NULL) | ||
153 | return 0; | ||
154 | |||
155 | int bRet = LzUnpack(ay.data.lzhdata, ay.data.lzhdata_size, | ||
156 | ay.data.regdata, ay.data.regdata_size); | ||
157 | |||
158 | if (bRet) | ||
159 | return 0; | ||
160 | |||
161 | return 1; | ||
162 | } | ||
163 | |||
164 | const char *AyumiRender_GetChipTypeName(vtx_chiptype_t chiptype) | ||
165 | { | ||
166 | if (chiptype > VTX_CHIP_YM) | ||
167 | chiptype = (vtx_chiptype_t) (VTX_CHIP_YM + 1); | ||
168 | return chiptype_name[chiptype]; | ||
169 | } | ||
170 | |||
171 | const char *AyumiRender_GetLayoutName(vtx_layout_t layout) | ||
172 | { | ||
173 | if (layout > VTX_LAYOUT_CUSTOM) | ||
174 | layout = (vtx_layout_t) (VTX_LAYOUT_CUSTOM + 1); | ||
175 | return layout_name[layout]; | ||
176 | } | ||
177 | |||
178 | int AyumiRender_AyInit(vtx_chiptype_t chiptype, uint samplerate, | ||
179 | uint chipfreq, double playerfreq, uint dcfilter) | ||
180 | { | ||
181 | if (chiptype > VTX_CHIP_YM) | ||
182 | return 0; | ||
183 | if ((samplerate < 8000) || (samplerate > 768000)) | ||
184 | return 0; | ||
185 | if ((chipfreq < 1000000) || (chipfreq > 2000000)) | ||
186 | return 0; | ||
187 | if ((playerfreq < 1) || (playerfreq > 100)) | ||
188 | return 0; | ||
189 | |||
190 | ay.is_ym = (chiptype == VTX_CHIP_YM) ? 1 : 0; | ||
191 | ay.clock_rate = chipfreq; | ||
192 | ay.sr = samplerate; | ||
193 | |||
194 | ay.dc_filter_on = dcfilter ? 1 : 0; | ||
195 | |||
196 | ay.frame = 0; | ||
197 | ay.isr_counter = 1; | ||
198 | ay.isr_step = playerfreq / samplerate; | ||
199 | |||
200 | if (!ayumi_configure(&ay.ay, ay.is_ym, ay.clock_rate, ay.sr)) | ||
201 | return 0; | ||
202 | |||
203 | return 1; | ||
204 | } | ||
205 | |||
206 | int AyumiRender_SetLayout(vtx_layout_t layout, uint eqpower) | ||
207 | { | ||
208 | if (layout > VTX_LAYOUT_CUSTOM) | ||
209 | return 0; | ||
210 | ay.is_eqp = eqpower ? 1 : 0; | ||
211 | |||
212 | switch (layout) { | ||
213 | case VTX_LAYOUT_MONO: | ||
214 | case VTX_LAYOUT_ABC: | ||
215 | case VTX_LAYOUT_ACB: | ||
216 | case VTX_LAYOUT_BAC: | ||
217 | case VTX_LAYOUT_BCA: | ||
218 | case VTX_LAYOUT_CAB: | ||
219 | case VTX_LAYOUT_CBA: | ||
220 | for (int i = 0; i < 3; i++) | ||
221 | ay.pan[i] = default_pan[layout][i]; | ||
222 | break; | ||
223 | case VTX_LAYOUT_CUSTOM: | ||
224 | for (int i = 0; i < 3; i++) | ||
225 | ay.pan[i] = 0; // no custom layout | ||
226 | break; | ||
227 | default: | ||
228 | return 0; | ||
229 | } | ||
230 | |||
231 | for (int i = 0; i < 3; i++) | ||
232 | ayumi_set_pan(&ay.ay, i, ay.pan[i], ay.is_eqp); | ||
233 | |||
234 | return 1; | ||
235 | } | ||
236 | |||
237 | uint AyumiRender_GetPos(void) | ||
238 | { | ||
239 | return ay.frame; | ||
240 | } | ||
241 | |||
242 | uint AyumiRender_GetMaxPos(void) | ||
243 | { | ||
244 | return ay.info.frames; | ||
245 | } | ||
246 | |||
247 | static void AyumiRender_UpdateAyumiState(void) | ||
248 | { | ||
249 | int r[16]; | ||
250 | |||
251 | if (ay.frame < ay.info.frames) { | ||
252 | uchar *ptr = ay.data.regdata + ay.frame; | ||
253 | for (int n = 0; n < 14; n++) { | ||
254 | r[n] = *ptr; | ||
255 | ptr += ay.info.frames; | ||
256 | } | ||
257 | } else { | ||
258 | for (int n = 0; n < 14; n++) { | ||
259 | r[n] = 0; | ||
260 | } | ||
261 | } | ||
262 | |||
263 | ayumi_set_tone(&ay.ay, 0, (r[1] << 8) | r[0]); | ||
264 | ayumi_set_tone(&ay.ay, 1, (r[3] << 8) | r[2]); | ||
265 | ayumi_set_tone(&ay.ay, 2, (r[5] << 8) | r[4]); | ||
266 | ayumi_set_noise(&ay.ay, r[6]); | ||
267 | ayumi_set_mixer(&ay.ay, 0, r[7] & 1, (r[7] >> 3) & 1, r[8] >> 4); | ||
268 | ayumi_set_mixer(&ay.ay, 1, (r[7] >> 1) & 1, (r[7] >> 4) & 1, r[9] >> 4); | ||
269 | ayumi_set_mixer(&ay.ay, 2, (r[7] >> 2) & 1, (r[7] >> 5) & 1, r[10] >> 4); | ||
270 | ayumi_set_volume(&ay.ay, 0, r[8] & 0xf); | ||
271 | ayumi_set_volume(&ay.ay, 1, r[9] & 0xf); | ||
272 | ayumi_set_volume(&ay.ay, 2, r[10] & 0xf); | ||
273 | ayumi_set_envelope(&ay.ay, (r[12] << 8) | r[11]); | ||
274 | if (r[13] != 255) { | ||
275 | ayumi_set_envelope_shape(&ay.ay, r[13]); | ||
276 | } | ||
277 | } | ||
278 | |||
279 | int AyumiRender_Seek(ulong nSample) | ||
280 | { | ||
281 | ulong samples = 0; | ||
282 | |||
283 | ay.frame = 0; | ||
284 | ay.isr_counter = 1; | ||
285 | |||
286 | ayumi_configure(&ay.ay, ay.is_ym, ay.clock_rate, ay.sr); | ||
287 | |||
288 | for (int i = 0; i < 3; i++) | ||
289 | ayumi_set_pan(&ay.ay, i, ay.pan[i], ay.is_eqp); | ||
290 | |||
291 | while (samples < nSample) { | ||
292 | ay.isr_counter += ay.isr_step; | ||
293 | if (ay.isr_counter >= 1) { | ||
294 | ay.isr_counter -= 1; | ||
295 | AyumiRender_UpdateAyumiState(); | ||
296 | ay.frame += 1; | ||
297 | } | ||
298 | ayumi_seek(&ay.ay); | ||
299 | samples++; | ||
300 | } | ||
301 | |||
302 | return 1; | ||
303 | } | ||
304 | |||
305 | ulong AyumiRender_AySynth(void *pBuffer, ulong nSamples) | ||
306 | { | ||
307 | ulong samples = 0; | ||
308 | short *out = (int16_t *) pBuffer; | ||
309 | |||
310 | for (ulong i = 0; i < nSamples; i++) { | ||
311 | ay.isr_counter += ay.isr_step; | ||
312 | if (ay.isr_counter >= 1) { | ||
313 | ay.isr_counter -= 1; | ||
314 | AyumiRender_UpdateAyumiState(); | ||
315 | ay.frame += 1; | ||
316 | } | ||
317 | ayumi_process(&ay.ay); | ||
318 | if (ay.dc_filter_on) { | ||
319 | ayumi_remove_dc(&ay.ay); | ||
320 | } | ||
321 | out[0] = (int16_t)(ay.ay.left * 16383); | ||
322 | out[1] = (int16_t)(ay.ay.right * 16383); | ||
323 | out += 2; | ||
324 | samples++; | ||
325 | } | ||
326 | |||
327 | return samples; | ||
328 | } | ||