source: mainline/uspace/lib/trackmod/trackmod.c@ fd1b1ce

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since fd1b1ce was 7e69e0e, checked in by Jiri Svoboda <jiri@…>, 11 years ago

Introducing trackmod and modplay.

  • Property mode set to 100644
File size: 12.4 KB
Line 
1/*
2 * Copyright (c) 2014 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup trackmod
30 * @{
31 */
32/**
33 * @file Tracker module handling library.
34 */
35
36#include <assert.h>
37#include <errno.h>
38#include <stdio.h>
39#include <stdlib.h>
40
41#include "macros.h"
42#include "trackmod.h"
43
44/** Tunables */
45enum {
46 amp_factor = 16
47};
48
49/** Standard definitions set in stone */
50enum {
51 /** Base sample clock */
52 base_clock = 8363 * 428,
53 /** Maximum sample volume */
54 vol_max = 63,
55 /** Default TPR */
56 def_tpr = 6,
57 /** Default BPM */
58 def_bpm = 125
59};
60
61static size_t trackmod_get_next_ord_idx(trackmod_modplay_t *);
62
63/** Destroy sample.
64 *
65 * @param sample Sample
66 */
67static void trackmod_sample_destroy(trackmod_sample_t *sample)
68{
69 free(sample->data);
70}
71
72/** Destroy pattern.
73 *
74 * @param pattern Pattern
75 */
76static void trackmod_pattern_destroy(trackmod_pattern_t *pattern)
77{
78 free(pattern->data);
79}
80
81/** Create new empty module structure.
82 *
83 * @return New module
84 */
85trackmod_module_t *trackmod_module_new(void)
86{
87 return calloc(1, sizeof(trackmod_module_t));
88}
89
90/** Destroy module.
91 *
92 * @param module Module
93 */
94void trackmod_module_destroy(trackmod_module_t *module)
95{
96 size_t i;
97
98 /* Destroy samples */
99 if (module->sample != NULL) {
100 for (i = 0; i < module->samples; i++)
101 trackmod_sample_destroy(&module->sample[i]);
102 free(module->sample);
103 }
104
105 /* Destroy patterns */
106 if (module->pattern != NULL) {
107 for (i = 0; i < module->patterns; i++)
108 trackmod_pattern_destroy(&module->pattern[i]);
109 free(module->pattern);
110 }
111
112 free(module->ord_list);
113 free(module);
114}
115
116/** Return current pattern.
117 *
118 * @param modplay Module playback
119 * @return Pattern
120 */
121static trackmod_pattern_t *trackmod_cur_pattern(trackmod_modplay_t *modplay)
122{
123 unsigned pat_idx;
124
125 pat_idx = modplay->module->ord_list[modplay->ord_idx];
126 return &modplay->module->pattern[pat_idx];
127}
128
129/** Decode pattern cell.
130 *
131 * @param pattern Pattern
132 * @param row Row number
133 * @param channel Channel number
134 * @param cell Place to store decoded cell
135 */
136static void trackmod_pattern_get_cell(trackmod_pattern_t *pattern,
137 size_t row, size_t channel, trackmod_cell_t *cell)
138{
139 uint32_t code;
140
141 code = pattern->data[row * pattern->channels + channel];
142 cell->period = (code >> (4 * 4)) & 0xfff;
143 cell->sample = (((code >> (7 * 4)) & 0xf) << 4) |
144 ((code >> (3 * 4)) & 0xf);
145 cell->effect = code & 0xfff;
146}
147
148/** Process note (period, sample index)
149 *
150 * @param modplay Module playback
151 * @param i Channel number
152 * @param cell Cell
153 */
154static void trackmod_process_note(trackmod_modplay_t *modplay, size_t i,
155 trackmod_cell_t *cell)
156{
157 trackmod_chan_t *chan = &modplay->chan[i];
158 size_t smpidx;
159
160 smpidx = (cell->sample - 1) % modplay->module->samples;
161 chan->sample = &modplay->module->sample[smpidx];
162 chan->smp_pos = 0;
163 chan->lsmp = 0;
164 chan->period = cell->period;
165 chan->volume = modplay->chan[i].sample->def_vol;
166}
167
168/** Process Set volume effect.
169 *
170 * @param modplay Module playback
171 * @param chan Channel number
172 * @param param Effect parameter
173 */
174static void trackmod_effect_set_volume(trackmod_modplay_t *modplay, size_t chan,
175 uint8_t param)
176{
177 modplay->chan[chan].volume = param & vol_max;
178}
179
180/** Process Pattern break effect.
181 *
182 * @param modplay Module playback
183 * @param chan Channel number
184 * @param param Effect parameter
185 */
186static void trackmod_effect_pattern_break(trackmod_modplay_t *modplay,
187 size_t chan, uint8_t param)
188{
189 size_t next_idx;
190 trackmod_pattern_t *next_pat;
191
192 next_idx = trackmod_get_next_ord_idx(modplay);
193 next_pat = &modplay->module->pattern[next_idx];
194
195 modplay->pat_break = true;
196 modplay->pat_break_row = param % next_pat->rows;
197}
198
199/** Process Set speed effect.
200 *
201 * @param modplay Module playback
202 * @param chan Channel number
203 * @param param Effect parameter
204 */
205static void trackmod_effect_set_speed(trackmod_modplay_t *modplay, size_t chan,
206 uint8_t param)
207{
208 if (param > 0 && param < 32)
209 modplay->tpr = param;
210 else if (param > 0)
211 modplay->bpm = param;
212}
213
214/** Process effect.
215 *
216 * @param modplay Module playback
217 * @param chan Channel number
218 * @param cell Cell
219 */
220static void trackmod_process_effect(trackmod_modplay_t *modplay, size_t chan,
221 trackmod_cell_t *cell)
222{
223 uint8_t param8;
224
225 param8 = cell->effect & 0xff;
226
227 switch (cell->effect & 0xf00) {
228 case 0xc00:
229 trackmod_effect_set_volume(modplay, chan, param8);
230 break;
231 case 0xd00:
232 trackmod_effect_pattern_break(modplay, chan, param8);
233 break;
234 case 0xf00:
235 trackmod_effect_set_speed(modplay, chan, param8);
236 break;
237 default:
238 break;
239 }
240}
241
242/** Process pattern cell.
243 *
244 * @param modplay Module playback
245 * @param chan Channel number
246 * @param cell Cell
247 */
248static void trackmod_process_cell(trackmod_modplay_t *modplay, size_t chan,
249 trackmod_cell_t *cell)
250{
251 if (cell->period != 0 && cell->sample != 0)
252 trackmod_process_note(modplay, chan, cell);
253
254 trackmod_process_effect(modplay, chan, cell);
255}
256
257/** Process pattern row.
258 *
259 * @param modplay Module playback
260 */
261static void trackmod_process_row(trackmod_modplay_t *modplay)
262{
263 trackmod_pattern_t *pattern;
264 trackmod_cell_t cell;
265 size_t i;
266
267 pattern = trackmod_cur_pattern(modplay);
268
269 for (i = 0; i < modplay->module->channels; i++) {
270 trackmod_pattern_get_cell(pattern, modplay->row, i, &cell);
271 if (modplay->debug)
272 printf("%4d %02x %03x |", cell.period, cell.sample, cell.effect);
273 trackmod_process_cell(modplay, i, &cell);
274 }
275
276 if (modplay->debug)
277 printf("\n");
278}
279
280/** Get next order list index.
281 *
282 * @param modplay Module playback
283 * @return Next order list index
284 */
285static size_t trackmod_get_next_ord_idx(trackmod_modplay_t *modplay)
286{
287 size_t ord_idx;
288
289 ord_idx = modplay->ord_idx + 1;
290 if (ord_idx >= modplay->module->ord_list_len)
291 ord_idx = 0; /* XXX */
292
293 return ord_idx;
294}
295
296/** Advance to next pattern.
297 *
298 * @param modplay Module playback
299 */
300static void trackmod_next_pattern(trackmod_modplay_t *modplay)
301{
302 if (modplay->debug)
303 printf("Next pattern\n");
304
305 modplay->row = 0;
306 modplay->ord_idx = trackmod_get_next_ord_idx(modplay);
307
308 /* If we are doing a pattern break */
309 if (modplay->pat_break) {
310 modplay->row = modplay->pat_break_row;
311 modplay->pat_break = false;
312 }
313}
314
315/** Advance to next row.
316 *
317 * @param modplay Module playback
318 */
319static void trackmod_next_row(trackmod_modplay_t *modplay)
320{
321 trackmod_pattern_t *pattern;
322
323 pattern = trackmod_cur_pattern(modplay);
324
325 modplay->tick = 0;
326 ++modplay->row;
327 if (modplay->row >= pattern->rows || modplay->pat_break)
328 trackmod_next_pattern(modplay);
329
330 trackmod_process_row(modplay);
331}
332
333/** Advance to next tick.
334 *
335 * @param modplay Module playback
336 */
337static void trackmod_next_tick(trackmod_modplay_t *modplay)
338{
339 modplay->smp = 0;
340 ++modplay->tick;
341 if (modplay->tick >= modplay->tpr)
342 trackmod_next_row(modplay);
343}
344
345/** Create module playback object.
346 *
347 * @param module Module
348 * @param smp_freq Sampling frequency
349 * @param rmodplay Place to store pointer to module playback object
350 */
351int trackmod_modplay_create(trackmod_module_t *module,
352 unsigned smp_freq, trackmod_modplay_t **rmodplay)
353{
354 trackmod_modplay_t *modplay = NULL;
355
356 modplay = calloc(1, sizeof(trackmod_modplay_t));
357 if (modplay == NULL)
358 goto error;
359
360 modplay->module = module;
361 modplay->smp_freq = smp_freq;
362 modplay->frame_size = sizeof(int16_t);
363 modplay->ord_idx = 0;
364 modplay->row = 0;
365 modplay->tick = 0;
366 modplay->smp = 0;
367
368 modplay->tpr = def_tpr;
369 modplay->bpm = def_bpm;
370
371 modplay->chan = calloc(module->channels,
372 sizeof(trackmod_chan_t));
373 if (modplay->chan == NULL)
374 goto error;
375
376 trackmod_process_row(modplay);
377
378 *rmodplay = modplay;
379 return EOK;
380error:
381 if (modplay != NULL)
382 free(modplay->chan);
383 free(modplay);
384 return ENOMEM;
385}
386
387/** Destroy module playback object.
388 *
389 * @param modplay Module playback
390 */
391void trackmod_modplay_destroy(trackmod_modplay_t *modplay)
392{
393 free(modplay->chan);
394 free(modplay);
395}
396
397/** Get number of samples per tick.
398 *
399 * @param modplay Module playback
400 * @return Number of samples per tick
401 */
402static size_t samples_per_tick(trackmod_modplay_t *modplay)
403{
404 return modplay->smp_freq * 10 / 4 / modplay->bpm;
405}
406
407/** Get number of samples remaining in current tick.
408 *
409 * @param modplay Module playback
410 * @return Number of remaining samples in tick
411 */
412static size_t samples_remain_tick(trackmod_modplay_t *modplay)
413{
414 /* XXX Integer samples per tick is a simplification */
415 return samples_per_tick(modplay) - modplay->smp;
416}
417
418/** Advance sample position to next frame.
419 *
420 * @param chan Channel playback
421 */
422static void chan_smp_next_frame(trackmod_chan_t *chan)
423{
424 chan->lsmp = chan->sample->data[chan->smp_pos];
425 ++chan->smp_pos;
426
427 if (chan->sample->loop_len == 0) {
428 /* No looping */
429 if (chan->smp_pos >= chan->sample->length) {
430 chan->sample = NULL;
431 chan->smp_pos = 0;
432 }
433 } else {
434 /** Looping */
435 if (chan->smp_pos >= chan->sample->loop_start +
436 chan->sample->loop_len) {
437 chan->smp_pos = chan->sample->loop_start;
438 }
439 }
440}
441
442/** Render next sample on channel.
443 *
444 * @param modplay Module playback
445 * @param cidx Channel number
446 * @return Sample value
447 */
448static int trackmod_chan_next_sample(trackmod_modplay_t *modplay,
449 size_t cidx)
450{
451 int sl, sn;
452 int period, clk;
453 int s;
454
455 trackmod_chan_t *chan = &modplay->chan[cidx];
456
457 if (chan->sample == NULL)
458 return 0;
459
460 /*
461 * Linear interpolation. Note this is slightly simplified:
462 * We ignore the half-sample offset and the boundary condition
463 * at the end of the sample (we should extend with zero).
464 */
465 sl = (int)chan->lsmp * amp_factor * chan->volume / vol_max;
466 sn = (int)chan->sample->data[chan->smp_pos] * amp_factor *
467 chan->volume / vol_max;
468
469 period = (int)chan->period;
470 clk = (int)chan->smp_clk;
471
472 s = (sl * (period - clk) + sn * clk) / period;
473
474 chan->smp_clk += base_clock / modplay->smp_freq;
475 while (chan->sample != NULL && chan->smp_clk >= chan->period) {
476 chan->smp_clk -= chan->period;
477 chan_smp_next_frame(chan);
478 }
479
480 return s;
481}
482
483/** Render a segment of samples contained entirely within a tick.
484 *
485 * @param modplay Module playback
486 * @param buffer Buffer for storing audio data
487 * @param bufsize Size of @a buffer in bytes
488 */
489static void get_samples_within_tick(trackmod_modplay_t *modplay,
490 void *buffer, size_t bufsize)
491{
492 size_t nsamples;
493 size_t smpidx;
494 size_t chan;
495 int s;
496
497 nsamples = bufsize / modplay->frame_size;
498
499 for (smpidx = 0; smpidx < nsamples; smpidx++) {
500 s = 0;
501 for (chan = 0; chan < modplay->module->channels; chan++) {
502 s += trackmod_chan_next_sample(modplay, chan);
503 }
504
505 ((int16_t *)buffer)[smpidx] = s;
506 }
507
508 modplay->smp += nsamples;
509}
510
511/** Render a segment of samples.
512 *
513 * @param modplay Module playback
514 * @param buffer Buffer for storing audio data
515 * @param bufsize Size of @a buffer in bytes
516 */
517void trackmod_modplay_get_samples(trackmod_modplay_t *modplay,
518 void *buffer, size_t bufsize)
519{
520 size_t nsamples;
521 size_t rsmp;
522 size_t now;
523
524 nsamples = bufsize / modplay->frame_size;
525 while (nsamples > 0) {
526 rsmp = samples_remain_tick(modplay);
527 if (rsmp == 0)
528 trackmod_next_tick(modplay);
529
530 rsmp = samples_remain_tick(modplay);
531 now = min(rsmp, nsamples);
532
533 get_samples_within_tick(modplay, buffer,
534 now * modplay->frame_size);
535 nsamples -= now;
536 buffer += now * modplay->frame_size;
537 }
538}
539
540/** @}
541 */
Note: See TracBrowser for help on using the repository browser.