forked from kd7tck/jar
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjar_xm.h
2635 lines (2349 loc) · 107 KB
/
jar_xm.h
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// jar_xm.h
//
// ORIGINAL LICENSE - FOR LIBXM:
//
// Author: Romain "Artefact2" Dalmaso <[email protected]>
// Contributor: Dan Spencer <[email protected]>
// Repackaged into jar_xm.h By: Joshua Adam Reisenauer <[email protected]>
//
// This program is free software.
// It comes without any warranty, to the extent permitted by applicable law. You can redistribute it and/or modify it
// under the terms as the Do What The Fuck You Want To Public License applied: published by Sam Hocevar link: http://sam.zoy.org/wtfpl/COPYING for more details.
//
// HISTORY:
// v0.1.0 2016-02-22 jar_xm.h - development by Joshua Reisenauer, MAR 2016
// v0.2.1 2021-03-07 m4ntr0n1c: Fix clipping noise for "bad" xm's (they will always clip), avoid clip noise and just put a ceiling)
// v0.2.2 2021-03-09 m4ntr0n1c: Add complete debug solution (raylib.h must be included)
// v0.2.3 2021-03-11 m4ntr0n1c: Fix tempo, bpm and volume on song stop / start / restart / loop
// v0.2.4 2021-03-17 m4ntr0n1c: Sanitize code for readability
// v0.2.5 2021-03-22 m4ntr0n1c: Minor adjustments
// v0.2.6 2021-04-01 m4ntr0n1c: Minor fixes and optimisation
// v0.3.0 2021-04-03 m4ntr0n1c: Addition of Stereo sample support, Linear Interpolation and Ramping now addressable options in code
// v0.3.1 2021-04-04 m4ntr0n1c: Volume effects column adjustments, sample offset handling adjustments
// v0.3.2 2021-04-04 m4ntr0n1c: Experimental; Compressor-Expander, more hooks _ended up a bad idea_
// v0.3.3 2021-06-06 m4ntr0n1c: Changed/Added; Pattern_jump, Pattern_jump_immediate, other minor tweaks
// v0.3.4 2021-06-09 m4ntr0n1c: Implemented channel mixer and api calls
// v0.3.5 2021-06-09 m4ntr0n1c: Effects Implementation: Panbello and Note Off
// v0.3.6 2021-06-13 m4ntr0n1c: Debug Display: Reduced performance hit & Improved usability
// v0.3.7 2021-10-03 m4ntr0n1c: Latest fixes applied (taken from the Artefact2 core sources)
// ie. multi-retrig, vibrato, note retrigger volume, volume panning cache, envelope speed, ghost notes
//
// USAGE:
//
// In ONE source file, put:
//
// #define JAR_XM_IMPLEMENTATION
// #include "jar_xm.h"
//
// Other source files should just include jar_xm.h
//
// SAMPLE CODE:
//
// jar_xm_context_t *musicptr;
// float musicBuffer[48000 / 60];
// int intro_load(void)
// {
// jar_xm_create_context_from_file(&musicptr, 48000, "Song.XM");
// return 1;
// }
// int intro_unload(void)
// {
// jar_xm_free_context(musicptr);
// return 1;
// }
// int intro_tick(long counter)
// {
// jar_xm_generate_samples(musicptr, musicBuffer, (48000 / 60) / 2);
// if(IsKeyDown(KEY_ENTER))
// return 1;
// return 0;
// }
//
#ifndef INCLUDE_JAR_XM_H
#define INCLUDE_JAR_XM_H
#include <stdint.h>
#define JAR_XM_DEBUG 0
#define JAR_XM_DEFENSIVE 1
#define JAR_XM_RAYLIB 0 // set to 0 to disable the RayLib visualizer extension
// Allow custom memory allocators
#ifndef JARXM_MALLOC
#define JARXM_MALLOC(sz) malloc(sz)
#endif
#ifndef JARXM_FREE
#define JARXM_FREE(p) free(p)
#endif
//---- STORAGE SPACE FOR LOADED MODULE
struct jar_xm_context_s;
typedef struct jar_xm_context_s jar_xm_context_t;
#ifdef __cplusplus
extern "C" {
#endif
//** Create a XM context.
// * @param moddata the contents of the module
// * @param rate play rate in Hz, recommended value of 48000
// * @returns 0 on success
// * @returns 1 if module data is not sane
// * @returns 2 if memory allocation failed
// * @returns 3 unable to open input file
// * @returns 4 fseek() failed
// * @returns 5 fread() failed
// * @returns 6 unkown error
// * @deprecated This function is unsafe!
// * @see jar_xm_create_context_safe()
int jar_xm_create_context_from_file(jar_xm_context_t** ctx, uint32_t rate, const char* filename);
//** Create a XM context.
// * @param moddata the contents of the module
// * @param rate play rate in Hz, recommended value of 48000
// * @returns 0 on success
// * @returns 1 if module data is not sane
// * @returns 2 if memory allocation failed
// * @deprecated This function is unsafe!
// * @see jar_xm_create_context_safe()
int jar_xm_create_context(jar_xm_context_t** ctx, const char* moddata, uint32_t rate);
//** Create a XM context.
// * @param moddata the contents of the module
// * @param moddata_length the length of the contents of the module, in bytes
// * @param rate play rate in Hz, recommended value of 48000
// * @returns 0 on success
// * @returns 1 if module data is not sane
// * @returns 2 if memory allocation failed
int jar_xm_create_context_safe(jar_xm_context_t** ctx, const char* moddata, size_t moddata_length, uint32_t rate);
//** Free a XM context created by jar_xm_create_context(). */
void jar_xm_free_context(jar_xm_context_t* ctx);
//** Play the module and put the sound samples in an output buffer.
// * @param output buffer of 2*numsamples elements (A left and right value for each sample)
// * @param numsamples number of samples to generate
void jar_xm_generate_samples(jar_xm_context_t* ctx, float* output, size_t numsamples);
//** Play the module, resample from float to 24 bit, and put the sound samples in an output buffer.
// * @param output buffer of 2*numsamples elements (A left and right value for each sample)
// * @param numsamples number of samples to generate
void jar_xm_generate_samples_24bit(jar_xm_context_t* ctx, short* output, size_t numsamples) {
float* musicBuffer = JARXM_MALLOC((2*numsamples)*sizeof(float));
jar_xm_generate_samples(ctx, musicBuffer, numsamples);
if(output){
for(int x=0;x<2*numsamples;x++) output[x] = (musicBuffer[x] * 8388607.0f); // scale sample to signed 24 bit value as int
}
JARXM_FREE(musicBuffer);
}
//** Play the module, resample from float to 16 bit, and put the sound samples in an output buffer.
// * @param output buffer of 2*numsamples elements (A left and right value for each sample)
// * @param numsamples number of samples to generate
void jar_xm_generate_samples_16bit(jar_xm_context_t* ctx, short* output, size_t numsamples) {
float* musicBuffer = JARXM_MALLOC((2*numsamples)*sizeof(float));
jar_xm_generate_samples(ctx, musicBuffer, numsamples);
if(output){
for(int x=0;x<2*numsamples;x++) output[x] = (musicBuffer[x] * 32767.0f); // scale sample to signed small int
}
JARXM_FREE(musicBuffer);
}
//** Play the module, resample from float to 8 bit, and put the sound samples in an output buffer.
// * @param output buffer of 2*numsamples elements (A left and right value for each sample)
// * @param numsamples number of samples to generate
void jar_xm_generate_samples_8bit(jar_xm_context_t* ctx, char* output, size_t numsamples) {
float* musicBuffer = JARXM_MALLOC((2*numsamples)*sizeof(float));
jar_xm_generate_samples(ctx, musicBuffer, numsamples);
if(output){
for(int x=0;x<2*numsamples;x++) output[x] = ((musicBuffer[x] + 1.0f) * 127.5f); // scale sample to unsigned 8 bit
}
JARXM_FREE(musicBuffer);
}
//** Set the maximum number of times a module can loop. After the specified number of loops, calls to jar_xm_generate_samples will only generate silence. You can control the current number of loops with jar_xm_get_loop_count().
// * @param loopcnt maximum number of loops. Use 0 to loop indefinitely.
void jar_xm_set_max_loop_count(jar_xm_context_t* ctx, uint8_t loopcnt);
//** Get the loop count of the currently playing module. This value is 0 when the module is still playing, 1 when the module has looped once, etc.
uint8_t jar_xm_get_loop_count(jar_xm_context_t* ctx);
//** Mute or unmute a channel.
// * @note Channel numbers go from 1 to jar_xm_get_number_of_channels(...).
// * @return whether the channel was muted.
bool jar_xm_mute_channel(jar_xm_context_t* ctx, uint16_t, bool);
//** Mute or unmute an instrument.
// * @note Instrument numbers go from 1 to jar_xm_get_number_of_instruments(...).
// * @return whether the instrument was muted.
bool jar_xm_mute_instrument(jar_xm_context_t* ctx, uint16_t, bool);
//** Get the module name as a NUL-terminated string.
const char* jar_xm_get_module_name(jar_xm_context_t* ctx);
//** Get the tracker name as a NUL-terminated string.
const char* jar_xm_get_tracker_name(jar_xm_context_t* ctx);
//** Get the number of channels.
uint16_t jar_xm_get_number_of_channels(jar_xm_context_t* ctx);
//** Get the module length (in patterns).
uint16_t jar_xm_get_module_length(jar_xm_context_t*);
//** Get the number of patterns.
uint16_t jar_xm_get_number_of_patterns(jar_xm_context_t* ctx);
//** Get the number of rows of a pattern.
// * @note Pattern numbers go from 0 to jar_xm_get_number_of_patterns(...)-1.
uint16_t jar_xm_get_number_of_rows(jar_xm_context_t* ctx, uint16_t);
//** Get the number of instruments.
uint16_t jar_xm_get_number_of_instruments(jar_xm_context_t* ctx);
//** Get the number of samples of an instrument.
// * @note Instrument numbers go from 1 to jar_xm_get_number_of_instruments(...).
uint16_t jar_xm_get_number_of_samples(jar_xm_context_t* ctx, uint16_t);
//** Get the current module speed.
// * @param bpm will receive the current BPM
// * @param tempo will receive the current tempo (ticks per line)
void jar_xm_get_playing_speed(jar_xm_context_t* ctx, uint16_t* bpm, uint16_t* tempo);
//** Get the current position in the module being played.
// * @param pattern_index if not NULL, will receive the current pattern index in the POT (pattern order table)
// * @param pattern if not NULL, will receive the current pattern number
// * @param row if not NULL, will receive the current row
// * @param samples if not NULL, will receive the total number of
// * generated samples (divide by sample rate to get seconds of generated audio)
void jar_xm_get_position(jar_xm_context_t* ctx, uint8_t* pattern_index, uint8_t* pattern, uint8_t* row, uint64_t* samples);
//** Get the latest time (in number of generated samples) when a particular instrument was triggered in any channel.
// * @note Instrument numbers go from 1 to jar_xm_get_number_of_instruments(...).
uint64_t jar_xm_get_latest_trigger_of_instrument(jar_xm_context_t* ctx, uint16_t);
//** Get the latest time (in number of generated samples) when a particular sample was triggered in any channel.
// * @note Instrument numbers go from 1 to jar_xm_get_number_of_instruments(...).
// * @note Sample numbers go from 0 to jar_xm_get_nubmer_of_samples(...,instr)-1.
uint64_t jar_xm_get_latest_trigger_of_sample(jar_xm_context_t* ctx, uint16_t instr, uint16_t sample);
//** Get the latest time (in number of generated samples) when any instrument was triggered in a given channel.
// * @note Channel numbers go from 1 to jar_xm_get_number_of_channels(...).
uint64_t jar_xm_get_latest_trigger_of_channel(jar_xm_context_t* ctx, uint16_t);
//** Get the number of remaining samples. Divide by 2 to get the number of individual LR data samples.
// * @note This is the remaining number of samples before the loop starts module again, or halts if on last pass.
// * @note This function is very slow and should only be run once, if at all.
uint64_t jar_xm_get_remaining_samples(jar_xm_context_t* ctx);
#ifdef __cplusplus
}
#endif
//-------------------------------------------------------------------------------
#ifdef JAR_XM_IMPLEMENTATION
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <inttypes.h>
#if JAR_XM_DEBUG //JAR_XM_DEBUG defined as 0
#include <stdio.h>
#define DEBUG(fmt, ...) do { \
fprintf(stderr, "%s(): " fmt "\n", __func__, __VA_ARGS__); \
fflush(stderr); \
} while(0)
#else
#define DEBUG(...)
#endif
#if jar_xm_BIG_ENDIAN
#error "Big endian platforms are not yet supported, sorry"
/* Make sure the compiler stops, even if #error is ignored */
extern int __fail[-1];
#endif
/* ----- XM constants ----- */
#define SAMPLE_NAME_LENGTH 22
#define INSTRUMENT_HEADER_LENGTH 263
#define INSTRUMENT_NAME_LENGTH 22
#define MODULE_NAME_LENGTH 20
#define TRACKER_NAME_LENGTH 20
#define PATTERN_ORDER_TABLE_LENGTH 256
#define NUM_NOTES 96 // from 1 to 96, where 1 = C-0
#define NUM_CHANNELS 32 // standard is 32, but Mod Plug Tracker allows up to 128
#define NUM_ENVELOPE_POINTS 12 // to be verified if 12 is the max
#define MAX_NUM_ROWS 256
#define jar_xm_SAMPLE_RAMPING_POINTS 8
/* ----- Data types ----- */
enum jar_xm_waveform_type_e {
jar_xm_SINE_WAVEFORM = 0,
jar_xm_RAMP_DOWN_WAVEFORM = 1,
jar_xm_SQUARE_WAVEFORM = 2,
jar_xm_RANDOM_WAVEFORM = 3,
jar_xm_RAMP_UP_WAVEFORM = 4,
};
typedef enum jar_xm_waveform_type_e jar_xm_waveform_type_t;
enum jar_xm_loop_type_e {
jar_xm_NO_LOOP,
jar_xm_FORWARD_LOOP,
jar_xm_PING_PONG_LOOP,
};
typedef enum jar_xm_loop_type_e jar_xm_loop_type_t;
enum jar_xm_frequency_type_e {
jar_xm_LINEAR_FREQUENCIES,
jar_xm_AMIGA_FREQUENCIES,
};
typedef enum jar_xm_frequency_type_e jar_xm_frequency_type_t;
struct jar_xm_envelope_point_s {
uint16_t frame;
uint16_t value;
};
typedef struct jar_xm_envelope_point_s jar_xm_envelope_point_t;
struct jar_xm_envelope_s {
jar_xm_envelope_point_t points[NUM_ENVELOPE_POINTS];
uint8_t num_points;
uint8_t sustain_point;
uint8_t loop_start_point;
uint8_t loop_end_point;
bool enabled;
bool sustain_enabled;
bool loop_enabled;
};
typedef struct jar_xm_envelope_s jar_xm_envelope_t;
struct jar_xm_sample_s {
char name[SAMPLE_NAME_LENGTH + 1];
int8_t bits; /* Either 8 or 16 */
int8_t stereo;
uint32_t length;
uint32_t loop_start;
uint32_t loop_length;
uint32_t loop_end;
float volume;
int8_t finetune;
jar_xm_loop_type_t loop_type;
float panning;
int8_t relative_note;
uint64_t latest_trigger;
float* data;
};
typedef struct jar_xm_sample_s jar_xm_sample_t;
struct jar_xm_instrument_s {
char name[INSTRUMENT_NAME_LENGTH + 1];
uint16_t num_samples;
uint8_t sample_of_notes[NUM_NOTES];
jar_xm_envelope_t volume_envelope;
jar_xm_envelope_t panning_envelope;
jar_xm_waveform_type_t vibrato_type;
uint8_t vibrato_sweep;
uint8_t vibrato_depth;
uint8_t vibrato_rate;
uint16_t volume_fadeout;
uint64_t latest_trigger;
bool muted;
jar_xm_sample_t* samples;
};
typedef struct jar_xm_instrument_s jar_xm_instrument_t;
struct jar_xm_pattern_slot_s {
uint8_t note; /* 1-96, 97 = Key Off note */
uint8_t instrument; /* 1-128 */
uint8_t volume_column; /* 1-64 */
uint8_t effect_type;
uint8_t effect_param;
};
typedef struct jar_xm_pattern_slot_s jar_xm_pattern_slot_t;
struct jar_xm_pattern_s {
uint16_t num_rows;
jar_xm_pattern_slot_t* slots; /* Array of size num_rows * num_channels */
};
typedef struct jar_xm_pattern_s jar_xm_pattern_t;
struct jar_xm_module_s {
char name[MODULE_NAME_LENGTH + 1];
char trackername[TRACKER_NAME_LENGTH + 1];
uint16_t length;
uint16_t restart_position;
uint16_t num_channels;
uint16_t num_patterns;
uint16_t num_instruments;
uint16_t linear_interpolation;
uint16_t ramping;
uint16_t comp_exp;
jar_xm_frequency_type_t frequency_type;
uint8_t pattern_table[PATTERN_ORDER_TABLE_LENGTH];
jar_xm_pattern_t* patterns;
jar_xm_instrument_t* instruments; /* Instrument 1 has index 0, instrument 2 has index 1, etc. */
};
typedef struct jar_xm_module_s jar_xm_module_t;
struct jar_xm_channel_context_s {
float note;
float orig_note; /* The original note before effect modifications, as read in the pattern. */
jar_xm_instrument_t* instrument; /* Could be NULL */
jar_xm_sample_t* sample; /* Could be NULL */
jar_xm_pattern_slot_t* current;
float sample_position;
float period;
float frequency;
float step;
bool ping; /* For ping-pong samples: true is -->, false is <-- */
float volume; /* Ideally between 0 (muted) and 1 (loudest) */
float panning; /* Between 0 (left) and 1 (right); 0.5 is centered */
uint16_t autovibrato_ticks;
bool sustained;
float fadeout_volume;
float volume_envelope_volume;
float panning_envelope_panning;
uint16_t volume_envelope_frame_count;
uint16_t panning_envelope_frame_count;
float autovibrato_note_offset;
bool arp_in_progress;
uint8_t arp_note_offset;
uint8_t volume_slide_param;
uint8_t fine_volume_slide_param;
uint8_t global_volume_slide_param;
uint8_t panning_slide_param;
uint8_t portamento_up_param;
uint8_t portamento_down_param;
uint8_t fine_portamento_up_param;
uint8_t fine_portamento_down_param;
uint8_t extra_fine_portamento_up_param;
uint8_t extra_fine_portamento_down_param;
uint8_t tone_portamento_param;
float tone_portamento_target_period;
uint8_t multi_retrig_param;
uint8_t note_delay_param;
uint8_t pattern_loop_origin; /* Where to restart a E6y loop */
uint8_t pattern_loop_count; /* How many loop passes have been done */
bool vibrato_in_progress;
jar_xm_waveform_type_t vibrato_waveform;
bool vibrato_waveform_retrigger; /* True if a new note retriggers the waveform */
uint8_t vibrato_param;
uint16_t vibrato_ticks; /* Position in the waveform */
float vibrato_note_offset;
jar_xm_waveform_type_t tremolo_waveform;
bool tremolo_waveform_retrigger;
uint8_t tremolo_param;
uint8_t tremolo_ticks;
float tremolo_volume;
uint8_t tremor_param;
bool tremor_on;
uint64_t latest_trigger;
bool muted;
//* These values are updated at the end of each tick, to save a couple of float operations on every generated sample.
float target_volume[2];
unsigned long frame_count;
float end_of_previous_sample_left[jar_xm_SAMPLE_RAMPING_POINTS];
float end_of_previous_sample_right[jar_xm_SAMPLE_RAMPING_POINTS];
float curr_left;
float curr_right;
float actual_volume[2];
};
typedef struct jar_xm_channel_context_s jar_xm_channel_context_t;
struct jar_xm_context_s {
void* allocated_memory;
jar_xm_module_t module;
uint32_t rate;
uint16_t default_tempo; // Number of ticks per row
uint16_t default_bpm;
float default_global_volume;
uint16_t tempo; // Number of ticks per row
uint16_t bpm;
float global_volume;
float channel_volume[NUM_CHANNELS];
float volume_ramp; /* How much is a channel final volume allowed to change per sample; this is used to avoid abrubt volume changes which manifest as "clicks" in the generated sound. */
uint8_t current_table_index;
uint8_t current_row;
uint16_t current_tick; /* Can go below 255, with high tempo and a pattern delay */
float remaining_samples_in_tick;
uint64_t generated_samples;
bool position_jump;
bool pattern_break;
uint8_t jump_dest;
uint8_t jump_row;
uint16_t extra_ticks; /* Extra ticks to be played before going to the next row - Used for EEy effect */
uint8_t* row_loop_count; /* Array of size MAX_NUM_ROWS * module_length */
uint8_t loop_count;
uint8_t max_loop_count;
jar_xm_channel_context_t* channels;
};
// Compressor / expander
const float comp_TBL[2][6] = {{0.0, 0.25, 0.5, 0.75, 0.90, 1.0}, {1.0, 2.0, 4.5, 2.8, 1.5, 1.0}};
const int COMP_TBL_ENTRIES = 6;
float jar_xm_compress_expand (float chan) {
int lo = 0, hi = 0;
for (int j=0;j<=(COMP_TBL_ENTRIES-1);j++) {
if (comp_TBL[0][j] <= chan) { lo = j;};
if (comp_TBL[0][j] >= chan) { hi = j;};
};
float a = chan - comp_TBL[0][lo];
float c = comp_TBL[0][hi] - chan;
float a1= comp_TBL[1][lo];
float c1= comp_TBL[1][hi];
return sqrt((c * c * c1 * c1 + a * a * a1 * a1) * chan * chan) * .333;
};
#if JAR_XM_DEFENSIVE
//** Check the module data for errors/inconsistencies.
// * @returns 0 if everything looks OK. Module should be safe to load.
int jar_xm_check_sanity_preload(const char*, size_t);
//** Check a loaded module for errors/inconsistencies.
// * @returns 0 if everything looks OK.
int jar_xm_check_sanity_postload(jar_xm_context_t*);
#endif
//** Get the number of bytes needed to store the module data in a dynamically allocated blank context.
// * Things that are dynamically allocated:
// * - sample data
// * - sample structures in instruments
// * - pattern data
// * - row loop count arrays
// * - pattern structures in module
// * - instrument structures in module
// * - channel contexts
// * - context structure itself
// * @returns 0 if everything looks OK.
size_t jar_xm_get_memory_needed_for_context(const char*, size_t);
//** Populate the context from module data.
// * @returns pointer to the memory pool
char* jar_xm_load_module(jar_xm_context_t*, const char*, size_t, char*);
int jar_xm_create_context(jar_xm_context_t** ctxp, const char* moddata, uint32_t rate) {
return jar_xm_create_context_safe(ctxp, moddata, SIZE_MAX, rate);
}
#define ALIGN(x, b) (((x) + ((b) - 1)) & ~((b) - 1))
#define ALIGN_PTR(x, b) (void*)(((uintptr_t)(x) + ((b) - 1)) & ~((b) - 1))
int jar_xm_create_context_safe(jar_xm_context_t** ctxp, const char* moddata, size_t moddata_length, uint32_t rate) {
#if JAR_XM_DEFENSIVE
int ret;
#endif
size_t bytes_needed;
char* mempool;
jar_xm_context_t* ctx;
#if JAR_XM_DEFENSIVE
if((ret = jar_xm_check_sanity_preload(moddata, moddata_length))) {
DEBUG("jar_xm_check_sanity_preload() returned %i, module is not safe to load", ret);
return 1;
}
#endif
bytes_needed = jar_xm_get_memory_needed_for_context(moddata, moddata_length);
mempool = JARXM_MALLOC(bytes_needed);
if(mempool == NULL && bytes_needed > 0) { /* JARXM_MALLOC() failed, trouble ahead */
DEBUG("call to JARXM_MALLOC() failed, returned %p", (void*)mempool);
return 2;
}
/* Initialize most of the fields to 0, 0.f, NULL or false depending on type */
memset(mempool, 0, bytes_needed);
ctx = (*ctxp = (jar_xm_context_t *)mempool);
ctx->allocated_memory = mempool; /* Keep original pointer for JARXM_FREE() */
mempool += sizeof(jar_xm_context_t);
ctx->rate = rate;
mempool = jar_xm_load_module(ctx, moddata, moddata_length, mempool);
mempool = ALIGN_PTR(mempool, 16);
ctx->channels = (jar_xm_channel_context_t*)mempool;
mempool += ctx->module.num_channels * sizeof(jar_xm_channel_context_t);
mempool = ALIGN_PTR(mempool, 16);
ctx->default_global_volume = 1.f;
ctx->global_volume = ctx->default_global_volume;
ctx->volume_ramp = (1.f / 128.f);
for(uint8_t i = 0; i < ctx->module.num_channels; ++i) {
ctx->channel_volume[i] = 1.0f; // set to full
jar_xm_channel_context_t *ch = ctx->channels + i;
ch->ping = true;
ch->vibrato_waveform = jar_xm_SINE_WAVEFORM;
ch->vibrato_waveform_retrigger = true;
ch->tremolo_waveform = jar_xm_SINE_WAVEFORM;
ch->tremolo_waveform_retrigger = true;
ch->volume = ch->volume_envelope_volume = ch->fadeout_volume = 1.0f;
ch->panning = ch->panning_envelope_panning = .5f;
ch->actual_volume[0] = .0f;
ch->actual_volume[1] = .0f;
}
mempool = ALIGN_PTR(mempool, 16);
ctx->row_loop_count = (uint8_t *)mempool;
mempool += MAX_NUM_ROWS * sizeof(uint8_t);
#if JAR_XM_DEFENSIVE
if((ret = jar_xm_check_sanity_postload(ctx))) { DEBUG("jar_xm_check_sanity_postload() returned %i, module is not safe to play", ret);
jar_xm_free_context(ctx);
return 1;
}
#endif
return 0;
}
void jar_xm_free_context(jar_xm_context_t *ctx) {
if (ctx != NULL) { JARXM_FREE(ctx->allocated_memory); }
}
void jar_xm_set_max_loop_count(jar_xm_context_t *ctx, uint8_t loopcnt) {
ctx->max_loop_count = loopcnt;
}
uint8_t jar_xm_get_loop_count(jar_xm_context_t *ctx) {
return ctx->loop_count;
}
bool jar_xm_mute_channel(jar_xm_context_t *ctx, uint16_t channel, bool mute) {
bool old = ctx->channels[channel - 1].muted;
ctx->channels[channel - 1].muted = mute;
return old;
}
bool jar_xm_mute_instrument(jar_xm_context_t *ctx, uint16_t instr, bool mute) {
bool old = ctx->module.instruments[instr - 1].muted;
ctx->module.instruments[instr - 1].muted = mute;
return old;
}
const char* jar_xm_get_module_name(jar_xm_context_t *ctx) {
return ctx->module.name;
}
const char* jar_xm_get_tracker_name(jar_xm_context_t *ctx) {
return ctx->module.trackername;
}
uint16_t jar_xm_get_number_of_channels(jar_xm_context_t *ctx) {
return ctx->module.num_channels;
}
uint16_t jar_xm_get_module_length(jar_xm_context_t *ctx) {
return ctx->module.length;
}
uint16_t jar_xm_get_number_of_patterns(jar_xm_context_t *ctx) {
return ctx->module.num_patterns;
}
uint16_t jar_xm_get_number_of_rows(jar_xm_context_t *ctx, uint16_t pattern) {
return ctx->module.patterns[pattern].num_rows;
}
uint16_t jar_xm_get_number_of_instruments(jar_xm_context_t *ctx) {
return ctx->module.num_instruments;
}
uint16_t jar_xm_get_number_of_samples(jar_xm_context_t *ctx, uint16_t instrument) {
return ctx->module.instruments[instrument - 1].num_samples;
}
void jar_xm_get_playing_speed(jar_xm_context_t *ctx, uint16_t *bpm, uint16_t *tempo) {
if(bpm) *bpm = ctx->bpm;
if(tempo) *tempo = ctx->tempo;
}
void jar_xm_get_position(jar_xm_context_t *ctx, uint8_t *pattern_index, uint8_t *pattern, uint8_t *row, uint64_t *samples) {
if(pattern_index) *pattern_index = ctx->current_table_index;
if(pattern) *pattern = ctx->module.pattern_table[ctx->current_table_index];
if(row) *row = ctx->current_row;
if(samples) *samples = ctx->generated_samples;
}
uint64_t jar_xm_get_latest_trigger_of_instrument(jar_xm_context_t *ctx, uint16_t instr) {
return ctx->module.instruments[instr - 1].latest_trigger;
}
uint64_t jar_xm_get_latest_trigger_of_sample(jar_xm_context_t *ctx, uint16_t instr, uint16_t sample) {
return ctx->module.instruments[instr - 1].samples[sample].latest_trigger;
}
uint64_t jar_xm_get_latest_trigger_of_channel(jar_xm_context_t *ctx, uint16_t chn) {
return ctx->channels[chn - 1].latest_trigger;
}
bool jar_xm_is_channel_active(jar_xm_context_t* ctx, uint16_t chn) {
jar_xm_channel_context_t* ch = ctx->channels + (chn - 1);
return ch->instrument != NULL && ch->sample != NULL && ch->sample_position >= 0;
}
float jar_xm_get_frequency_of_channel(jar_xm_context_t* ctx, uint16_t chn) {
return ctx->channels[chn - 1].frequency;
}
float jar_xm_get_volume_of_channel(jar_xm_context_t* ctx, uint16_t chn) {
return ctx->channels[chn - 1].volume * ctx->global_volume;
}
float jar_xm_get_panning_of_channel(jar_xm_context_t* ctx, uint16_t chn) {
return ctx->channels[chn - 1].panning;
}
uint16_t jar_xm_get_instrument_of_channel(jar_xm_context_t* ctx, uint16_t chn) {
jar_xm_channel_context_t* ch = ctx->channels + (chn - 1);
if(ch->instrument == NULL) return 0;
return 1 + (ch->instrument - ctx->module.instruments);
}
//* Bound reader macros.
//* If we attempt to read the buffer out-of-bounds, pretend that the buffer is infinitely padded with zeroes.
#define READ_U8(offset) (((offset) < moddata_length) ? (*(uint8_t*)(moddata + (offset))) : 0)
#define READ_U16(offset) ((uint16_t)READ_U8(offset) | ((uint16_t)READ_U8((offset) + 1) << 8))
#define READ_U32(offset) ((uint32_t)READ_U16(offset) | ((uint32_t)READ_U16((offset) + 2) << 16))
#define READ_MEMCPY(ptr, offset, length) memcpy_pad(ptr, length, moddata, moddata_length, offset)
#define MIN(a, b) ((a) < (b) ? (a) : (b))
static void memcpy_pad(void *dst, size_t dst_len, const void *src, size_t src_len, size_t offset) {
uint8_t *dst_c = dst;
const uint8_t *src_c = src;
/* how many bytes can be copied without overrunning `src` */
size_t copy_bytes = (src_len >= offset) ? (src_len - offset) : 0;
copy_bytes = copy_bytes > dst_len ? dst_len : copy_bytes;
memcpy(dst_c, src_c + offset, copy_bytes);
/* padded bytes */
memset(dst_c + copy_bytes, 0, dst_len - copy_bytes);
}
#if JAR_XM_DEFENSIVE
int jar_xm_check_sanity_preload(const char* module, size_t module_length) {
if(module_length < 60) { return 4; }
if(memcmp("Extended Module: ", module, 17) != 0) { return 1; }
if(module[37] != 0x1A) { return 2; }
if(module[59] != 0x01 || module[58] != 0x04) { return 3; } /* Not XM 1.04 */
return 0;
}
int jar_xm_check_sanity_postload(jar_xm_context_t* ctx) {
/* Check the POT */
for(uint8_t i = 0; i < ctx->module.length; ++i) {
if(ctx->module.pattern_table[i] >= ctx->module.num_patterns) {
if(i+1 == ctx->module.length && ctx->module.length > 1) {
DEBUG("trimming invalid POT at pos %X", i);
--ctx->module.length;
} else {
DEBUG("module has invalid POT, pos %X references nonexistent pattern %X", i, ctx->module.pattern_table[i]);
return 1;
}
}
}
return 0;
}
#endif
size_t jar_xm_get_memory_needed_for_context(const char* moddata, size_t moddata_length) {
size_t memory_needed = 0;
size_t offset = 60; /* 60 = Skip the first header */
uint16_t num_channels;
uint16_t num_patterns;
uint16_t num_instruments;
/* Read the module header */
num_channels = READ_U16(offset + 8);
num_patterns = READ_U16(offset + 10);
memory_needed += num_patterns * sizeof(jar_xm_pattern_t);
memory_needed = ALIGN(memory_needed, 16);
num_instruments = READ_U16(offset + 12);
memory_needed += num_instruments * sizeof(jar_xm_instrument_t);
memory_needed = ALIGN(memory_needed, 16);
memory_needed += MAX_NUM_ROWS * READ_U16(offset + 4) * sizeof(uint8_t); /* Module length */
offset += READ_U32(offset); /* Header size */
/* Read pattern headers */
for(uint16_t i = 0; i < num_patterns; ++i) {
uint16_t num_rows;
num_rows = READ_U16(offset + 5);
memory_needed += num_rows * num_channels * sizeof(jar_xm_pattern_slot_t);
offset += READ_U32(offset) + READ_U16(offset + 7); /* Pattern header length + packed pattern data size */
}
memory_needed = ALIGN(memory_needed, 16);
/* Read instrument headers */
for(uint16_t i = 0; i < num_instruments; ++i) {
uint16_t num_samples;
uint32_t sample_header_size = 0;
uint32_t sample_size_aggregate = 0;
num_samples = READ_U16(offset + 27);
memory_needed += num_samples * sizeof(jar_xm_sample_t);
if(num_samples > 0) { sample_header_size = READ_U32(offset + 29); }
/* Instrument header size */
uint32_t ins_header_size = READ_U32(offset);
if (ins_header_size == 0 || ins_header_size > INSTRUMENT_HEADER_LENGTH)
ins_header_size = INSTRUMENT_HEADER_LENGTH;
offset += ins_header_size;
for(uint16_t j = 0; j < num_samples; ++j) {
uint32_t sample_size;
uint8_t flags;
sample_size = READ_U32(offset);
flags = READ_U8(offset + 14);
sample_size_aggregate += sample_size;
if(flags & (1 << 4)) { /* 16 bit sample */
memory_needed += sample_size * (sizeof(float) >> 1);
} else { /* 8 bit sample */
memory_needed += sample_size * sizeof(float);
}
offset += sample_header_size;
}
offset += sample_size_aggregate;
}
memory_needed += num_channels * sizeof(jar_xm_channel_context_t);
memory_needed += sizeof(jar_xm_context_t);
return memory_needed;
}
char* jar_xm_load_module(jar_xm_context_t* ctx, const char* moddata, size_t moddata_length, char* mempool) {
size_t offset = 0;
jar_xm_module_t* mod = &(ctx->module);
/* Read XM header */
READ_MEMCPY(mod->name, offset + 17, MODULE_NAME_LENGTH);
READ_MEMCPY(mod->trackername, offset + 38, TRACKER_NAME_LENGTH);
offset += 60;
/* Read module header */
uint32_t header_size = READ_U32(offset);
mod->length = READ_U16(offset + 4);
mod->restart_position = READ_U16(offset + 6);
mod->num_channels = READ_U16(offset + 8);
mod->num_patterns = READ_U16(offset + 10);
mod->num_instruments = READ_U16(offset + 12);
mod->patterns = (jar_xm_pattern_t*)mempool;
mod->linear_interpolation = 0; // Linear interpolation can be set after loading
mod->ramping = 1; // ramping can be set after loading
mod->comp_exp = 0; // use compressor - expander (experimental)
mempool += mod->num_patterns * sizeof(jar_xm_pattern_t);
mempool = ALIGN_PTR(mempool, 16);
mod->instruments = (jar_xm_instrument_t*)mempool;
mempool += mod->num_instruments * sizeof(jar_xm_instrument_t);
mempool = ALIGN_PTR(mempool, 16);
uint16_t flags = READ_U32(offset + 14);
mod->frequency_type = (flags & (1 << 0)) ? jar_xm_LINEAR_FREQUENCIES : jar_xm_AMIGA_FREQUENCIES;
ctx->default_tempo = READ_U16(offset + 16);
ctx->default_bpm = READ_U16(offset + 18);
ctx->tempo =ctx->default_tempo;
ctx->bpm = ctx->default_bpm;
READ_MEMCPY(mod->pattern_table, offset + 20, PATTERN_ORDER_TABLE_LENGTH);
offset += header_size;
/* Read patterns */
for(uint16_t i = 0; i < mod->num_patterns; ++i) {
uint16_t packed_patterndata_size = READ_U16(offset + 7);
jar_xm_pattern_t* pat = mod->patterns + i;
pat->num_rows = READ_U16(offset + 5);
pat->slots = (jar_xm_pattern_slot_t*)mempool;
mempool += mod->num_channels * pat->num_rows * sizeof(jar_xm_pattern_slot_t);
offset += READ_U32(offset); /* Pattern header length */
if(packed_patterndata_size == 0) { /* No pattern data is present */
memset(pat->slots, 0, sizeof(jar_xm_pattern_slot_t) * pat->num_rows * mod->num_channels);
} else {
/* This isn't your typical for loop */
for(uint16_t j = 0, k = 0; j < packed_patterndata_size; ++k) {
uint8_t note = READ_U8(offset + j);
jar_xm_pattern_slot_t* slot = pat->slots + k;
if(note & (1 << 7)) {
/* MSB is set, this is a compressed packet */
++j;
if(note & (1 << 0)) { /* Note follows */
slot->note = READ_U8(offset + j);
++j;
} else {
slot->note = 0;
}
if(note & (1 << 1)) { /* Instrument follows */
slot->instrument = READ_U8(offset + j);
++j;
} else {
slot->instrument = 0;
}
if(note & (1 << 2)) { /* Volume column follows */
slot->volume_column = READ_U8(offset + j);
++j;
} else {
slot->volume_column = 0;
}
if(note & (1 << 3)) { /* Effect follows */
slot->effect_type = READ_U8(offset + j);
++j;
} else {
slot->effect_type = 0;
}
if(note & (1 << 4)) { /* Effect parameter follows */
slot->effect_param = READ_U8(offset + j);
++j;
} else {
slot->effect_param = 0;
}
} else { /* Uncompressed packet */
slot->note = note;
slot->instrument = READ_U8(offset + j + 1);
slot->volume_column = READ_U8(offset + j + 2);
slot->effect_type = READ_U8(offset + j + 3);
slot->effect_param = READ_U8(offset + j + 4);
j += 5;
}
}
}
offset += packed_patterndata_size;
}
mempool = ALIGN_PTR(mempool, 16);
/* Read instruments */
for(uint16_t i = 0; i < ctx->module.num_instruments; ++i) {
uint32_t sample_header_size = 0;
jar_xm_instrument_t* instr = mod->instruments + i;
READ_MEMCPY(instr->name, offset + 4, INSTRUMENT_NAME_LENGTH);
instr->num_samples = READ_U16(offset + 27);
if(instr->num_samples > 0) {
/* Read extra header properties */
sample_header_size = READ_U32(offset + 29);
READ_MEMCPY(instr->sample_of_notes, offset + 33, NUM_NOTES);
instr->volume_envelope.num_points = READ_U8(offset + 225);
instr->panning_envelope.num_points = READ_U8(offset + 226);
for(uint8_t j = 0; j < instr->volume_envelope.num_points; ++j) {
instr->volume_envelope.points[j].frame = READ_U16(offset + 129 + 4 * j);
instr->volume_envelope.points[j].value = READ_U16(offset + 129 + 4 * j + 2);
}
for(uint8_t j = 0; j < instr->panning_envelope.num_points; ++j) {
instr->panning_envelope.points[j].frame = READ_U16(offset + 177 + 4 * j);
instr->panning_envelope.points[j].value = READ_U16(offset + 177 + 4 * j + 2);
}
instr->volume_envelope.sustain_point = READ_U8(offset + 227);
instr->volume_envelope.loop_start_point = READ_U8(offset + 228);
instr->volume_envelope.loop_end_point = READ_U8(offset + 229);
instr->panning_envelope.sustain_point = READ_U8(offset + 230);
instr->panning_envelope.loop_start_point = READ_U8(offset + 231);
instr->panning_envelope.loop_end_point = READ_U8(offset + 232);
// Fix broken modules with loop points outside of defined points
if (instr->volume_envelope.num_points > 0) {
instr->volume_envelope.loop_start_point = MIN(instr->volume_envelope.loop_start_point, instr->volume_envelope.num_points - 1);
instr->volume_envelope.loop_end_point = MIN(instr->volume_envelope.loop_end_point, instr->volume_envelope.num_points - 1);
}
if (instr->panning_envelope.num_points > 0) {
instr->panning_envelope.loop_start_point = MIN(instr->panning_envelope.loop_start_point, instr->panning_envelope.num_points - 1);
instr->panning_envelope.loop_end_point = MIN(instr->panning_envelope.loop_end_point, instr->panning_envelope.num_points - 1);
}
uint8_t flags = READ_U8(offset + 233);
instr->volume_envelope.enabled = flags & (1 << 0);
instr->volume_envelope.sustain_enabled = flags & (1 << 1);
instr->volume_envelope.loop_enabled = flags & (1 << 2);
flags = READ_U8(offset + 234);
instr->panning_envelope.enabled = flags & (1 << 0);
instr->panning_envelope.sustain_enabled = flags & (1 << 1);
instr->panning_envelope.loop_enabled = flags & (1 << 2);
instr->vibrato_type = READ_U8(offset + 235);
if(instr->vibrato_type == 2) {