From 9058b416aeb0630447ab1d507b2a527c1300fcdf Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Sun, 20 Apr 2025 15:28:01 +0900 Subject: [PATCH] add jar --- engine/README.md | 1 + engine/external/jar/README.md | 1 + engine/external/jar/jar_mod.h | 1384 +++++++++++++++++ engine/external/jar/jar_xm.h | 2534 +++++++++++++++++++++++++++++++ engine/gf_jar_mod.c | 2 + engine/gf_jar_xm.c | 2 + engine/include/gf_input.h | 1 + engine/include/gf_macro.h | 8 + engine/include/gf_type/compat.h | 34 + engine/include/gf_type/input.h | 24 + engine/premake5.lua | 3 +- tool/format.sh | 2 +- 12 files changed, 3994 insertions(+), 2 deletions(-) create mode 100644 engine/external/jar/README.md create mode 100644 engine/external/jar/jar_mod.h create mode 100644 engine/external/jar/jar_xm.h create mode 100644 engine/gf_jar_mod.c create mode 100644 engine/gf_jar_xm.c create mode 100644 engine/include/gf_type/compat.h create mode 100644 engine/include/gf_type/input.h diff --git a/engine/README.md b/engine/README.md index 7265983..60b9e89 100644 --- a/engine/README.md +++ b/engine/README.md @@ -7,4 +7,5 @@ - [Lua](https://lua.org) - [stb](https://github.com/nothings/stb) - [miniaudio](https://github.com/mackron/miniaudio) + - [jar](https://github.com/kd7tck/jar) - [Premake5](https://premake.github.io) diff --git a/engine/external/jar/README.md b/engine/external/jar/README.md new file mode 100644 index 0000000..1e1f404 --- /dev/null +++ b/engine/external/jar/README.md @@ -0,0 +1 @@ +This is modified version of [jar](https://github.com/kd7tck/jar). diff --git a/engine/external/jar/jar_mod.h b/engine/external/jar/jar_mod.h new file mode 100644 index 0000000..4f17009 --- /dev/null +++ b/engine/external/jar/jar_mod.h @@ -0,0 +1,1384 @@ +/** + * jar_mod.h - v0.01 - public domain C0 - Joshua Reisenauer + * + * HISTORY: + * + * v0.01 2016-03-12 Setup + * + * + * USAGE: + * + * In ONE source file, put: + * + * #define JAR_MOD_IMPLEMENTATION + * #include "jar_mod.h" + * + * Other source files should just include jar_mod.h + * + * SAMPLE CODE: + * jar_mod_context_t modctx; + * short samplebuff[4096]; + * gf_bool_t bufferFull = false; + * int intro_load(void) + * { + * jar_mod_init(&modctx); + * jar_mod_load_file(&modctx, "file.mod"); + * return 1; + * } + * int intro_unload(void) + * { + * jar_mod_unload(&modctx); + * return 1; + * } + * int intro_tick(long counter) + * { + * if(!bufferFull) + * { + * jar_mod_fillbuffer(&modctx, samplebuff, 4096, 0); + * bufferFull=true; + * } + * if(IsKeyDown(KEY_ENTER)) + * return 1; + * return 0; + * } + * + * + * LISCENSE: + * + * Written by: Jean-François DEL NERO (http://hxc2001.com/) free.fr> + * Adapted to jar_mod by: Joshua Adam Reisenauer + * 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 of the Do What The Fuck You Want To Public + * License, Version 2, as published by Sam Hocevar. See + * http://sam.zoy.org/wtfpl/COPYING for more details. + * + * HxCMOD Core API: + * ------------------------------------------- + * int jar_mod_init(jar_mod_context_t * modctx) + * + * - Initialize the jar_mod_context_t buffer. Must be called before doing anything else. + * Return 1 if success. 0 in case of error. + * ------------------------------------------- + * mulong jar_mod_load_file(jar_mod_context_t * modctx, const char* filename) + * + * - "Load" a MOD from file, context must already be initialized. + * Return size of file in bytes. + * ------------------------------------------- + * void jar_mod_fillbuffer( jar_mod_context_t * modctx, short * outbuffer, unsigned long nbsample, jar_mod_tracker_buffer_state * trkbuf ) + * + * - Generate and return the next samples chunk to outbuffer. + * nbsample specify the number of stereo 16bits samples you want. + * The output format is by default signed 48000Hz 16-bit Stereo PCM samples, otherwise it is changed with jar_mod_setcfg(). + * The output buffer size in bytes must be equal to ( nbsample * 2 * channels ). + * The optional trkbuf parameter can be used to get detailed status of the player. Put NULL/0 is unused. + * ------------------------------------------- + * void jar_mod_unload( jar_mod_context_t * modctx ) + * - "Unload" / clear the player status. + * ------------------------------------------- + */ + +#ifndef INCLUDE_JAR_MOD_H +#define INCLUDE_JAR_MOD_H + +/* Allow custom memory allocators */ +#ifndef JARMOD_MALLOC +#define JARMOD_MALLOC(sz) malloc(sz) +#endif +#ifndef JARMOD_FREE +#define JARMOD_FREE(p) free(p) +#endif + +/* Basic type */ +#if 0 +/** + * This is the original definitions, but not preferred + * since these type size might differ. + */ +typedef unsigned char muchar; +typedef unsigned short muint; +typedef short mint; +typedef unsigned long mulong; +#else +#include +typedef gf_uint8_t muchar; +typedef gf_uint16_t muint; +typedef gf_int16_t mint; +typedef gf_uint32_t mulong; +#endif + +#define NUMMAXCHANNELS 32 +#define MAXNOTES 12 * 12 +#define DEFAULT_SAMPLE_RATE 48000 +/** + * MOD file structures + */ + +#pragma pack(1) + +typedef struct { + muchar name[22]; + muint length; + muchar finetune; + muchar volume; + muint reppnt; + muint replen; +} sample; + +typedef struct { + muchar sampperiod; + muchar period; + muchar sampeffect; + muchar effect; +} note; + +typedef struct { + muchar title[20]; + sample samples[31]; + muchar length; /* length of tablepos */ + muchar protracker; + muchar patterntable[128]; + muchar signature[4]; + muchar speed; +} module; + +#pragma pack() + +/** + * HxCMod Internal structures + */ +typedef struct { + signed char* sampdata; + muint sampnum; + muint length; + muint reppnt; + muint replen; + mulong samppos; + muint period; + muchar volume; + mulong ticks; + muchar effect; + muchar parameffect; + muint effect_code; + mint decalperiod; + mint portaspeed; + mint portaperiod; + mint vibraperiod; + mint Arpperiods[3]; + muchar ArpIndex; + mint oldk; + muchar volumeslide; + muchar vibraparam; + muchar vibrapointeur; + muchar finetune; + muchar cut_param; + muint patternloopcnt; + muint patternloopstartpoint; +} channel; + +typedef struct { + module song; + char* sampledata[31]; + note* patterndata[128]; + + mulong playrate; + muint tablepos; + muint patternpos; + muint patterndelay; + muint jump_loop_effect; + muchar bpm; + mulong patternticks; + mulong patterntickse; + mulong patternticksaim; + mulong sampleticksconst; + mulong samplenb; + channel channels[NUMMAXCHANNELS]; + muint number_of_channels; + muint fullperiod[MAXNOTES * 8]; + muint mod_loaded; + mint last_r_sample; + mint last_l_sample; + mint stereo; + mint stereo_separation; + mint bits; + mint filter; + + muchar* modfile; /* the raw mod file */ + mulong modfilesize; + muint loopcount; +} jar_mod_context_t; + +/** + * Player states structures + */ +typedef struct track_state_ { + unsigned char instrument_number; + unsigned short cur_period; + unsigned char cur_volume; + unsigned short cur_effect; + unsigned short cur_parameffect; +} track_state; + +typedef struct tracker_state_ { + int number_of_tracks; + int bpm; + int speed; + int cur_pattern; + int cur_pattern_pos; + int cur_pattern_table_pos; + unsigned int buf_index; + track_state tracks[32]; +} tracker_state; + +typedef struct tracker_state_instrument_ { + char name[22]; + int active; +} tracker_state_instrument; + +typedef struct jar_mod_tracker_buffer_state_ { + int nb_max_of_state; + int nb_of_state; + int cur_rd_index; + int sample_step; + char name[64]; + tracker_state_instrument instruments[31]; + tracker_state* track_state_buf; +} jar_mod_tracker_buffer_state; + +#ifdef __cplusplus +extern "C" { +#endif + +gf_bool_t jar_mod_init(jar_mod_context_t* modctx); +gf_bool_t jar_mod_setcfg(jar_mod_context_t* modctx, int samplerate, int bits, int stereo, int stereo_separation, int filter); +void jar_mod_fillbuffer(jar_mod_context_t* modctx, short* outbuffer, unsigned long nbsample, jar_mod_tracker_buffer_state* trkbuf); +void jar_mod_unload(jar_mod_context_t* modctx); +mulong jar_mod_load_file(jar_mod_context_t* modctx, const char* filename); +mulong jar_mod_current_samples(jar_mod_context_t* modctx); +mulong jar_mod_max_samples(jar_mod_context_t* modctx); +void jar_mod_seek_start(jar_mod_context_t* ctx); + +#ifdef __cplusplus +} +#endif +/*--------------------------------------------------------------------*/ + +/*-------------------------------------------------------------------------------*/ +#ifdef JAR_MOD_IMPLEMENTATION + +#include +#include +#include + +/* Effects list */ +#define EFFECT_ARPEGGIO 0x0 /* Supported */ +#define EFFECT_PORTAMENTO_UP 0x1 /* Supported */ +#define EFFECT_PORTAMENTO_DOWN 0x2 /* Supported */ +#define EFFECT_TONE_PORTAMENTO 0x3 /* Supported */ +#define EFFECT_VIBRATO 0x4 /* Supported */ +#define EFFECT_VOLSLIDE_TONEPORTA 0x5 /* Supported */ +#define EFFECT_VOLSLIDE_VIBRATO 0x6 /* Supported */ +#define EFFECT_VOLSLIDE_TREMOLO 0x7 /* - TO BE DONE - */ +#define EFFECT_SET_PANNING 0x8 /* - TO BE DONE - */ +#define EFFECT_SET_OFFSET 0x9 /* Supported */ +#define EFFECT_VOLUME_SLIDE 0xA /* Supported */ +#define EFFECT_JUMP_POSITION 0xB /* Supported */ +#define EFFECT_SET_VOLUME 0xC /* Supported */ +#define EFFECT_PATTERN_BREAK 0xD /* Supported */ + +#define EFFECT_EXTENDED 0xE +#define EFFECT_E_FINE_PORTA_UP 0x1 /* Supported */ +#define EFFECT_E_FINE_PORTA_DOWN 0x2 /* Supported */ +#define EFFECT_E_GLISSANDO_CTRL 0x3 /* - TO BE DONE - */ +#define EFFECT_E_VIBRATO_WAVEFORM 0x4 /* - TO BE DONE - */ +#define EFFECT_E_SET_FINETUNE 0x5 /* - TO BE DONE - */ +#define EFFECT_E_PATTERN_LOOP 0x6 /* Supported */ +#define EFFECT_E_TREMOLO_WAVEFORM 0x7 /* - TO BE DONE - */ +#define EFFECT_E_SET_PANNING_2 0x8 /* - TO BE DONE - */ +#define EFFECT_E_RETRIGGER_NOTE 0x9 /* - TO BE DONE - */ +#define EFFECT_E_FINE_VOLSLIDE_UP 0xA /* Supported */ +#define EFFECT_E_FINE_VOLSLIDE_DOWN 0xB /* Supported */ +#define EFFECT_E_NOTE_CUT 0xC /* Supported */ +#define EFFECT_E_NOTE_DELAY 0xD /* - TO BE DONE - */ +#define EFFECT_E_PATTERN_DELAY 0xE /* Supported */ +#define EFFECT_E_INVERT_LOOP 0xF /* - TO BE DONE - */ +#define EFFECT_SET_SPEED 0xF0 /* Supported */ +#define EFFECT_SET_TEMPO 0xF2 /* Supported */ + +#define PERIOD_TABLE_LENGTH MAXNOTES +#define FULL_PERIOD_TABLE_LENGTH (PERIOD_TABLE_LENGTH * 8) + +static const short periodtable[] = {27392, 25856, 24384, 23040, 21696, 20480, 19328, 18240, 17216, 16256, 15360, 14496, 13696, 12928, 12192, 11520, 10848, 10240, 9664, 9120, 8606, 8128, 7680, 7248, 6848, 6464, 6096, 5760, 5424, 5120, 4832, 4560, 4304, 4064, 3840, 3624, 3424, 3232, 3048, 2880, 2712, 2560, 2416, 2280, 2152, 2032, 1920, 1812, 1712, 1616, 1524, 1440, 1356, 1280, 1208, 1140, 1076, 1016, 960, 906, 856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453, 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226, 214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113, 107, 101, 95, 90, 85, 80, 75, 71, 67, 63, 60, 56, 53, 50, 47, 45, 42, 40, 37, 35, 33, 31, 30, 28, 27, 25, 24, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 13, 12, 11, 11, 10, 9, 9, 8, 8, 7, 7}; + +static const short sintable[] = {0, 24, 49, 74, 97, 120, 141, 161, 180, 197, 212, 224, 235, 244, 250, 253, 255, 253, 250, 244, 235, 224, 212, 197, 180, 161, 141, 120, 97, 74, 49, 24}; + +typedef struct modtype_ { + unsigned char signature[5]; + int numberofchannels; +} modtype; + +modtype modlist[] = {{"M!K!", 4}, {"M.K.", 4}, {"FLT4", 4}, {"FLT8", 8}, {"4CHN", 4}, {"6CHN", 6}, {"8CHN", 8}, {"10CH", 10}, {"12CH", 12}, {"14CH", 14}, {"16CH", 16}, {"18CH", 18}, {"20CH", 20}, {"22CH", 22}, {"24CH", 24}, {"26CH", 26}, {"28CH", 28}, {"30CH", 30}, {"32CH", 32}, {"", 0}}; + +static void memcopy(void* dest, void* source, unsigned long size) { + unsigned long i; + unsigned char *d, *s; + + d = (unsigned char*)dest; + s = (unsigned char*)source; + for(i = 0; i < size; i++) { + d[i] = s[i]; + } +} + +static void memclear(void* dest, unsigned char value, unsigned long size) { + unsigned long i; + unsigned char* d; + + d = (unsigned char*)dest; + for(i = 0; i < size; i++) { + d[i] = value; + } +} + +static int memcompare(unsigned char* buf1, unsigned char* buf2, unsigned int size) { + unsigned int i; + + i = 0; + + while(i < size) { + if(buf1[i] != buf2[i]) { + return 0; + } + i++; + } + + return 1; +} + +static int getnote(jar_mod_context_t* mod, unsigned short period, int finetune) { + int i; + + for(i = 0; i < FULL_PERIOD_TABLE_LENGTH; i++) { + if(period >= mod->fullperiod[i]) { + return i; + } + } + + return MAXNOTES; +} + +static void worknote(note* nptr, channel* cptr, char t, jar_mod_context_t* mod) { + muint sample, period, effect, operiod; + muint curnote, arpnote; + + sample = (nptr->sampperiod & 0xF0) | (nptr->sampeffect >> 4); + period = ((nptr->sampperiod & 0xF) << 8) | nptr->period; + effect = ((nptr->sampeffect & 0xF) << 8) | nptr->effect; + + operiod = cptr->period; + + if(period || sample) { + if(sample && sample < 32) { + cptr->sampnum = sample - 1; + } + + if(period || sample) { + cptr->sampdata = (char*)mod->sampledata[cptr->sampnum]; + cptr->length = mod->song.samples[cptr->sampnum].length; + cptr->reppnt = mod->song.samples[cptr->sampnum].reppnt; + cptr->replen = mod->song.samples[cptr->sampnum].replen; + + cptr->finetune = (mod->song.samples[cptr->sampnum].finetune) & 0xF; + + if(effect >> 8 != 4 && effect >> 8 != 6) { + cptr->vibraperiod = 0; + cptr->vibrapointeur = 0; + } + } + + if((sample != 0) && ((effect >> 8) != EFFECT_VOLSLIDE_TONEPORTA)) { + cptr->volume = mod->song.samples[cptr->sampnum].volume; + cptr->volumeslide = 0; + } + + if(((effect >> 8) != EFFECT_TONE_PORTAMENTO && (effect >> 8) != EFFECT_VOLSLIDE_TONEPORTA)) { + if(period != 0) cptr->samppos = 0; + } + + cptr->decalperiod = 0; + if(period) { + if(cptr->finetune) { + if(cptr->finetune <= 7) { + period = mod->fullperiod[getnote(mod, period, 0) + cptr->finetune]; + } else { + period = mod->fullperiod[getnote(mod, period, 0) - (16 - (cptr->finetune))]; + } + } + + cptr->period = period; + } + } + + cptr->effect = 0; + cptr->parameffect = 0; + cptr->effect_code = effect; + + switch(effect >> 8) { + case EFFECT_ARPEGGIO: + /* + [0]: Arpeggio + Where [0][x][y] means "play note, note+x semitones, note+y + semitones, then return to original note". The fluctuations are + carried out evenly spaced in one pattern division. They are usually + used to simulate chords, but this doesn't work too well. They are + also used to produce heavy vibrato. A major chord is when x=4, y=7. + A minor chord is when x=3, y=7. + */ + + if(effect & 0xff) { + cptr->effect = EFFECT_ARPEGGIO; + cptr->parameffect = effect & 0xff; + + cptr->ArpIndex = 0; + + curnote = getnote(mod, cptr->period, cptr->finetune); + + cptr->Arpperiods[0] = cptr->period; + + arpnote = curnote + (((cptr->parameffect >> 4) & 0xF) * 8); + if(arpnote >= FULL_PERIOD_TABLE_LENGTH) arpnote = FULL_PERIOD_TABLE_LENGTH - 1; + + cptr->Arpperiods[1] = mod->fullperiod[arpnote]; + + arpnote = curnote + (((cptr->parameffect) & 0xF) * 8); + if(arpnote >= FULL_PERIOD_TABLE_LENGTH) arpnote = FULL_PERIOD_TABLE_LENGTH - 1; + + cptr->Arpperiods[2] = mod->fullperiod[arpnote]; + } + break; + + case EFFECT_PORTAMENTO_UP: + /* + [1]: Slide up + Where [1][x][y] means "smoothly decrease the period of current + sample by x*16+y after each tick in the division". The + ticks/division are set with the 'set speed' effect (see below). If + the period of the note being played is z, then the final period + will be z - (x*16 + y)*(ticks - 1). As the slide rate depends on + the speed, changing the speed will change the slide. You cannot + slide beyond the note B3 (period 113). + */ + + cptr->effect = EFFECT_PORTAMENTO_UP; + cptr->parameffect = effect & 0xff; + break; + + case EFFECT_PORTAMENTO_DOWN: + /* + [2]: Slide down + Where [2][x][y] means "smoothly increase the period of current + sample by x*16+y after each tick in the division". Similar to [1], + but lowers the pitch. You cannot slide beyond the note C1 (period + 856). + */ + + cptr->effect = EFFECT_PORTAMENTO_DOWN; + cptr->parameffect = effect & 0xff; + break; + + case EFFECT_TONE_PORTAMENTO: + /* + [3]: Slide to note + Where [3][x][y] means "smoothly change the period of current sample + by x*16+y after each tick in the division, never sliding beyond + current period". The period-length in this channel's division is a + parameter to this effect, and hence is not played. Sliding to a + note is similar to effects [1] and [2], but the slide will not go + beyond the given period, and the direction is implied by that + period. If x and y are both 0, then the old slide will continue. + */ + + cptr->effect = EFFECT_TONE_PORTAMENTO; + if((effect & 0xff) != 0) { + cptr->portaspeed = (short)(effect & 0xff); + } + + if(period != 0) { + cptr->portaperiod = period; + cptr->period = operiod; + } + break; + + case EFFECT_VIBRATO: + /* + [4]: Vibrato + Where [4][x][y] means "oscillate the sample pitch using a + particular waveform with amplitude y/16 semitones, such that (x * + ticks)/64 cycles occur in the division". The waveform is set using + effect [14][4]. By placing vibrato effects on consecutive + divisions, the vibrato effect can be maintained. If either x or y + are 0, then the old vibrato values will be used. + */ + + cptr->effect = EFFECT_VIBRATO; + if((effect & 0x0F) != 0) /* Depth continue or change ? */ + cptr->vibraparam = (cptr->vibraparam & 0xF0) | (effect & 0x0F); + if((effect & 0xF0) != 0) /* Speed continue or change ? */ + cptr->vibraparam = (cptr->vibraparam & 0x0F) | (effect & 0xF0); + + break; + + case EFFECT_VOLSLIDE_TONEPORTA: + /* + [5]: Continue 'Slide to note', but also do Volume slide + Where [5][x][y] means "either slide the volume up x*(ticks - 1) or + slide the volume down y*(ticks - 1), at the same time as continuing + the last 'Slide to note'". It is illegal for both x and y to be + non-zero. You cannot slide outside the volume range 0..64. The + period-length in this channel's division is a parameter to this + effect, and hence is not played. + */ + + if(period != 0) { + cptr->portaperiod = period; + cptr->period = operiod; + } + + cptr->effect = EFFECT_VOLSLIDE_TONEPORTA; + if((effect & 0xFF) != 0) cptr->volumeslide = (effect & 0xFF); + + break; + + case EFFECT_VOLSLIDE_VIBRATO: + /* + [6]: Continue 'Vibrato', but also do Volume slide + Where [6][x][y] means "either slide the volume up x*(ticks - 1) or + slide the volume down y*(ticks - 1), at the same time as continuing + the last 'Vibrato'". It is illegal for both x and y to be non-zero. + You cannot slide outside the volume range 0..64. + */ + + cptr->effect = EFFECT_VOLSLIDE_VIBRATO; + if((effect & 0xFF) != 0) cptr->volumeslide = (effect & 0xFF); + break; + + case EFFECT_SET_OFFSET: + /* + [9]: Set sample offset + Where [9][x][y] means "play the sample from offset x*4096 + y*256". + The offset is measured in words. If no sample is given, yet one is + still playing on this channel, it should be retriggered to the new + offset using the current volume. + */ + + cptr->samppos = ((effect >> 4) * 4096) + ((effect & 0xF) * 256); + + break; + + case EFFECT_VOLUME_SLIDE: + /* + [10]: Volume slide + Where [10][x][y] means "either slide the volume up x*(ticks - 1) or + slide the volume down y*(ticks - 1)". If both x and y are non-zero, + then the y value is ignored (assumed to be 0). You cannot slide + outside the volume range 0..64. + */ + + cptr->effect = EFFECT_VOLUME_SLIDE; + cptr->volumeslide = (effect & 0xFF); + break; + + case EFFECT_JUMP_POSITION: + /* + [11]: Position Jump + Where [11][x][y] means "stop the pattern after this division, and + continue the song at song-position x*16+y". This shifts the + 'pattern-cursor' in the pattern table (see above). Legal values for + x*16+y are from 0 to 127. + */ + + mod->tablepos = (effect & 0xFF); + if(mod->tablepos >= mod->song.length) { + mod->tablepos = 0; + } + mod->patternpos = 0; + mod->jump_loop_effect = 1; + + break; + + case EFFECT_SET_VOLUME: + /* + [12]: Set volume + Where [12][x][y] means "set current sample's volume to x*16+y". + Legal volumes are 0..64. + */ + + cptr->volume = (effect & 0xFF); + break; + + case EFFECT_PATTERN_BREAK: + /* + [13]: Pattern Break + Where [13][x][y] means "stop the pattern after this division, and + continue the song at the next pattern at division x*10+y" (the 10 + is not a typo). Legal divisions are from 0 to 63 (note Protracker + exception above). + */ + + mod->patternpos = (((effect >> 4) & 0xF) * 10 + (effect & 0xF)) * mod->number_of_channels; + mod->jump_loop_effect = 1; + mod->tablepos++; + if(mod->tablepos >= mod->song.length) { + mod->tablepos = 0; + } + + break; + + case EFFECT_EXTENDED: + switch((effect >> 4) & 0xF) { + case EFFECT_E_FINE_PORTA_UP: + /* + [14][1]: Fineslide up + Where [14][1][x] means "decrement the period of the current sample + by x". The incrementing takes place at the beginning of the + division, and hence there is no actual sliding. You cannot slide + beyond the note B3 (period 113). + */ + + cptr->period -= (effect & 0xF); + if(cptr->period < 113) cptr->period = 113; + break; + + case EFFECT_E_FINE_PORTA_DOWN: + /* + [14][2]: Fineslide down + Where [14][2][x] means "increment the period of the current sample + by x". Similar to [14][1] but shifts the pitch down. You cannot + slide beyond the note C1 (period 856). + */ + + cptr->period += (effect & 0xF); + if(cptr->period > 856) cptr->period = 856; + break; + + case EFFECT_E_FINE_VOLSLIDE_UP: + /* + [14][10]: Fine volume slide up + Where [14][10][x] means "increment the volume of the current sample + by x". The incrementing takes place at the beginning of the + division, and hence there is no sliding. You cannot slide beyond + volume 64. + */ + + cptr->volume += (effect & 0xF); + if(cptr->volume > 64) cptr->volume = 64; + break; + + case EFFECT_E_FINE_VOLSLIDE_DOWN: + /* + [14][11]: Fine volume slide down + Where [14][11][x] means "decrement the volume of the current sample + by x". Similar to [14][10] but lowers volume. You cannot slide + beyond volume 0. + */ + + cptr->volume -= (effect & 0xF); + if(cptr->volume > 200) cptr->volume = 0; + break; + + case EFFECT_E_PATTERN_LOOP: + /* + [14][6]: Loop pattern + Where [14][6][x] means "set the start of a loop to this division if + x is 0, otherwise after this division, jump back to the start of a + loop and play it another x times before continuing". If the start + of the loop was not set, it will default to the start of the + current pattern. Hence 'loop pattern' cannot be performed across + multiple patterns. Note that loops do not support nesting, and you + may generate an infinite loop if you try to nest 'loop pattern's. + */ + + if(effect & 0xF) { + if(cptr->patternloopcnt) { + cptr->patternloopcnt--; + if(cptr->patternloopcnt) { + mod->patternpos = cptr->patternloopstartpoint; + mod->jump_loop_effect = 1; + } else { + cptr->patternloopstartpoint = mod->patternpos; + } + } else { + cptr->patternloopcnt = (effect & 0xF); + mod->patternpos = cptr->patternloopstartpoint; + mod->jump_loop_effect = 1; + } + } else /* Start point */ + { + cptr->patternloopstartpoint = mod->patternpos; + } + + break; + + case EFFECT_E_PATTERN_DELAY: + /* + [14][14]: Delay pattern + Where [14][14][x] means "after this division there will be a delay + equivalent to the time taken to play x divisions after which the + pattern will be resumed". The delay only relates to the + interpreting of new divisions, and all effects and previous notes + continue during delay. + */ + + mod->patterndelay = (effect & 0xF); + break; + + case EFFECT_E_NOTE_CUT: + /* + [14][12]: Cut sample + Where [14][12][x] means "after the current sample has been played + for x ticks in this division, its volume will be set to 0". This + implies that if x is 0, then you will not hear any of the sample. + If you wish to insert "silence" in a pattern, it is better to use a + "silence"-sample (see above) due to the lack of proper support for + this effect. + */ + cptr->effect = EFFECT_E_NOTE_CUT; + cptr->cut_param = (effect & 0xF); + if(!cptr->cut_param) cptr->volume = 0; + break; + + default: + + break; + } + break; + + case 0xF: + /* + [15]: Set speed + Where [15][x][y] means "set speed to x*16+y". Though it is nowhere + near that simple. Let z = x*16+y. Depending on what values z takes, + different units of speed are set, there being two: ticks/division + and beats/minute (though this one is only a label and not strictly + true). If z=0, then what should technically happen is that the + module stops, but in practice it is treated as if z=1, because + there is already a method for stopping the module (running out of + patterns). If z<=32, then it means "set ticks/division to z" + otherwise it means "set beats/minute to z" (convention says that + this should read "If z<32.." but there are some composers out there + that defy conventions). Default values are 6 ticks/division, and + 125 beats/minute (4 divisions = 1 beat). The beats/minute tag is + only meaningful for 6 ticks/division. To get a more accurate view + of how things work, use the following formula: + 24 * beats/minute + divisions/minute = ----------------- + ticks/division + Hence divisions/minute range from 24.75 to 6120, eg. to get a value + of 2000 divisions/minute use 3 ticks/division and 250 beats/minute. + If multiple "set speed" effects are performed in a single division, + the ones on higher-numbered channels take precedence over the ones + on lower-numbered channels. This effect has a large number of + different implementations, but the one described here has the + widest usage. + */ + + if((effect & 0xFF) < 0x21) { + if(effect & 0xFF) { + mod->song.speed = effect & 0xFF; + mod->patternticksaim = (long)mod->song.speed * ((mod->playrate * 5) / (((long)2 * (long)mod->bpm))); + } + } + + if((effect & 0xFF) >= 0x21) { + /* HZ = 2 * BPM / 5 */ + mod->bpm = effect & 0xFF; + mod->patternticksaim = (long)mod->song.speed * ((mod->playrate * 5) / (((long)2 * (long)mod->bpm))); + } + + break; + + default: + /* Unsupported effect */ + break; + } +} + +static void workeffect(note* nptr, channel* cptr) { + switch(cptr->effect) { + case EFFECT_ARPEGGIO: + + if(cptr->parameffect) { + cptr->decalperiod = cptr->period - cptr->Arpperiods[cptr->ArpIndex]; + + cptr->ArpIndex++; + if(cptr->ArpIndex > 2) cptr->ArpIndex = 0; + } + break; + + case EFFECT_PORTAMENTO_UP: + + if(cptr->period) { + cptr->period -= cptr->parameffect; + + if(cptr->period < 113 || cptr->period > 20000) cptr->period = 113; + } + + break; + + case EFFECT_PORTAMENTO_DOWN: + + if(cptr->period) { + cptr->period += cptr->parameffect; + + if(cptr->period > 20000) cptr->period = 20000; + } + + break; + + case EFFECT_VOLSLIDE_TONEPORTA: + case EFFECT_TONE_PORTAMENTO: + + if(cptr->period && (cptr->period != cptr->portaperiod) && cptr->portaperiod) { + if(cptr->period > cptr->portaperiod) { + if(cptr->period - cptr->portaperiod >= cptr->portaspeed) { + cptr->period -= cptr->portaspeed; + } else { + cptr->period = cptr->portaperiod; + } + } else { + if(cptr->portaperiod - cptr->period >= cptr->portaspeed) { + cptr->period += cptr->portaspeed; + } else { + cptr->period = cptr->portaperiod; + } + } + + if(cptr->period == cptr->portaperiod) { + /* If the slide is over, don't let it to be retriggered. */ + cptr->portaperiod = 0; + } + } + + if(cptr->effect == EFFECT_VOLSLIDE_TONEPORTA) { + if(cptr->volumeslide > 0x0F) { + cptr->volume = cptr->volume + (cptr->volumeslide >> 4); + + if(cptr->volume > 63) cptr->volume = 63; + } else { + cptr->volume = cptr->volume - (cptr->volumeslide); + + if(cptr->volume > 63) cptr->volume = 0; + } + } + break; + + case EFFECT_VOLSLIDE_VIBRATO: + case EFFECT_VIBRATO: + + cptr->vibraperiod = ((cptr->vibraparam & 0xF) * sintable[cptr->vibrapointeur & 0x1F]) >> 7; + + if(cptr->vibrapointeur > 31) cptr->vibraperiod = -cptr->vibraperiod; + + cptr->vibrapointeur = (cptr->vibrapointeur + (((cptr->vibraparam >> 4)) & 0xf)) & 0x3F; + + if(cptr->effect == EFFECT_VOLSLIDE_VIBRATO) { + if(cptr->volumeslide > 0xF) { + cptr->volume = cptr->volume + (cptr->volumeslide >> 4); + + if(cptr->volume > 64) cptr->volume = 64; + } else { + cptr->volume = cptr->volume - cptr->volumeslide; + + if(cptr->volume > 64) cptr->volume = 0; + } + } + + break; + + case EFFECT_VOLUME_SLIDE: + + if(cptr->volumeslide > 0xF) { + cptr->volume += (cptr->volumeslide >> 4); + + if(cptr->volume > 64) cptr->volume = 64; + } else { + cptr->volume -= (cptr->volumeslide & 0xf); + + if(cptr->volume > 64) cptr->volume = 0; + } + break; + + case EFFECT_E_NOTE_CUT: + if(cptr->cut_param) cptr->cut_param--; + + if(!cptr->cut_param) cptr->volume = 0; + break; + + default: + break; + } +} + +gf_bool_t jar_mod_init(jar_mod_context_t* modctx) { + muint i, j; + + if(modctx) { + memclear(modctx, 0, sizeof(jar_mod_context_t)); + modctx->playrate = DEFAULT_SAMPLE_RATE; + modctx->stereo = 1; + modctx->stereo_separation = 1; + modctx->bits = 16; + modctx->filter = 1; + + for(i = 0; i < PERIOD_TABLE_LENGTH - 1; i++) { + for(j = 0; j < 8; j++) { + modctx->fullperiod[(i * 8) + j] = periodtable[i] - (((periodtable[i] - periodtable[i + 1]) / 8) * j); + } + } + + return 1; + } + + return 0; +} + +gf_bool_t jar_mod_setcfg(jar_mod_context_t* modctx, int samplerate, int bits, int stereo, int stereo_separation, int filter) { + if(modctx) { + modctx->playrate = samplerate; + + if(stereo) modctx->stereo = 1; + else + modctx->stereo = 0; + + if(stereo_separation < 4) { + modctx->stereo_separation = stereo_separation; + } + + if(bits == 8 || bits == 16) modctx->bits = bits; + else + modctx->bits = 16; + + if(filter) modctx->filter = 1; + else + modctx->filter = 0; + + return 1; + } + + return 0; +} + +/* make certain that mod_data stays in memory while playing */ +static gf_bool_t jar_mod_load(jar_mod_context_t* modctx, void* mod_data, int mod_data_size) { + muint i, max; + unsigned short t; + sample* sptr; + unsigned char *modmemory, *endmodmemory; + + modmemory = (unsigned char*)mod_data; + endmodmemory = modmemory + mod_data_size; + + if(modmemory) { + if(modctx) { + memcopy(&(modctx->song.title), modmemory, 1084); + + i = 0; + modctx->number_of_channels = 0; + while(modlist[i].numberofchannels) { + if(memcompare(modctx->song.signature, modlist[i].signature, 4)) { + modctx->number_of_channels = modlist[i].numberofchannels; + } + + i++; + } + + if(!modctx->number_of_channels) { + /** + * 15 Samples modules support + * Shift the whole datas to make it look likes a standard 4 channels mod. + */ + memcopy(&(modctx->song.signature), "M.K.", 4); + memcopy(&(modctx->song.length), &(modctx->song.samples[15]), 130); + memclear(&(modctx->song.samples[15]), 0, 480); + modmemory += 600; + modctx->number_of_channels = 4; + } else { + modmemory += 1084; + } + + if(modmemory >= endmodmemory) return 0; /* End passed ? - Probably a bad file ! */ + + /* Patterns loading */ + for(i = max = 0; i < 128; i++) { + while(max <= modctx->song.patterntable[i]) { + modctx->patterndata[max] = (note*)modmemory; + modmemory += (256 * modctx->number_of_channels); + max++; + + if(modmemory >= endmodmemory) return 0; /* End passed ? - Probably a bad file ! */ + } + } + + for(i = 0; i < 31; i++) modctx->sampledata[i] = 0; + + /* Samples loading */ + for(i = 0, sptr = modctx->song.samples; i < 31; i++, sptr++) { + unsigned short num = 1; + if(1 == *(unsigned char*)&num) { + /* Little endian */ + t = (sptr->length & 0xFF00) >> 8 | (sptr->length & 0xFF) << 8; + sptr->length = t * 2; + + t = (sptr->reppnt & 0xFF00) >> 8 | (sptr->reppnt & 0xFF) << 8; + sptr->reppnt = t * 2; + + t = (sptr->replen & 0xFF00) >> 8 | (sptr->replen & 0xFF) << 8; + sptr->replen = t * 2; + } else { + /* Big endian */ + sptr->length *= 2; + sptr->reppnt *= 2; + sptr->replen *= 2; + } + + if(sptr->length == 0) continue; + + modctx->sampledata[i] = (char*)modmemory; + modmemory += sptr->length; + + if(sptr->replen + sptr->reppnt > sptr->length) sptr->replen = sptr->length - sptr->reppnt; + + if(modmemory > endmodmemory) return 0; /* End passed ? - Probably a bad file ! */ + } + + /* States init */ + + modctx->tablepos = 0; + modctx->patternpos = 0; + modctx->song.speed = 6; + modctx->bpm = 125; + modctx->samplenb = 0; + + modctx->patternticks = (((long)modctx->song.speed * modctx->playrate * 5) / (2 * modctx->bpm)) + 1; + modctx->patternticksaim = ((long)modctx->song.speed * modctx->playrate * 5) / (2 * modctx->bpm); + + modctx->sampleticksconst = 3546894UL / modctx->playrate; /*8448*428/playrate; */ + + for(i = 0; i < modctx->number_of_channels; i++) { + modctx->channels[i].volume = 0; + modctx->channels[i].period = 0; + } + + modctx->mod_loaded = 1; + + return 1; + } + } + + return 0; +} + +void jar_mod_fillbuffer(jar_mod_context_t* modctx, short* outbuffer, unsigned long nbsample, jar_mod_tracker_buffer_state* trkbuf) { + unsigned long i, j; + unsigned long k; + unsigned char c; + unsigned int state_remaining_steps; + int l, r; + int ll, lr; + int tl, tr; + short finalperiod; + note* nptr; + channel* cptr; + + if(modctx && outbuffer) { + if(modctx->mod_loaded) { + state_remaining_steps = 0; + + if(trkbuf) { + trkbuf->cur_rd_index = 0; + + memcopy(trkbuf->name, modctx->song.title, sizeof(modctx->song.title)); + + for(i = 0; i < 31; i++) { + memcopy(trkbuf->instruments[i].name, modctx->song.samples[i].name, sizeof(trkbuf->instruments[i].name)); + } + } + + ll = modctx->last_l_sample; + lr = modctx->last_r_sample; + + for(i = 0; i < nbsample; i++) { + /*---------------------------------------*/ + if(modctx->patternticks++ > modctx->patternticksaim) { + if(!modctx->patterndelay) { + nptr = modctx->patterndata[modctx->song.patterntable[modctx->tablepos]]; + nptr = nptr + modctx->patternpos; + cptr = modctx->channels; + + modctx->patternticks = 0; + modctx->patterntickse = 0; + + for(c = 0; c < modctx->number_of_channels; c++) { + worknote((note*)(nptr + c), (channel*)(cptr + c), (char)(c + 1), modctx); + } + + if(!modctx->jump_loop_effect) modctx->patternpos += modctx->number_of_channels; + else + modctx->jump_loop_effect = 0; + + if(modctx->patternpos == 64 * modctx->number_of_channels) { + modctx->tablepos++; + modctx->patternpos = 0; + if(modctx->tablepos >= modctx->song.length) { + modctx->tablepos = 0; + modctx->loopcount++; /* count next loop */ + } + } + } else { + modctx->patterndelay--; + modctx->patternticks = 0; + modctx->patterntickse = 0; + } + } + + if(modctx->patterntickse++ > (modctx->patternticksaim / modctx->song.speed)) { + nptr = modctx->patterndata[modctx->song.patterntable[modctx->tablepos]]; + nptr = nptr + modctx->patternpos; + cptr = modctx->channels; + + for(c = 0; c < modctx->number_of_channels; c++) { + workeffect(nptr + c, cptr + c); + } + + modctx->patterntickse = 0; + } + + /*---------------------------------------*/ + + if(trkbuf && !state_remaining_steps) { + if(trkbuf->nb_of_state < trkbuf->nb_max_of_state) { + memclear(&trkbuf->track_state_buf[trkbuf->nb_of_state], 0, sizeof(tracker_state)); + } + } + + l = 0; + r = 0; + + for(j = 0, cptr = modctx->channels; j < modctx->number_of_channels; j++, cptr++) { + if(cptr->period != 0) { + finalperiod = cptr->period - cptr->decalperiod - cptr->vibraperiod; + if(finalperiod) { + cptr->samppos += ((modctx->sampleticksconst << 10) / finalperiod); + } + + cptr->ticks++; + + if(cptr->replen <= 2) { + if((cptr->samppos >> 10) >= (cptr->length)) { + cptr->length = 0; + cptr->reppnt = 0; + + if(cptr->length) cptr->samppos = cptr->samppos % (((unsigned long)cptr->length) << 10); + else + cptr->samppos = 0; + } + } else { + if((cptr->samppos >> 10) >= (unsigned long)(cptr->replen + cptr->reppnt)) { + cptr->samppos = ((unsigned long)(cptr->reppnt) << 10) + (cptr->samppos % ((unsigned long)(cptr->replen + cptr->reppnt) << 10)); + } + } + + k = cptr->samppos >> 10; + + if(cptr->sampdata != 0 && (((j & 3) == 1) || ((j & 3) == 2))) { + r += (cptr->sampdata[k] * cptr->volume); + } + + if(cptr->sampdata != 0 && (((j & 3) == 0) || ((j & 3) == 3))) { + l += (cptr->sampdata[k] * cptr->volume); + } + + if(trkbuf && !state_remaining_steps) { + if(trkbuf->nb_of_state < trkbuf->nb_max_of_state) { + trkbuf->track_state_buf[trkbuf->nb_of_state].number_of_tracks = modctx->number_of_channels; + trkbuf->track_state_buf[trkbuf->nb_of_state].buf_index = i; + trkbuf->track_state_buf[trkbuf->nb_of_state].cur_pattern = modctx->song.patterntable[modctx->tablepos]; + trkbuf->track_state_buf[trkbuf->nb_of_state].cur_pattern_pos = modctx->patternpos / modctx->number_of_channels; + trkbuf->track_state_buf[trkbuf->nb_of_state].cur_pattern_table_pos = modctx->tablepos; + trkbuf->track_state_buf[trkbuf->nb_of_state].bpm = modctx->bpm; + trkbuf->track_state_buf[trkbuf->nb_of_state].speed = modctx->song.speed; + trkbuf->track_state_buf[trkbuf->nb_of_state].tracks[j].cur_effect = cptr->effect_code; + trkbuf->track_state_buf[trkbuf->nb_of_state].tracks[j].cur_parameffect = cptr->parameffect; + trkbuf->track_state_buf[trkbuf->nb_of_state].tracks[j].cur_period = finalperiod; + trkbuf->track_state_buf[trkbuf->nb_of_state].tracks[j].cur_volume = cptr->volume; + trkbuf->track_state_buf[trkbuf->nb_of_state].tracks[j].instrument_number = (unsigned char)cptr->sampnum; + } + } + } + } + + if(trkbuf && !state_remaining_steps) { + state_remaining_steps = trkbuf->sample_step; + + if(trkbuf->nb_of_state < trkbuf->nb_max_of_state) trkbuf->nb_of_state++; + } else { + state_remaining_steps--; + } + + tl = (short)l; + tr = (short)r; + + if(modctx->filter) { + /* Filter */ + l = (l + ll) >> 1; + r = (r + lr) >> 1; + } + + if(modctx->stereo_separation == 1) { + /* Left & Right Stereo panning */ + l = (l + (r >> 1)); + r = (r + (l >> 1)); + } + + /* Level limitation */ + if(l > 32767) l = 32767; + if(l < -32768) l = -32768; + if(r > 32767) r = 32767; + if(r < -32768) r = -32768; + + /* Store the final sample. */ + outbuffer[(i * 2)] = l; + outbuffer[(i * 2) + 1] = r; + + ll = tl; + lr = tr; + } + + modctx->last_l_sample = ll; + modctx->last_r_sample = lr; + + modctx->samplenb = modctx->samplenb + nbsample; + } else { + for(i = 0; i < nbsample; i++) { + /* Mod not loaded. Return blank buffer. */ + outbuffer[(i * 2)] = 0; + outbuffer[(i * 2) + 1] = 0; + } + + if(trkbuf) { + trkbuf->nb_of_state = 0; + trkbuf->cur_rd_index = 0; + trkbuf->name[0] = 0; + memclear(trkbuf->track_state_buf, 0, sizeof(tracker_state) * trkbuf->nb_max_of_state); + memclear(trkbuf->instruments, 0, sizeof(trkbuf->instruments)); + } + } + } +} + +/*resets internals for mod context*/ +static gf_bool_t jar_mod_reset(jar_mod_context_t* modctx) { + if(modctx) { + memclear(&modctx->song, 0, sizeof(modctx->song)); + memclear(&modctx->sampledata, 0, sizeof(modctx->sampledata)); + memclear(&modctx->patterndata, 0, sizeof(modctx->patterndata)); + modctx->tablepos = 0; + modctx->patternpos = 0; + modctx->patterndelay = 0; + modctx->jump_loop_effect = 0; + modctx->bpm = 0; + modctx->patternticks = 0; + modctx->patterntickse = 0; + modctx->patternticksaim = 0; + modctx->sampleticksconst = 0; + modctx->samplenb = 0; + memclear(modctx->channels, 0, sizeof(modctx->channels)); + modctx->number_of_channels = 0; + modctx->mod_loaded = 0; + modctx->last_r_sample = 0; + modctx->last_l_sample = 0; + + return jar_mod_init(modctx); + } + return 0; +} + +void jar_mod_unload(jar_mod_context_t* modctx) { + if(modctx) { + if(modctx->modfile) { + JARMOD_FREE(modctx->modfile); + modctx->modfile = 0; + modctx->modfilesize = 0; + modctx->loopcount = 0; + } + jar_mod_reset(modctx); + } +} + +mulong jar_mod_load_file(jar_mod_context_t* modctx, const char* filename) { + mulong fsize = 0; + if(modctx->modfile) { + JARMOD_FREE(modctx->modfile); + modctx->modfile = 0; + } + + FILE* f = fopen(filename, "rb"); + if(f) { + fseek(f, 0, SEEK_END); + fsize = ftell(f); + fseek(f, 0, SEEK_SET); + + if(fsize && fsize < 32 * 1024 * 1024) { + modctx->modfile = JARMOD_MALLOC(fsize); + modctx->modfilesize = fsize; + memset(modctx->modfile, 0, fsize); + fread(modctx->modfile, fsize, 1, f); + fclose(f); + + if(!jar_mod_load(modctx, (void*)modctx->modfile, fsize)) fsize = 0; + } else + fsize = 0; + } + return fsize; +} + +mulong jar_mod_current_samples(jar_mod_context_t* modctx) { + if(modctx) return modctx->samplenb; + + return 0; +} + +/* Works, however it is very slow, this data should be cached to ensure it is run only once per file */ +mulong jar_mod_max_samples(jar_mod_context_t* ctx) { + mint buff[2]; + mulong len; + mulong lastcount = ctx->loopcount; + + while(ctx->loopcount <= lastcount) jar_mod_fillbuffer(ctx, buff, 1, 0); + + len = ctx->samplenb; + jar_mod_seek_start(ctx); + + return len; +} + +/* move seek_val to sample index, 0 -> jar_mod_max_samples is the range */ +void jar_mod_seek_start(jar_mod_context_t* ctx) { + if(ctx && ctx->modfile) { + muchar* ftmp = ctx->modfile; + mulong stmp = ctx->modfilesize; + muint lcnt = ctx->loopcount; + + if(jar_mod_reset(ctx)) { + jar_mod_load(ctx, ftmp, stmp); + ctx->modfile = ftmp; + ctx->modfilesize = stmp; + ctx->loopcount = lcnt; + } + } +} + +#endif /* end of JAR_MOD_IMPLEMENTATION */ +/*-------------------------------------------------------------------------------*/ + +#endif /*end of header file*/ diff --git a/engine/external/jar/jar_xm.h b/engine/external/jar/jar_xm.h new file mode 100644 index 0000000..6f616c5 --- /dev/null +++ b/engine/external/jar/jar_xm.h @@ -0,0 +1,2534 @@ +/** + * jar_xm.h - v0.01 - public domain - Joshua Reisenauer, MAR 2016 + * + * HISTORY: + * + * v0.01 2016-02-22 Setup + * + * + * 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; + * } + * + * + * LISCENSE - FOR LIBXM: + * + * Author: Romain "Artefact2" Dalmaso + * Contributor: Dan Spencer + * Repackaged into jar_xm.h By: Joshua Adam Reisenauer + * 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 of the Do What The Fuck You Want To Public + * License, Version 2, as published by Sam Hocevar. See + * http://sam.zoy.org/wtfpl/COPYING for more details. + */ + +#ifndef INCLUDE_JAR_XM_H +#define INCLUDE_JAR_XM_H + +#define JAR_XM_DEBUG 0 +#define JAR_XM_LINEAR_INTERPOLATION 1 /* speed increase with decrease in quality */ +#define JAR_XM_DEFENSIVE 1 +#define JAR_XM_RAMPING 1 + +#include +#include +#include +#include +#include + +/*-------------------------------------------------------------------------------*/ +#ifdef __cplusplus +extern "C" { +#endif + +struct jar_xm_context_s; +typedef struct jar_xm_context_s jar_xm_context_t; + +/** 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, gf_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**, const char* moddata, gf_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**, const char* moddata, size_t moddata_length, gf_uint32_t rate); + +/** Free a XM context created by jar_xm_create_context(). */ +void jar_xm_free_context(jar_xm_context_t*); + +/** 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*, float* output, size_t numsamples); + +/** Play the module, resample from 32 bit 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); + +/** Play the module, resample from 32 bit 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); + +/** 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*, gf_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. */ +gf_uint8_t jar_xm_get_loop_count(jar_xm_context_t*); + +/** Mute or unmute a channel. + * + * @note Channel numbers go from 1 to jar_xm_get_number_of_channels(...). + * + * @return whether the channel was muted. + */ +gf_bool_t jar_xm_mute_channel(jar_xm_context_t*, gf_uint16_t, gf_bool_t); + +/** Mute or unmute an instrument. + * + * @note Instrument numbers go from 1 to + * jar_xm_get_number_of_instruments(...). + * + * @return whether the instrument was muted. + */ +gf_bool_t jar_xm_mute_instrument(jar_xm_context_t*, gf_uint16_t, gf_bool_t); + +/** Get the module name as a NUL-terminated string. */ +const char* jar_xm_get_module_name(jar_xm_context_t*); + +/** Get the tracker name as a NUL-terminated string. */ +const char* jar_xm_get_tracker_name(jar_xm_context_t*); + +/** Get the number of channels. */ +gf_uint16_t jar_xm_get_number_of_channels(jar_xm_context_t*); + +/** Get the module length (in patterns). */ +gf_uint16_t jar_xm_get_module_length(jar_xm_context_t*); + +/** Get the number of patterns. */ +gf_uint16_t jar_xm_get_number_of_patterns(jar_xm_context_t*); + +/** Get the number of rows of a pattern. + * + * @note Pattern numbers go from 0 to + * jar_xm_get_number_of_patterns(...)-1. + */ +gf_uint16_t jar_xm_get_number_of_rows(jar_xm_context_t*, gf_uint16_t); + +/** Get the number of instruments. */ +gf_uint16_t jar_xm_get_number_of_instruments(jar_xm_context_t*); + +/** Get the number of samples of an instrument. + * + * @note Instrument numbers go from 1 to + * jar_xm_get_number_of_instruments(...). + */ +gf_uint16_t jar_xm_get_number_of_samples(jar_xm_context_t*, gf_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*, gf_uint16_t* bpm, gf_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*, gf_uint8_t* pattern_index, gf_uint8_t* pattern, gf_uint8_t* row, gf_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(...). + */ +gf_uint64_t jar_xm_get_latest_trigger_of_instrument(jar_xm_context_t*, gf_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. + */ +gf_uint64_t jar_xm_get_latest_trigger_of_sample(jar_xm_context_t*, gf_uint16_t instr, gf_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(...). + */ +gf_uint64_t jar_xm_get_latest_trigger_of_channel(jar_xm_context_t*, gf_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. + */ +gf_uint64_t jar_xm_get_remaining_samples(jar_xm_context_t*); + +#ifdef __cplusplus +} +#endif +/*-------------------------------------------------------------------------------*/ + +/*Function Definitions-----------------------------------------------------------*/ +#ifdef JAR_XM_IMPLEMENTATION + +#include +#include + +#if JAR_XM_DEBUG +#include +#define JX_DEBUG(fmt, ...) \ + do { \ + fprintf(stderr, "%s(): " fmt "\n", __func__, __VA_ARGS__); \ + fflush(stderr); \ + } while(0) +#else +#define JX_DEBUG(...) +#endif + +/* ----- XM constants ----- */ + +#define SAMPLE_NAME_LENGTH 22 +#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 +#define NUM_ENVELOPE_POINTS 12 +#define MAX_NUM_ROWS 256 + +#if JAR_XM_RAMPING +#define jar_xm_SAMPLE_RAMPING_POINTS 0x20 +#endif + +/* ----- 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 { + gf_uint16_t frame; + gf_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]; + gf_uint8_t num_points; + gf_uint8_t sustain_point; + gf_uint8_t loop_start_point; + gf_uint8_t loop_end_point; + gf_bool_t enabled; + gf_bool_t sustain_enabled; + gf_bool_t loop_enabled; +}; +typedef struct jar_xm_envelope_s jar_xm_envelope_t; + +struct jar_xm_sample_s { + char name[SAMPLE_NAME_LENGTH + 1]; + gf_int8_t bits; /* Either 8 or 16 */ + + gf_uint32_t length; + gf_uint32_t loop_start; + gf_uint32_t loop_length; + gf_uint32_t loop_end; + float volume; + gf_int8_t finetune; + jar_xm_loop_type_t loop_type; + float panning; + gf_int8_t relative_note; + gf_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]; + gf_uint16_t num_samples; + gf_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; + gf_uint8_t vibrato_sweep; + gf_uint8_t vibrato_depth; + gf_uint8_t vibrato_rate; + gf_uint16_t volume_fadeout; + gf_uint64_t latest_trigger; + gf_bool_t muted; + + jar_xm_sample_t* samples; +}; +typedef struct jar_xm_instrument_s jar_xm_instrument_t; + +struct jar_xm_pattern_slot_s { + gf_uint8_t note; /* 1-96, 97 = Key Off note */ + gf_uint8_t instrument; /* 1-128 */ + gf_uint8_t volume_column; + gf_uint8_t effect_type; + gf_uint8_t effect_param; +}; +typedef struct jar_xm_pattern_slot_s jar_xm_pattern_slot_t; + +struct jar_xm_pattern_s { + gf_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]; + gf_uint16_t length; + gf_uint16_t restart_position; + gf_uint16_t num_channels; + gf_uint16_t num_patterns; + gf_uint16_t num_instruments; + jar_xm_frequency_type_t frequency_type; + gf_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; + gf_bool_t ping; /* For ping-pong samples: gf_true is -->, gf_false is <-- */ + + float volume; /* Ideally between 0 (muted) and 1 (loudest) */ + float panning; /* Between 0 (left) and 1 (right); 0.5 is centered */ + + gf_uint16_t autovibrato_ticks; + + gf_bool_t sustained; + float fadeout_volume; + float volume_envelope_volume; + float panning_envelope_panning; + gf_uint16_t volume_envelope_frame_count; + gf_uint16_t panning_envelope_frame_count; + + float autovibrato_note_offset; + + gf_bool_t arp_in_progress; + gf_uint8_t arp_note_offset; + gf_uint8_t volume_slide_param; + gf_uint8_t fine_volume_slide_param; + gf_uint8_t global_volume_slide_param; + gf_uint8_t panning_slide_param; + gf_uint8_t portamento_up_param; + gf_uint8_t portamento_down_param; + gf_uint8_t fine_portamento_up_param; + gf_uint8_t fine_portamento_down_param; + gf_uint8_t extra_fine_portamento_up_param; + gf_uint8_t extra_fine_portamento_down_param; + gf_uint8_t tone_portamento_param; + float tone_portamento_target_period; + gf_uint8_t multi_retrig_param; + gf_uint8_t note_delay_param; + gf_uint8_t pattern_loop_origin; /* Where to restart a E6y loop */ + gf_uint8_t pattern_loop_count; /* How many loop passes have been done */ + gf_bool_t vibrato_in_progress; + jar_xm_waveform_type_t vibrato_waveform; + gf_bool_t vibrato_waveform_retrigger; /* True if a new note retriggers the waveform */ + gf_uint8_t vibrato_param; + gf_uint16_t vibrato_ticks; /* Position in the waveform */ + float vibrato_note_offset; + jar_xm_waveform_type_t tremolo_waveform; + gf_bool_t tremolo_waveform_retrigger; + gf_uint8_t tremolo_param; + gf_uint8_t tremolo_ticks; + float tremolo_volume; + gf_uint8_t tremor_param; + gf_bool_t tremor_on; + + gf_uint64_t latest_trigger; + gf_bool_t muted; + +#if JAR_XM_RAMPING + /* These values are updated at the end of each tick, to save + * a couple of float operations on every generated sample. */ + float target_panning; + float target_volume; + + unsigned long frame_count; + float end_of_previous_sample[jar_xm_SAMPLE_RAMPING_POINTS]; +#endif + + float actual_panning; + float actual_volume; +}; +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; + gf_uint32_t rate; + + gf_uint16_t tempo; + gf_uint16_t bpm; + float global_volume; + float amplification; + +#if JAR_XM_RAMPING + /* 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. */ + float volume_ramp; + float panning_ramp; /* Same for panning. */ +#endif + + gf_uint8_t current_table_index; + gf_uint8_t current_row; + gf_uint16_t current_tick; /* Can go below 255, with high tempo and a pattern delay */ + float remaining_samples_in_tick; + gf_uint64_t generated_samples; + + gf_bool_t position_jump; + gf_bool_t pattern_break; + gf_uint8_t jump_dest; + gf_uint8_t jump_row; + + /* Extra ticks to be played before going to the next row - + * Used for EEy effect */ + gf_uint16_t extra_ticks; + + gf_uint8_t* row_loop_count; /* Array of size MAX_NUM_ROWS * module_length */ + gf_uint8_t loop_count; + gf_uint8_t max_loop_count; + + jar_xm_channel_context_t* channels; +}; + +/* ----- Internal API ----- */ + +#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*); + +void jar_xm_generate_samples_16bit(jar_xm_context_t* ctx, short* output, size_t numsamples) { + float* musicBuffer = malloc((2 * numsamples) * sizeof(float)); + jar_xm_generate_samples(ctx, musicBuffer, numsamples); + + if(output) { + int x; + for(x = 0; x < 2 * numsamples; x++) output[x] = musicBuffer[x] * SHRT_MAX; + } + + free(musicBuffer); +} + +void jar_xm_generate_samples_8bit(jar_xm_context_t* ctx, char* output, size_t numsamples) { + float* musicBuffer = malloc((2 * numsamples) * sizeof(float)); + jar_xm_generate_samples(ctx, musicBuffer, numsamples); + + if(output) { + int x; + for(x = 0; x < 2 * numsamples; x++) output[x] = musicBuffer[x] * CHAR_MAX; + } + + free(musicBuffer); +} + +int jar_xm_create_context(jar_xm_context_t** ctxp, const char* moddata, gf_uint32_t rate) { return jar_xm_create_context_safe(ctxp, moddata, SIZE_MAX, rate); } + +int jar_xm_create_context_safe(jar_xm_context_t** ctxp, const char* moddata, size_t moddata_length, gf_uint32_t rate) { +#if JAR_XM_DEFENSIVE + int ret; +#endif + size_t bytes_needed; + char* mempool; + jar_xm_context_t* ctx; + gf_uint8_t i; + +#if JAR_XM_DEFENSIVE + if((ret = jar_xm_check_sanity_preload(moddata, moddata_length))) { + JX_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 = malloc(bytes_needed); + if(mempool == NULL && bytes_needed > 0) { + /* malloc() failed, trouble ahead */ + JX_DEBUG("call to malloc() failed, returned %p", (void*)mempool); + return 2; + } + + /* Initialize most of the fields to 0, 0.f, NULL or gf_false depending on type */ + memset(mempool, 0, bytes_needed); + + ctx = (*ctxp = (jar_xm_context_t*)mempool); + ctx->allocated_memory = mempool; /* Keep original pointer for free() */ + mempool += sizeof(jar_xm_context_t); + + ctx->rate = rate; + mempool = jar_xm_load_module(ctx, moddata, moddata_length, mempool); + + ctx->channels = (jar_xm_channel_context_t*)mempool; + mempool += ctx->module.num_channels * sizeof(jar_xm_channel_context_t); + + ctx->global_volume = 1.f; + ctx->amplification = .25f; /* XXX: some bad modules may still clip. Find out something better. */ + +#if JAR_XM_RAMPING + ctx->volume_ramp = (1.f / 128.f); + ctx->panning_ramp = (1.f / 128.f); +#endif + + for(i = 0; i < ctx->module.num_channels; ++i) { + jar_xm_channel_context_t* ch = ctx->channels + i; + + ch->ping = gf_true; + ch->vibrato_waveform = jar_xm_SINE_WAVEFORM; + ch->vibrato_waveform_retrigger = gf_true; + ch->tremolo_waveform = jar_xm_SINE_WAVEFORM; + ch->tremolo_waveform_retrigger = gf_true; + + ch->volume = ch->volume_envelope_volume = ch->fadeout_volume = 1.0f; + ch->panning = ch->panning_envelope_panning = .5f; + ch->actual_volume = .0f; + ch->actual_panning = .5f; + } + + ctx->row_loop_count = (gf_uint8_t*)mempool; + mempool += MAX_NUM_ROWS * sizeof(gf_uint8_t); + +#if JAR_XM_DEFENSIVE + if((ret = jar_xm_check_sanity_postload(ctx))) { + JX_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* context) { free(context->allocated_memory); } + +void jar_xm_set_max_loop_count(jar_xm_context_t* context, gf_uint8_t loopcnt) { context->max_loop_count = loopcnt; } + +gf_uint8_t jar_xm_get_loop_count(jar_xm_context_t* context) { return context->loop_count; } + +gf_bool_t jar_xm_mute_channel(jar_xm_context_t* ctx, gf_uint16_t channel, gf_bool_t mute) { + gf_bool_t old = ctx->channels[channel - 1].muted; + ctx->channels[channel - 1].muted = mute; + return old; +} + +gf_bool_t jar_xm_mute_instrument(jar_xm_context_t* ctx, gf_uint16_t instr, gf_bool_t mute) { + gf_bool_t 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; } + +gf_uint16_t jar_xm_get_number_of_channels(jar_xm_context_t* ctx) { return ctx->module.num_channels; } + +gf_uint16_t jar_xm_get_module_length(jar_xm_context_t* ctx) { return ctx->module.length; } + +gf_uint16_t jar_xm_get_number_of_patterns(jar_xm_context_t* ctx) { return ctx->module.num_patterns; } + +gf_uint16_t jar_xm_get_number_of_rows(jar_xm_context_t* ctx, gf_uint16_t pattern) { return ctx->module.patterns[pattern].num_rows; } + +gf_uint16_t jar_xm_get_number_of_instruments(jar_xm_context_t* ctx) { return ctx->module.num_instruments; } + +gf_uint16_t jar_xm_get_number_of_samples(jar_xm_context_t* ctx, gf_uint16_t instrument) { return ctx->module.instruments[instrument - 1].num_samples; } + +void jar_xm_get_playing_speed(jar_xm_context_t* ctx, gf_uint16_t* bpm, gf_uint16_t* tempo) { + if(bpm) *bpm = ctx->bpm; + if(tempo) *tempo = ctx->tempo; +} + +void jar_xm_get_position(jar_xm_context_t* ctx, gf_uint8_t* pattern_index, gf_uint8_t* pattern, gf_uint8_t* row, gf_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; +} + +gf_uint64_t jar_xm_get_latest_trigger_of_instrument(jar_xm_context_t* ctx, gf_uint16_t instr) { return ctx->module.instruments[instr - 1].latest_trigger; } + +gf_uint64_t jar_xm_get_latest_trigger_of_sample(jar_xm_context_t* ctx, gf_uint16_t instr, gf_uint16_t sample) { return ctx->module.instruments[instr - 1].samples[sample].latest_trigger; } + +gf_uint64_t jar_xm_get_latest_trigger_of_channel(jar_xm_context_t* ctx, gf_uint16_t chn) { return ctx->channels[chn - 1].latest_trigger; } + +/* .xm files are little-endian. (XXX: Are they really?) */ + +/* Bounded 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) ? (*(gf_uint8_t*)(moddata + (offset))) : 0) +#define READ_U16(offset) ((gf_uint16_t)READ_U8(offset) | ((gf_uint16_t)READ_U8((offset) + 1) << 8)) +#define READ_U32(offset) ((gf_uint32_t)READ_U16(offset) | ((gf_uint32_t)READ_U16((offset) + 2) << 16)) +#define READ_MEMCPY(ptr, offset, length) memcpy_pad(ptr, length, moddata, moddata_length, offset) + +static inline void memcpy_pad(void* dst, size_t dst_len, const void* src, size_t src_len, size_t offset) { + gf_uint8_t* dst_c = dst; + const gf_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) { + /* Not XM 1.04 */ + return 3; + } + + return 0; +} + +int jar_xm_check_sanity_postload(jar_xm_context_t* ctx) { + /* @todo: plenty of stuff to do here… */ + gf_uint8_t i; + + /* Check the POT */ + for(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) { + /* Cheap fix */ + --ctx->module.length; + JX_DEBUG("trimming invalid POT at pos %X", i); + } else { + JX_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; /* Skip the first header */ + gf_uint16_t num_channels; + gf_uint16_t num_patterns; + gf_uint16_t num_instruments; + + /* Read the module header */ + + num_channels = READ_U16(offset + 8); + num_channels = READ_U16(offset + 8); + + num_patterns = READ_U16(offset + 10); + memory_needed += num_patterns * sizeof(jar_xm_pattern_t); + + num_instruments = READ_U16(offset + 12); + memory_needed += num_instruments * sizeof(jar_xm_instrument_t); + + memory_needed += MAX_NUM_ROWS * READ_U16(offset + 4) * sizeof(gf_uint8_t); /* Module length */ + + /* Header size */ + offset += READ_U32(offset); + + /* Read pattern headers */ + for(gf_uint16_t i = 0; i < num_patterns; ++i) { + gf_uint16_t num_rows; + + num_rows = READ_U16(offset + 5); + memory_needed += num_rows * num_channels * sizeof(jar_xm_pattern_slot_t); + + /* Pattern header length + packed pattern data size */ + offset += READ_U32(offset) + READ_U16(offset + 7); + } + + /* Read instrument headers */ + for(gf_uint16_t i = 0; i < num_instruments; ++i) { + gf_uint16_t num_samples; + gf_uint32_t sample_header_size = 0; + gf_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 */ + offset += READ_U32(offset); + + for(gf_uint16_t j = 0; j < num_samples; ++j) { + gf_uint32_t sample_size; + gf_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 */ + gf_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; + mempool += mod->num_patterns * sizeof(jar_xm_pattern_t); + + mod->instruments = (jar_xm_instrument_t*)mempool; + mempool += mod->num_instruments * sizeof(jar_xm_instrument_t); + + gf_uint16_t flags = READ_U32(offset + 14); + mod->frequency_type = (flags & (1 << 0)) ? jar_xm_LINEAR_FREQUENCIES : jar_xm_AMIGA_FREQUENCIES; + + ctx->tempo = READ_U16(offset + 16); + ctx->bpm = READ_U16(offset + 18); + + READ_MEMCPY(mod->pattern_table, offset + 20, PATTERN_ORDER_TABLE_LENGTH); + offset += header_size; + + /* Read patterns */ + for(gf_uint16_t i = 0; i < mod->num_patterns; ++i) { + gf_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); + + /* Pattern header length */ + offset += READ_U32(offset); + + 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(gf_uint16_t j = 0, k = 0; j < packed_patterndata_size; ++k) { + gf_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; + } + + /* Read instruments */ + for(gf_uint16_t i = 0; i < ctx->module.num_instruments; ++i) { + gf_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(gf_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(gf_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); + + gf_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) { + instr->vibrato_type = 1; + } else if(instr->vibrato_type == 1) { + instr->vibrato_type = 2; + } + instr->vibrato_sweep = READ_U8(offset + 236); + instr->vibrato_depth = READ_U8(offset + 237); + instr->vibrato_rate = READ_U8(offset + 238); + instr->volume_fadeout = READ_U16(offset + 239); + + instr->samples = (jar_xm_sample_t*)mempool; + mempool += instr->num_samples * sizeof(jar_xm_sample_t); + } else { + instr->samples = NULL; + } + + /* Instrument header size */ + offset += READ_U32(offset); + + for(gf_uint16_t j = 0; j < instr->num_samples; ++j) { + /* Read sample header */ + jar_xm_sample_t* sample = instr->samples + j; + + sample->length = READ_U32(offset); + sample->loop_start = READ_U32(offset + 4); + sample->loop_length = READ_U32(offset + 8); + sample->loop_end = sample->loop_start + sample->loop_length; + sample->volume = (float)READ_U8(offset + 12) / (float)0x40; + sample->finetune = (gf_int8_t)READ_U8(offset + 13); + + gf_uint8_t flags = READ_U8(offset + 14); + if((flags & 3) == 0) { + sample->loop_type = jar_xm_NO_LOOP; + } else if((flags & 3) == 1) { + sample->loop_type = jar_xm_FORWARD_LOOP; + } else { + sample->loop_type = jar_xm_PING_PONG_LOOP; + } + + sample->bits = (flags & (1 << 4)) ? 16 : 8; + + sample->panning = (float)READ_U8(offset + 15) / (float)0xFF; + sample->relative_note = (gf_int8_t)READ_U8(offset + 16); + READ_MEMCPY(sample->name, 18, SAMPLE_NAME_LENGTH); + sample->data = (float*)mempool; + + if(sample->bits == 16) { + /* 16 bit sample */ + mempool += sample->length * (sizeof(float) >> 1); + sample->loop_start >>= 1; + sample->loop_length >>= 1; + sample->loop_end >>= 1; + sample->length >>= 1; + } else { + /* 8 bit sample */ + mempool += sample->length * sizeof(float); + } + + offset += sample_header_size; + } + + for(gf_uint16_t j = 0; j < instr->num_samples; ++j) { + /* Read sample data */ + jar_xm_sample_t* sample = instr->samples + j; + gf_uint32_t length = sample->length; + + if(sample->bits == 16) { + gf_int16_t v = 0; + for(gf_uint32_t k = 0; k < length; ++k) { + v = v + (gf_int16_t)READ_U16(offset + (k << 1)); + sample->data[k] = (float)v / (float)(1 << 15); + } + offset += sample->length << 1; + } else { + gf_int8_t v = 0; + for(gf_uint32_t k = 0; k < length; ++k) { + v = v + (gf_int8_t)READ_U8(offset + k); + sample->data[k] = (float)v / (float)(1 << 7); + } + offset += sample->length; + } + } + } + + return mempool; +} + +/*-------------------------------------------------------------------------------*/ +/* THE FOLLOWING IS FOR PLAYING */ +/*-------------------------------------------------------------------------------*/ + +/* ----- Static functions ----- */ + +static float jar_xm_waveform(jar_xm_waveform_type_t, gf_uint8_t); +static void jar_xm_autovibrato(jar_xm_context_t*, jar_xm_channel_context_t*); +static void jar_xm_vibrato(jar_xm_context_t*, jar_xm_channel_context_t*, gf_uint8_t, gf_uint16_t); +static void jar_xm_tremolo(jar_xm_context_t*, jar_xm_channel_context_t*, gf_uint8_t, gf_uint16_t); +static void jar_xm_arpeggio(jar_xm_context_t*, jar_xm_channel_context_t*, gf_uint8_t, gf_uint16_t); +static void jar_xm_tone_portamento(jar_xm_context_t*, jar_xm_channel_context_t*); +static void jar_xm_pitch_slide(jar_xm_context_t*, jar_xm_channel_context_t*, float); +static void jar_xm_panning_slide(jar_xm_channel_context_t*, gf_uint8_t); +static void jar_xm_volume_slide(jar_xm_channel_context_t*, gf_uint8_t); + +static float jar_xm_envelope_lerp(jar_xm_envelope_point_t*, jar_xm_envelope_point_t*, gf_uint16_t); +static void jar_xm_envelope_tick(jar_xm_channel_context_t*, jar_xm_envelope_t*, gf_uint16_t*, float*); +static void jar_xm_envelopes(jar_xm_channel_context_t*); + +static float jar_xm_linear_period(float); +static float jar_xm_linear_frequency(float); +static float jar_xm_amiga_period(float); +static float jar_xm_amiga_frequency(float); +static float jar_xm_period(jar_xm_context_t*, float); +static float jar_xm_frequency(jar_xm_context_t*, float, float); +static void jar_xm_update_frequency(jar_xm_context_t*, jar_xm_channel_context_t*); + +static void jar_xm_handle_note_and_instrument(jar_xm_context_t*, jar_xm_channel_context_t*, jar_xm_pattern_slot_t*); +static void jar_xm_trigger_note(jar_xm_context_t*, jar_xm_channel_context_t*, unsigned int flags); +static void jar_xm_cut_note(jar_xm_channel_context_t*); +static void jar_xm_key_off(jar_xm_channel_context_t*); + +static void jar_xm_post_pattern_change(jar_xm_context_t*); +static void jar_xm_row(jar_xm_context_t*); +static void jar_xm_tick(jar_xm_context_t*); + +static float jar_xm_next_of_sample(jar_xm_channel_context_t*); +static void jar_xm_sample(jar_xm_context_t*, float*, float*); + +/* ----- Other oddities ----- */ + +#define jar_xm_TRIGGER_KEEP_VOLUME (1 << 0) +#define jar_xm_TRIGGER_KEEP_PERIOD (1 << 1) +#define jar_xm_TRIGGER_KEEP_SAMPLE_POSITION (1 << 2) + +static const gf_uint16_t amiga_frequencies[] = { + 1712, 1616, 1525, 1440, /* C-2, C#2, D-2, D#2 */ + 1357, 1281, 1209, 1141, /* E-2, F-2, F#2, G-2 */ + 1077, 1017, 961, 907, /* G#2, A-2, A#2, B-2 */ + 856, /* C-3 */ +}; + +static const float multi_retrig_add[] = { + 0.f, -1.f, -2.f, -4.f, /* 0, 1, 2, 3 */ + -8.f, -16.f, 0.f, 0.f, /* 4, 5, 6, 7 */ + 0.f, 1.f, 2.f, 4.f, /* 8, 9, A, B */ + 8.f, 16.f, 0.f, 0.f /* C, D, E, F */ +}; + +static const float multi_retrig_multiply[] = { + 1.f, 1.f, 1.f, 1.f, /* 0, 1, 2, 3 */ + 1.f, 1.f, .6666667f, .5f, /* 4, 5, 6, 7 */ + 1.f, 1.f, 1.f, 1.f, /* 8, 9, A, B */ + 1.f, 1.f, 1.5f, 2.f /* C, D, E, F */ +}; + +#define jar_xm_CLAMP_UP1F(vol, limit) \ + do { \ + if((vol) > (limit)) (vol) = (limit); \ + } while(0) +#define jar_xm_CLAMP_UP(vol) jar_xm_CLAMP_UP1F((vol), 1.f) + +#define jar_xm_CLAMP_DOWN1F(vol, limit) \ + do { \ + if((vol) < (limit)) (vol) = (limit); \ + } while(0) +#define jar_xm_CLAMP_DOWN(vol) jar_xm_CLAMP_DOWN1F((vol), .0f) + +#define jar_xm_CLAMP2F(vol, up, down) \ + do { \ + if((vol) > (up)) (vol) = (up); \ + else if((vol) < (down)) \ + (vol) = (down); \ + } while(0) +#define jar_xm_CLAMP(vol) jar_xm_CLAMP2F((vol), 1.f, .0f) + +#define jar_xm_SLIDE_TOWARDS(val, goal, incr) \ + do { \ + if((val) > (goal)) { \ + (val) -= (incr); \ + jar_xm_CLAMP_DOWN1F((val), (goal)); \ + } else if((val) < (goal)) { \ + (val) += (incr); \ + jar_xm_CLAMP_UP1F((val), (goal)); \ + } \ + } while(0) + +#define jar_xm_LERP(u, v, t) ((u) + (t) * ((v) - (u))) +#define jar_xm_INVERSE_LERP(u, v, lerp) (((lerp) - (u)) / ((v) - (u))) + +#define HAS_TONE_PORTAMENTO(s) ((s)->effect_type == 3 || (s)->effect_type == 5 || ((s)->volume_column >> 4) == 0xF) +#define HAS_ARPEGGIO(s) ((s)->effect_type == 0 && (s)->effect_param != 0) +#define HAS_VIBRATO(s) ((s)->effect_type == 4 || (s)->effect_param == 6 || ((s)->volume_column >> 4) == 0xB) +#define NOTE_IS_VALID(n) ((n) > 0 && (n) < 97) + +/* ----- Function definitions ----- */ + +static float jar_xm_waveform(jar_xm_waveform_type_t waveform, gf_uint8_t step) { + static unsigned int next_rand = 24492; + step %= 0x40; + + switch(waveform) { + + case jar_xm_SINE_WAVEFORM: + /* Why not use a table? For saving space, and because there's + * very very little actual performance gain. */ + return -sinf(2.f * 3.141592f * (float)step / (float)0x40); + + case jar_xm_RAMP_DOWN_WAVEFORM: + /* Ramp down: 1.0f when step = 0; -1.0f when step = 0x40 */ + return (float)(0x20 - step) / 0x20; + + case jar_xm_SQUARE_WAVEFORM: + /* Square with a 50% duty */ + return (step >= 0x20) ? 1.f : -1.f; + + case jar_xm_RANDOM_WAVEFORM: + /* Use the POSIX.1-2001 example, just to be deterministic + * across different machines */ + next_rand = next_rand * 1103515245 + 12345; + return (float)((next_rand >> 16) & 0x7FFF) / (float)0x4000 - 1.f; + + case jar_xm_RAMP_UP_WAVEFORM: + /* Ramp up: -1.f when step = 0; 1.f when step = 0x40 */ + return (float)(step - 0x20) / 0x20; + + default: + break; + } + + return .0f; +} + +static void jar_xm_autovibrato(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch) { + if(ch->instrument == NULL || ch->instrument->vibrato_depth == 0) return; + jar_xm_instrument_t* instr = ch->instrument; + float sweep = 1.f; + + if(ch->autovibrato_ticks < instr->vibrato_sweep) { + /* No idea if this is correct, but it sounds close enough… */ + sweep = jar_xm_LERP(0.f, 1.f, (float)ch->autovibrato_ticks / (float)instr->vibrato_sweep); + } + + unsigned int step = ((ch->autovibrato_ticks++) * instr->vibrato_rate) >> 2; + ch->autovibrato_note_offset = .25f * jar_xm_waveform(instr->vibrato_type, step) * (float)instr->vibrato_depth / (float)0xF * sweep; + jar_xm_update_frequency(ctx, ch); +} + +static void jar_xm_vibrato(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, gf_uint8_t param, gf_uint16_t pos) { + unsigned int step = pos * (param >> 4); + ch->vibrato_note_offset = 2.f * jar_xm_waveform(ch->vibrato_waveform, step) * (float)(param & 0x0F) / (float)0xF; + jar_xm_update_frequency(ctx, ch); +} + +static void jar_xm_tremolo(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, gf_uint8_t param, gf_uint16_t pos) { + unsigned int step = pos * (param >> 4); + /* Not so sure about this, it sounds correct by ear compared with + * MilkyTracker, but it could come from other bugs */ + ch->tremolo_volume = -1.f * jar_xm_waveform(ch->tremolo_waveform, step) * (float)(param & 0x0F) / (float)0xF; +} + +static void jar_xm_arpeggio(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, gf_uint8_t param, gf_uint16_t tick) { + switch(tick % 3) { + case 0: + ch->arp_in_progress = gf_false; + ch->arp_note_offset = 0; + break; + case 2: + ch->arp_in_progress = gf_true; + ch->arp_note_offset = param >> 4; + break; + case 1: + ch->arp_in_progress = gf_true; + ch->arp_note_offset = param & 0x0F; + break; + } + + jar_xm_update_frequency(ctx, ch); +} + +static void jar_xm_tone_portamento(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch) { + /* 3xx called without a note, wait until we get an actual + * target note. */ + if(ch->tone_portamento_target_period == 0.f) return; + + if(ch->period != ch->tone_portamento_target_period) { + jar_xm_SLIDE_TOWARDS(ch->period, ch->tone_portamento_target_period, (ctx->module.frequency_type == jar_xm_LINEAR_FREQUENCIES ? 4.f : 1.f) * ch->tone_portamento_param); + jar_xm_update_frequency(ctx, ch); + } +} + +static void jar_xm_pitch_slide(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, float period_offset) { + /* Don't ask about the 4.f coefficient. I found mention of it + * nowhere. Found by ear™. */ + if(ctx->module.frequency_type == jar_xm_LINEAR_FREQUENCIES) { + period_offset *= 4.f; + } + + ch->period += period_offset; + jar_xm_CLAMP_DOWN(ch->period); + /* XXX: upper bound of period ? */ + + jar_xm_update_frequency(ctx, ch); +} + +static void jar_xm_panning_slide(jar_xm_channel_context_t* ch, gf_uint8_t rawval) { + float f; + + if((rawval & 0xF0) && (rawval & 0x0F)) { + /* Illegal state */ + return; + } + + if(rawval & 0xF0) { + /* Slide right */ + f = (float)(rawval >> 4) / (float)0xFF; + ch->panning += f; + jar_xm_CLAMP_UP(ch->panning); + } else { + /* Slide left */ + f = (float)(rawval & 0x0F) / (float)0xFF; + ch->panning -= f; + jar_xm_CLAMP_DOWN(ch->panning); + } +} + +static void jar_xm_volume_slide(jar_xm_channel_context_t* ch, gf_uint8_t rawval) { + float f; + + if((rawval & 0xF0) && (rawval & 0x0F)) { + /* Illegal state */ + return; + } + + if(rawval & 0xF0) { + /* Slide up */ + f = (float)(rawval >> 4) / (float)0x40; + ch->volume += f; + jar_xm_CLAMP_UP(ch->volume); + } else { + /* Slide down */ + f = (float)(rawval & 0x0F) / (float)0x40; + ch->volume -= f; + jar_xm_CLAMP_DOWN(ch->volume); + } +} + +static float jar_xm_envelope_lerp(jar_xm_envelope_point_t* restrict a, jar_xm_envelope_point_t* restrict b, gf_uint16_t pos) { + /* Linear interpolation between two envelope points */ + if(pos <= a->frame) return a->value; + else if(pos >= b->frame) + return b->value; + else { + float p = (float)(pos - a->frame) / (float)(b->frame - a->frame); + return a->value * (1 - p) + b->value * p; + } +} + +static void jar_xm_post_pattern_change(jar_xm_context_t* ctx) { + /* Loop if necessary */ + if(ctx->current_table_index >= ctx->module.length) { + ctx->current_table_index = ctx->module.restart_position; + } +} + +static float jar_xm_linear_period(float note) { return 7680.f - note * 64.f; } + +static float jar_xm_linear_frequency(float period) { return 8363.f * powf(2.f, (4608.f - period) / 768.f); } + +static float jar_xm_amiga_period(float note) { + unsigned int intnote = note; + gf_uint8_t a = intnote % 12; + gf_int8_t octave = note / 12.f - 2; + gf_uint16_t p1 = amiga_frequencies[a], p2 = amiga_frequencies[a + 1]; + + if(octave > 0) { + p1 >>= octave; + p2 >>= octave; + } else if(octave < 0) { + p1 <<= (-octave); + p2 <<= (-octave); + } + + return jar_xm_LERP(p1, p2, note - intnote); +} + +static float jar_xm_amiga_frequency(float period) { + if(period == .0f) return .0f; + + /* This is the PAL value. No reason to choose this one over the + * NTSC value. */ + return 7093789.2f / (period * 2.f); +} + +static float jar_xm_period(jar_xm_context_t* ctx, float note) { + switch(ctx->module.frequency_type) { + case jar_xm_LINEAR_FREQUENCIES: + return jar_xm_linear_period(note); + case jar_xm_AMIGA_FREQUENCIES: + return jar_xm_amiga_period(note); + } + return .0f; +} + +static float jar_xm_frequency(jar_xm_context_t* ctx, float period, float note_offset) { + gf_uint8_t a; + gf_int8_t octave; + float note; + gf_uint16_t p1, p2; + + switch(ctx->module.frequency_type) { + + case jar_xm_LINEAR_FREQUENCIES: + return jar_xm_linear_frequency(period - 64.f * note_offset); + + case jar_xm_AMIGA_FREQUENCIES: + if(note_offset == 0) { + /* A chance to escape from insanity */ + return jar_xm_amiga_frequency(period); + } + + /* FIXME: this is very crappy at best */ + a = octave = 0; + + /* Find the octave of the current period */ + if(period > amiga_frequencies[0]) { + --octave; + while(period > (amiga_frequencies[0] << (-octave))) --octave; + } else if(period < amiga_frequencies[12]) { + ++octave; + while(period < (amiga_frequencies[12] >> octave)) ++octave; + } + + /* Find the smallest note closest to the current period */ + for(gf_uint8_t i = 0; i < 12; ++i) { + p1 = amiga_frequencies[i], p2 = amiga_frequencies[i + 1]; + + if(octave > 0) { + p1 >>= octave; + p2 >>= octave; + } else if(octave < 0) { + p1 <<= (-octave); + p2 <<= (-octave); + } + + if(p2 <= period && period <= p1) { + a = i; + break; + } + } + + if(JAR_XM_DEBUG && (p1 < period || p2 > period)) { + JX_DEBUG("%i <= %f <= %i should hold but doesn't, this is a bug", p2, period, p1); + } + + note = 12.f * (octave + 2) + a + jar_xm_INVERSE_LERP(p1, p2, period); + + return jar_xm_amiga_frequency(jar_xm_amiga_period(note + note_offset)); + } + + return .0f; +} + +static void jar_xm_update_frequency(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch) { + ch->frequency = jar_xm_frequency(ctx, ch->period, (ch->arp_note_offset > 0 ? ch->arp_note_offset : (ch->vibrato_note_offset + ch->autovibrato_note_offset))); + ch->step = ch->frequency / ctx->rate; +} + +static void jar_xm_handle_note_and_instrument(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, jar_xm_pattern_slot_t* s) { + if(s->instrument > 0) { + if(HAS_TONE_PORTAMENTO(ch->current) && ch->instrument != NULL && ch->sample != NULL) { + /* Tone portamento in effect, unclear stuff happens */ + jar_xm_trigger_note(ctx, ch, jar_xm_TRIGGER_KEEP_PERIOD | jar_xm_TRIGGER_KEEP_SAMPLE_POSITION); + } else if(s->instrument > ctx->module.num_instruments) { + /* Invalid instrument, Cut current note */ + jar_xm_cut_note(ch); + ch->instrument = NULL; + ch->sample = NULL; + } else { + ch->instrument = ctx->module.instruments + (s->instrument - 1); + if(s->note == 0 && ch->sample != NULL) { + /* Ghost instrument, trigger note */ + /* Sample position is kept, but envelopes are reset */ + jar_xm_trigger_note(ctx, ch, jar_xm_TRIGGER_KEEP_SAMPLE_POSITION); + } + } + } + + if(NOTE_IS_VALID(s->note)) { + /* Yes, the real note number is s->note -1. Try finding + * THAT in any of the specs! :-) */ + + jar_xm_instrument_t* instr = ch->instrument; + + if(HAS_TONE_PORTAMENTO(ch->current) && instr != NULL && ch->sample != NULL) { + /* Tone portamento in effect */ + ch->note = s->note + ch->sample->relative_note + ch->sample->finetune / 128.f - 1.f; + ch->tone_portamento_target_period = jar_xm_period(ctx, ch->note); + } else if(instr == NULL || ch->instrument->num_samples == 0) { + /* Bad instrument */ + jar_xm_cut_note(ch); + } else { + if(instr->sample_of_notes[s->note - 1] < instr->num_samples) { +#if JAR_XM_RAMPING + for(unsigned int z = 0; z < jar_xm_SAMPLE_RAMPING_POINTS; ++z) { + ch->end_of_previous_sample[z] = jar_xm_next_of_sample(ch); + } + ch->frame_count = 0; +#endif + ch->sample = instr->samples + instr->sample_of_notes[s->note - 1]; + ch->orig_note = ch->note = s->note + ch->sample->relative_note + ch->sample->finetune / 128.f - 1.f; + if(s->instrument > 0) { + jar_xm_trigger_note(ctx, ch, 0); + } else { + /* Ghost note: keep old volume */ + jar_xm_trigger_note(ctx, ch, jar_xm_TRIGGER_KEEP_VOLUME); + } + } else { + /* Bad sample */ + jar_xm_cut_note(ch); + } + } + } else if(s->note == 97) { + /* Key Off */ + jar_xm_key_off(ch); + } + + switch(s->volume_column >> 4) { + + case 0x5: + if(s->volume_column > 0x50) break; + case 0x1: + case 0x2: + case 0x3: + case 0x4: + /* Set volume */ + ch->volume = (float)(s->volume_column - 0x10) / (float)0x40; + break; + + case 0x8: /* Fine volume slide down */ + jar_xm_volume_slide(ch, s->volume_column & 0x0F); + break; + + case 0x9: /* Fine volume slide up */ + jar_xm_volume_slide(ch, s->volume_column << 4); + break; + + case 0xA: /* Set vibrato speed */ + ch->vibrato_param = (ch->vibrato_param & 0x0F) | ((s->volume_column & 0x0F) << 4); + break; + + case 0xC: /* Set panning */ + ch->panning = (float)(((s->volume_column & 0x0F) << 4) | (s->volume_column & 0x0F)) / (float)0xFF; + break; + + case 0xF: /* Tone portamento */ + if(s->volume_column & 0x0F) { + ch->tone_portamento_param = ((s->volume_column & 0x0F) << 4) | (s->volume_column & 0x0F); + } + break; + + default: + break; + } + + switch(s->effect_type) { + + case 1: /* 1xx: Portamento up */ + if(s->effect_param > 0) { + ch->portamento_up_param = s->effect_param; + } + break; + + case 2: /* 2xx: Portamento down */ + if(s->effect_param > 0) { + ch->portamento_down_param = s->effect_param; + } + break; + + case 3: /* 3xx: Tone portamento */ + if(s->effect_param > 0) { + ch->tone_portamento_param = s->effect_param; + } + break; + + case 4: /* 4xy: Vibrato */ + if(s->effect_param & 0x0F) { + /* Set vibrato depth */ + ch->vibrato_param = (ch->vibrato_param & 0xF0) | (s->effect_param & 0x0F); + } + if(s->effect_param >> 4) { + /* Set vibrato speed */ + ch->vibrato_param = (s->effect_param & 0xF0) | (ch->vibrato_param & 0x0F); + } + break; + + case 5: /* 5xy: Tone portamento + Volume slide */ + if(s->effect_param > 0) { + ch->volume_slide_param = s->effect_param; + } + break; + + case 6: /* 6xy: Vibrato + Volume slide */ + if(s->effect_param > 0) { + ch->volume_slide_param = s->effect_param; + } + break; + + case 7: /* 7xy: Tremolo */ + if(s->effect_param & 0x0F) { + /* Set tremolo depth */ + ch->tremolo_param = (ch->tremolo_param & 0xF0) | (s->effect_param & 0x0F); + } + if(s->effect_param >> 4) { + /* Set tremolo speed */ + ch->tremolo_param = (s->effect_param & 0xF0) | (ch->tremolo_param & 0x0F); + } + break; + + case 8: /* 8xx: Set panning */ + ch->panning = (float)s->effect_param / (float)0xFF; + break; + + case 9: /* 9xx: Sample offset */ + if(ch->sample != NULL && NOTE_IS_VALID(s->note)) { + gf_uint32_t final_offset = s->effect_param << (ch->sample->bits == 16 ? 7 : 8); + if(final_offset >= ch->sample->length) { + /* Pretend the sample dosen't loop and is done playing */ + ch->sample_position = -1; + break; + } + ch->sample_position = final_offset; + } + break; + + case 0xA: /* Axy: Volume slide */ + if(s->effect_param > 0) { + ch->volume_slide_param = s->effect_param; + } + break; + + case 0xB: /* Bxx: Position jump */ + if(s->effect_param < ctx->module.length) { + ctx->position_jump = gf_true; + ctx->jump_dest = s->effect_param; + } + break; + + case 0xC: /* Cxx: Set volume */ + ch->volume = (float)((s->effect_param > 0x40) ? 0x40 : s->effect_param) / (float)0x40; + break; + + case 0xD: /* Dxx: Pattern break */ + /* Jump after playing this line */ + ctx->pattern_break = gf_true; + ctx->jump_row = (s->effect_param >> 4) * 10 + (s->effect_param & 0x0F); + break; + + case 0xE: /* EXy: Extended command */ + switch(s->effect_param >> 4) { + + case 1: /* E1y: Fine portamento up */ + if(s->effect_param & 0x0F) { + ch->fine_portamento_up_param = s->effect_param & 0x0F; + } + jar_xm_pitch_slide(ctx, ch, -ch->fine_portamento_up_param); + break; + + case 2: /* E2y: Fine portamento down */ + if(s->effect_param & 0x0F) { + ch->fine_portamento_down_param = s->effect_param & 0x0F; + } + jar_xm_pitch_slide(ctx, ch, ch->fine_portamento_down_param); + break; + + case 4: /* E4y: Set vibrato control */ + ch->vibrato_waveform = s->effect_param & 3; + ch->vibrato_waveform_retrigger = !((s->effect_param >> 2) & 1); + break; + + case 5: /* E5y: Set finetune */ + if(NOTE_IS_VALID(ch->current->note) && ch->sample != NULL) { + ch->note = ch->current->note + ch->sample->relative_note + (float)(((s->effect_param & 0x0F) - 8) << 4) / 128.f - 1.f; + ch->period = jar_xm_period(ctx, ch->note); + jar_xm_update_frequency(ctx, ch); + } + break; + + case 6: /* E6y: Pattern loop */ + if(s->effect_param & 0x0F) { + if((s->effect_param & 0x0F) == ch->pattern_loop_count) { + /* Loop is over */ + ch->pattern_loop_count = 0; + break; + } + + /* Jump to the beginning of the loop */ + ch->pattern_loop_count++; + ctx->position_jump = gf_true; + ctx->jump_row = ch->pattern_loop_origin; + ctx->jump_dest = ctx->current_table_index; + } else { + /* Set loop start point */ + ch->pattern_loop_origin = ctx->current_row; + /* Replicate FT2 E60 bug */ + ctx->jump_row = ch->pattern_loop_origin; + } + break; + + case 7: /* E7y: Set tremolo control */ + ch->tremolo_waveform = s->effect_param & 3; + ch->tremolo_waveform_retrigger = !((s->effect_param >> 2) & 1); + break; + + case 0xA: /* EAy: Fine volume slide up */ + if(s->effect_param & 0x0F) { + ch->fine_volume_slide_param = s->effect_param & 0x0F; + } + jar_xm_volume_slide(ch, ch->fine_volume_slide_param << 4); + break; + + case 0xB: /* EBy: Fine volume slide down */ + if(s->effect_param & 0x0F) { + ch->fine_volume_slide_param = s->effect_param & 0x0F; + } + jar_xm_volume_slide(ch, ch->fine_volume_slide_param); + break; + + case 0xD: /* EDy: Note delay */ + /* XXX: figure this out better. EDx triggers + * the note even when there no note and no + * instrument. But ED0 acts like like a ghost + * note, EDx (x ≠ 0) does not. */ + if(s->note == 0 && s->instrument == 0) { + unsigned int flags = jar_xm_TRIGGER_KEEP_VOLUME; + + if(ch->current->effect_param & 0x0F) { + ch->note = ch->orig_note; + jar_xm_trigger_note(ctx, ch, flags); + } else { + jar_xm_trigger_note(ctx, ch, flags | jar_xm_TRIGGER_KEEP_PERIOD | jar_xm_TRIGGER_KEEP_SAMPLE_POSITION); + } + } + break; + + case 0xE: /* EEy: Pattern delay */ + ctx->extra_ticks = (ch->current->effect_param & 0x0F) * ctx->tempo; + break; + + default: + break; + } + break; + + case 0xF: /* Fxx: Set tempo/BPM */ + if(s->effect_param > 0) { + if(s->effect_param <= 0x1F) { + ctx->tempo = s->effect_param; + } else { + ctx->bpm = s->effect_param; + } + } + break; + + case 16: /* Gxx: Set global volume */ + ctx->global_volume = (float)((s->effect_param > 0x40) ? 0x40 : s->effect_param) / (float)0x40; + break; + + case 17: /* Hxy: Global volume slide */ + if(s->effect_param > 0) { + ch->global_volume_slide_param = s->effect_param; + } + break; + + case 21: /* Lxx: Set envelope position */ + ch->volume_envelope_frame_count = s->effect_param; + ch->panning_envelope_frame_count = s->effect_param; + break; + + case 25: /* Pxy: Panning slide */ + if(s->effect_param > 0) { + ch->panning_slide_param = s->effect_param; + } + break; + + case 27: /* Rxy: Multi retrig note */ + if(s->effect_param > 0) { + if((s->effect_param >> 4) == 0) { + /* Keep previous x value */ + ch->multi_retrig_param = (ch->multi_retrig_param & 0xF0) | (s->effect_param & 0x0F); + } else { + ch->multi_retrig_param = s->effect_param; + } + } + break; + + case 29: /* Txy: Tremor */ + if(s->effect_param > 0) { + /* Tremor x and y params do not appear to be separately + * kept in memory, unlike Rxy */ + ch->tremor_param = s->effect_param; + } + break; + + case 33: /* Xxy: Extra stuff */ + switch(s->effect_param >> 4) { + + case 1: /* X1y: Extra fine portamento up */ + if(s->effect_param & 0x0F) { + ch->extra_fine_portamento_up_param = s->effect_param & 0x0F; + } + jar_xm_pitch_slide(ctx, ch, -1.0f * ch->extra_fine_portamento_up_param); + break; + + case 2: /* X2y: Extra fine portamento down */ + if(s->effect_param & 0x0F) { + ch->extra_fine_portamento_down_param = s->effect_param & 0x0F; + } + jar_xm_pitch_slide(ctx, ch, ch->extra_fine_portamento_down_param); + break; + + default: + break; + } + break; + + default: + break; + } +} + +static void jar_xm_trigger_note(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, unsigned int flags) { + if(!(flags & jar_xm_TRIGGER_KEEP_SAMPLE_POSITION)) { + ch->sample_position = 0.f; + ch->ping = gf_true; + } + + if(ch->sample != NULL) { + if(!(flags & jar_xm_TRIGGER_KEEP_VOLUME)) { + ch->volume = ch->sample->volume; + } + + ch->panning = ch->sample->panning; + } + + ch->sustained = gf_true; + ch->fadeout_volume = ch->volume_envelope_volume = 1.0f; + ch->panning_envelope_panning = .5f; + ch->volume_envelope_frame_count = ch->panning_envelope_frame_count = 0; + ch->vibrato_note_offset = 0.f; + ch->tremolo_volume = 0.f; + ch->tremor_on = gf_false; + + ch->autovibrato_ticks = 0; + + if(ch->vibrato_waveform_retrigger) { + ch->vibrato_ticks = 0; /* XXX: should the waveform itself also + * be reset to sine? */ + } + if(ch->tremolo_waveform_retrigger) { + ch->tremolo_ticks = 0; + } + + if(!(flags & jar_xm_TRIGGER_KEEP_PERIOD)) { + ch->period = jar_xm_period(ctx, ch->note); + jar_xm_update_frequency(ctx, ch); + } + + ch->latest_trigger = ctx->generated_samples; + if(ch->instrument != NULL) { + ch->instrument->latest_trigger = ctx->generated_samples; + } + if(ch->sample != NULL) { + ch->sample->latest_trigger = ctx->generated_samples; + } +} + +static void jar_xm_cut_note(jar_xm_channel_context_t* ch) { + /* NB: this is not the same as Key Off */ + ch->volume = .0f; +} + +static void jar_xm_key_off(jar_xm_channel_context_t* ch) { + /* Key Off */ + ch->sustained = gf_false; + + /* If no volume envelope is used, also cut the note */ + if(ch->instrument == NULL || !ch->instrument->volume_envelope.enabled) { + jar_xm_cut_note(ch); + } +} + +static void jar_xm_row(jar_xm_context_t* ctx) { + if(ctx->position_jump) { + ctx->current_table_index = ctx->jump_dest; + ctx->current_row = ctx->jump_row; + ctx->position_jump = gf_false; + ctx->pattern_break = gf_false; + ctx->jump_row = 0; + jar_xm_post_pattern_change(ctx); + } else if(ctx->pattern_break) { + ctx->current_table_index++; + ctx->current_row = ctx->jump_row; + ctx->pattern_break = gf_false; + ctx->jump_row = 0; + jar_xm_post_pattern_change(ctx); + } + + jar_xm_pattern_t* cur = ctx->module.patterns + ctx->module.pattern_table[ctx->current_table_index]; + gf_bool_t in_a_loop = gf_false; + + /* Read notes… */ + for(gf_uint8_t i = 0; i < ctx->module.num_channels; ++i) { + jar_xm_pattern_slot_t* s = cur->slots + ctx->current_row * ctx->module.num_channels + i; + jar_xm_channel_context_t* ch = ctx->channels + i; + + ch->current = s; + + if(s->effect_type != 0xE || s->effect_param >> 4 != 0xD) { + jar_xm_handle_note_and_instrument(ctx, ch, s); + } else { + ch->note_delay_param = s->effect_param & 0x0F; + } + + if(!in_a_loop && ch->pattern_loop_count > 0) { + in_a_loop = gf_true; + } + } + + if(!in_a_loop) { + /* No E6y loop is in effect (or we are in the first pass) */ + ctx->loop_count = (ctx->row_loop_count[MAX_NUM_ROWS * ctx->current_table_index + ctx->current_row]++); + } + + ctx->current_row++; /* Since this is an uint8, this line can + * increment from 255 to 0, in which case it + * is still necessary to go the next + * pattern. */ + if(!ctx->position_jump && !ctx->pattern_break && (ctx->current_row >= cur->num_rows || ctx->current_row == 0)) { + ctx->current_table_index++; + ctx->current_row = ctx->jump_row; /* This will be 0 most of + * the time, except when E60 + * is used */ + ctx->jump_row = 0; + jar_xm_post_pattern_change(ctx); + } +} + +static void jar_xm_envelope_tick(jar_xm_channel_context_t* ch, jar_xm_envelope_t* env, gf_uint16_t* counter, float* outval) { + if(env->num_points < 2) { + /* Don't really know what to do… */ + if(env->num_points == 1) { + /* XXX I am pulling this out of my ass */ + *outval = (float)env->points[0].value / (float)0x40; + if(*outval > 1) { + *outval = 1; + } + } + + return; + } else { + gf_uint8_t j; + + if(env->loop_enabled) { + gf_uint16_t loop_start = env->points[env->loop_start_point].frame; + gf_uint16_t loop_end = env->points[env->loop_end_point].frame; + gf_uint16_t loop_length = loop_end - loop_start; + + if(*counter >= loop_end) { + *counter -= loop_length; + } + } + + for(j = 0; j < (env->num_points - 2); ++j) { + if(env->points[j].frame <= *counter && env->points[j + 1].frame >= *counter) { + break; + } + } + + *outval = jar_xm_envelope_lerp(env->points + j, env->points + j + 1, *counter) / (float)0x40; + + /* Make sure it is safe to increment frame count */ + if(!ch->sustained || !env->sustain_enabled || *counter != env->points[env->sustain_point].frame) { + (*counter)++; + } + } +} + +static void jar_xm_envelopes(jar_xm_channel_context_t* ch) { + if(ch->instrument != NULL) { + if(ch->instrument->volume_envelope.enabled) { + if(!ch->sustained) { + ch->fadeout_volume -= (float)ch->instrument->volume_fadeout / 65536.f; + jar_xm_CLAMP_DOWN(ch->fadeout_volume); + } + + jar_xm_envelope_tick(ch, &(ch->instrument->volume_envelope), &(ch->volume_envelope_frame_count), &(ch->volume_envelope_volume)); + } + + if(ch->instrument->panning_envelope.enabled) { + jar_xm_envelope_tick(ch, &(ch->instrument->panning_envelope), &(ch->panning_envelope_frame_count), &(ch->panning_envelope_panning)); + } + } +} + +static void jar_xm_tick(jar_xm_context_t* ctx) { + if(ctx->current_tick == 0) { + jar_xm_row(ctx); + } + + for(gf_uint8_t i = 0; i < ctx->module.num_channels; ++i) { + jar_xm_channel_context_t* ch = ctx->channels + i; + + jar_xm_envelopes(ch); + jar_xm_autovibrato(ctx, ch); + + if(ch->arp_in_progress && !HAS_ARPEGGIO(ch->current)) { + ch->arp_in_progress = gf_false; + ch->arp_note_offset = 0; + jar_xm_update_frequency(ctx, ch); + } + if(ch->vibrato_in_progress && !HAS_VIBRATO(ch->current)) { + ch->vibrato_in_progress = gf_false; + ch->vibrato_note_offset = 0.f; + jar_xm_update_frequency(ctx, ch); + } + + switch(ch->current->volume_column >> 4) { + + case 0x6: /* Volume slide down */ + if(ctx->current_tick == 0) break; + jar_xm_volume_slide(ch, ch->current->volume_column & 0x0F); + break; + + case 0x7: /* Volume slide up */ + if(ctx->current_tick == 0) break; + jar_xm_volume_slide(ch, ch->current->volume_column << 4); + break; + + case 0xB: /* Vibrato */ + if(ctx->current_tick == 0) break; + ch->vibrato_in_progress = gf_false; + jar_xm_vibrato(ctx, ch, ch->vibrato_param, ch->vibrato_ticks++); + break; + + case 0xD: /* Panning slide left */ + if(ctx->current_tick == 0) break; + jar_xm_panning_slide(ch, ch->current->volume_column & 0x0F); + break; + + case 0xE: /* Panning slide right */ + if(ctx->current_tick == 0) break; + jar_xm_panning_slide(ch, ch->current->volume_column << 4); + break; + + case 0xF: /* Tone portamento */ + if(ctx->current_tick == 0) break; + jar_xm_tone_portamento(ctx, ch); + break; + + default: + break; + } + + switch(ch->current->effect_type) { + + case 0: /* 0xy: Arpeggio */ + if(ch->current->effect_param > 0) { + char arp_offset = ctx->tempo % 3; + switch(arp_offset) { + case 2: /* 0 -> x -> 0 -> y -> x -> … */ + if(ctx->current_tick == 1) { + ch->arp_in_progress = gf_true; + ch->arp_note_offset = ch->current->effect_param >> 4; + jar_xm_update_frequency(ctx, ch); + break; + } + /* No break here, this is intended */ + case 1: /* 0 -> 0 -> y -> x -> … */ + if(ctx->current_tick == 0) { + ch->arp_in_progress = gf_false; + ch->arp_note_offset = 0; + jar_xm_update_frequency(ctx, ch); + break; + } + /* No break here, this is intended */ + case 0: /* 0 -> y -> x -> … */ + jar_xm_arpeggio(ctx, ch, ch->current->effect_param, ctx->current_tick - arp_offset); + default: + break; + } + } + break; + + case 1: /* 1xx: Portamento up */ + if(ctx->current_tick == 0) break; + jar_xm_pitch_slide(ctx, ch, -ch->portamento_up_param); + break; + + case 2: /* 2xx: Portamento down */ + if(ctx->current_tick == 0) break; + jar_xm_pitch_slide(ctx, ch, ch->portamento_down_param); + break; + + case 3: /* 3xx: Tone portamento */ + if(ctx->current_tick == 0) break; + jar_xm_tone_portamento(ctx, ch); + break; + + case 4: /* 4xy: Vibrato */ + if(ctx->current_tick == 0) break; + ch->vibrato_in_progress = gf_true; + jar_xm_vibrato(ctx, ch, ch->vibrato_param, ch->vibrato_ticks++); + break; + + case 5: /* 5xy: Tone portamento + Volume slide */ + if(ctx->current_tick == 0) break; + jar_xm_tone_portamento(ctx, ch); + jar_xm_volume_slide(ch, ch->volume_slide_param); + break; + + case 6: /* 6xy: Vibrato + Volume slide */ + if(ctx->current_tick == 0) break; + ch->vibrato_in_progress = gf_true; + jar_xm_vibrato(ctx, ch, ch->vibrato_param, ch->vibrato_ticks++); + jar_xm_volume_slide(ch, ch->volume_slide_param); + break; + + case 7: /* 7xy: Tremolo */ + if(ctx->current_tick == 0) break; + jar_xm_tremolo(ctx, ch, ch->tremolo_param, ch->tremolo_ticks++); + break; + + case 0xA: /* Axy: Volume slide */ + if(ctx->current_tick == 0) break; + jar_xm_volume_slide(ch, ch->volume_slide_param); + break; + + case 0xE: /* EXy: Extended command */ + switch(ch->current->effect_param >> 4) { + + case 0x9: /* E9y: Retrigger note */ + if(ctx->current_tick != 0 && ch->current->effect_param & 0x0F) { + if(!(ctx->current_tick % (ch->current->effect_param & 0x0F))) { + jar_xm_trigger_note(ctx, ch, 0); + jar_xm_envelopes(ch); + } + } + break; + + case 0xC: /* ECy: Note cut */ + if((ch->current->effect_param & 0x0F) == ctx->current_tick) { + jar_xm_cut_note(ch); + } + break; + + case 0xD: /* EDy: Note delay */ + if(ch->note_delay_param == ctx->current_tick) { + jar_xm_handle_note_and_instrument(ctx, ch, ch->current); + jar_xm_envelopes(ch); + } + break; + + default: + break; + } + break; + + case 17: /* Hxy: Global volume slide */ + if(ctx->current_tick == 0) break; + if((ch->global_volume_slide_param & 0xF0) && (ch->global_volume_slide_param & 0x0F)) { + /* Illegal state */ + break; + } + if(ch->global_volume_slide_param & 0xF0) { + /* Global slide up */ + float f = (float)(ch->global_volume_slide_param >> 4) / (float)0x40; + ctx->global_volume += f; + jar_xm_CLAMP_UP(ctx->global_volume); + } else { + /* Global slide down */ + float f = (float)(ch->global_volume_slide_param & 0x0F) / (float)0x40; + ctx->global_volume -= f; + jar_xm_CLAMP_DOWN(ctx->global_volume); + } + break; + + case 20: /* Kxx: Key off */ + /* Most documentations will tell you the parameter has no + * use. Don't be fooled. */ + if(ctx->current_tick == ch->current->effect_param) { + jar_xm_key_off(ch); + } + break; + + case 25: /* Pxy: Panning slide */ + if(ctx->current_tick == 0) break; + jar_xm_panning_slide(ch, ch->panning_slide_param); + break; + + case 27: /* Rxy: Multi retrig note */ + if(ctx->current_tick == 0) break; + if(((ch->multi_retrig_param) & 0x0F) == 0) break; + if((ctx->current_tick % (ch->multi_retrig_param & 0x0F)) == 0) { + float v = ch->volume * multi_retrig_multiply[ch->multi_retrig_param >> 4] + multi_retrig_add[ch->multi_retrig_param >> 4]; + jar_xm_CLAMP(v); + jar_xm_trigger_note(ctx, ch, 0); + ch->volume = v; + } + break; + + case 29: /* Txy: Tremor */ + if(ctx->current_tick == 0) break; + ch->tremor_on = ((ctx->current_tick - 1) % ((ch->tremor_param >> 4) + (ch->tremor_param & 0x0F) + 2) > (ch->tremor_param >> 4)); + break; + + default: + break; + } + + float panning, volume; + + panning = ch->panning + (ch->panning_envelope_panning - .5f) * (.5f - fabsf(ch->panning - .5f)) * 2.0f; + + if(ch->tremor_on) { + volume = .0f; + } else { + volume = ch->volume + ch->tremolo_volume; + jar_xm_CLAMP(volume); + volume *= ch->fadeout_volume * ch->volume_envelope_volume; + } + +#if JAR_XM_RAMPING + ch->target_panning = panning; + ch->target_volume = volume; +#else + ch->actual_panning = panning; + ch->actual_volume = volume; +#endif + } + + ctx->current_tick++; + if(ctx->current_tick >= ctx->tempo + ctx->extra_ticks) { + ctx->current_tick = 0; + ctx->extra_ticks = 0; + } + + /* FT2 manual says number of ticks / second = BPM * 0.4 */ + ctx->remaining_samples_in_tick += (float)ctx->rate / ((float)ctx->bpm * 0.4f); +} + +static float jar_xm_next_of_sample(jar_xm_channel_context_t* ch) { + if(ch->instrument == NULL || ch->sample == NULL || ch->sample_position < 0) { +#if JAR_XM_RAMPING + if(ch->frame_count < jar_xm_SAMPLE_RAMPING_POINTS) { + return jar_xm_LERP(ch->end_of_previous_sample[ch->frame_count], .0f, (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + } +#endif + return .0f; + } + if(ch->sample->length == 0) { + return .0f; + } + + float u, v, t; + gf_uint32_t a, b; + a = (gf_uint32_t)ch->sample_position; /* This cast is fine, + * sample_position will not + * go above integer + * ranges */ + if(JAR_XM_LINEAR_INTERPOLATION) { + b = a + 1; + t = ch->sample_position - a; /* Cheaper than fmodf(., 1.f) */ + } + u = ch->sample->data[a]; + + switch(ch->sample->loop_type) { + + case jar_xm_NO_LOOP: + if(JAR_XM_LINEAR_INTERPOLATION) { + v = (b < ch->sample->length) ? ch->sample->data[b] : .0f; + } + ch->sample_position += ch->step; + if(ch->sample_position >= ch->sample->length) { + ch->sample_position = -1; + } + break; + + case jar_xm_FORWARD_LOOP: + if(JAR_XM_LINEAR_INTERPOLATION) { + v = ch->sample->data[(b == ch->sample->loop_end) ? ch->sample->loop_start : b]; + } + ch->sample_position += ch->step; + while(ch->sample_position >= ch->sample->loop_end) { + ch->sample_position -= ch->sample->loop_length; + } + break; + + case jar_xm_PING_PONG_LOOP: + if(ch->ping) { + ch->sample_position += ch->step; + } else { + ch->sample_position -= ch->step; + } + /* XXX: this may not work for very tight ping-pong loops + * (ie switches direction more than once per sample */ + if(ch->ping) { + if(JAR_XM_LINEAR_INTERPOLATION) { + v = (b >= ch->sample->loop_end) ? ch->sample->data[a] : ch->sample->data[b]; + } + if(ch->sample_position >= ch->sample->loop_end) { + ch->ping = gf_false; + ch->sample_position = (ch->sample->loop_end << 1) - ch->sample_position; + } + /* sanity checking */ + if(ch->sample_position >= ch->sample->length) { + ch->ping = gf_false; + ch->sample_position -= ch->sample->length - 1; + } + } else { + if(JAR_XM_LINEAR_INTERPOLATION) { + v = u; + u = (b == 1 || b - 2 <= ch->sample->loop_start) ? ch->sample->data[a] : ch->sample->data[b - 2]; + } + if(ch->sample_position <= ch->sample->loop_start) { + ch->ping = gf_true; + ch->sample_position = (ch->sample->loop_start << 1) - ch->sample_position; + } + /* sanity checking */ + if(ch->sample_position <= .0f) { + ch->ping = gf_true; + ch->sample_position = .0f; + } + } + break; + + default: + v = .0f; + break; + } + + float endval = JAR_XM_LINEAR_INTERPOLATION ? jar_xm_LERP(u, v, t) : u; + +#if JAR_XM_RAMPING + if(ch->frame_count < jar_xm_SAMPLE_RAMPING_POINTS) { + /* Smoothly transition between old and new sample. */ + return jar_xm_LERP(ch->end_of_previous_sample[ch->frame_count], endval, (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + } +#endif + + return endval; +} + +static void jar_xm_sample(jar_xm_context_t* ctx, float* left, float* right) { + if(ctx->remaining_samples_in_tick <= 0) { + jar_xm_tick(ctx); + } + ctx->remaining_samples_in_tick--; + + *left = 0.f; + *right = 0.f; + + if(ctx->max_loop_count > 0 && ctx->loop_count >= ctx->max_loop_count) { + return; + } + + for(gf_uint8_t i = 0; i < ctx->module.num_channels; ++i) { + jar_xm_channel_context_t* ch = ctx->channels + i; + + if(ch->instrument == NULL || ch->sample == NULL || ch->sample_position < 0) { + continue; + } + + const float fval = jar_xm_next_of_sample(ch); + + if(!ch->muted && !ch->instrument->muted) { + *left += fval * ch->actual_volume * (1.f - ch->actual_panning); + *right += fval * ch->actual_volume * ch->actual_panning; + } + +#if JAR_XM_RAMPING + ch->frame_count++; + jar_xm_SLIDE_TOWARDS(ch->actual_volume, ch->target_volume, ctx->volume_ramp); + jar_xm_SLIDE_TOWARDS(ch->actual_panning, ch->target_panning, ctx->panning_ramp); +#endif + } + + const float fgvol = ctx->global_volume * ctx->amplification; + *left *= fgvol; + *right *= fgvol; + +#if JAR_XM_DEBUG + if(fabs(*left) > 1 || fabs(*right) > 1) { + JX_DEBUG("clipping frame: %f %f, this is a bad module or a libxm bug", *left, *right); + } +#endif +} + +void jar_xm_generate_samples(jar_xm_context_t* ctx, float* output, size_t numsamples) { + if(ctx && output) { + ctx->generated_samples += numsamples; + for(size_t i = 0; i < numsamples; i++) { + jar_xm_sample(ctx, output + (2 * i), output + (2 * i + 1)); + } + } +} + +gf_uint64_t jar_xm_get_remaining_samples(jar_xm_context_t* ctx) { + gf_uint64_t total = 0; + gf_uint8_t currentLoopCount = jar_xm_get_loop_count(ctx); + jar_xm_set_max_loop_count(ctx, 0); + + while(jar_xm_get_loop_count(ctx) == currentLoopCount) { + total += ctx->remaining_samples_in_tick; + ctx->remaining_samples_in_tick = 0; + jar_xm_tick(ctx); + } + + ctx->loop_count = currentLoopCount; + return total; +} + +/*--------------------------------------------*/ +/*FILE LOADER - TODO - NEEDS TO BE CLEANED UP*/ +/*--------------------------------------------*/ + +#undef JX_DEBUG +#define JX_DEBUG(...) \ + do { \ + fprintf(stderr, __VA_ARGS__); \ + fflush(stderr); \ + } while(0) + +#define JX_DEBUG_ERR(...) \ + do { \ + fprintf(stderr, __VA_ARGS__); \ + fflush(stderr); \ + } while(0) + +#define JX_FATAL(...) \ + do { \ + fprintf(stderr, __VA_ARGS__); \ + fflush(stderr); \ + exit(1); \ + } while(0) + +#define JX_FATAL_ERR(...) \ + do { \ + fprintf(stderr, __VA_ARGS__); \ + fflush(stderr); \ + exit(1); \ + } while(0) + +int jar_xm_create_context_from_file(jar_xm_context_t** ctx, gf_uint32_t rate, const char* filename) { + FILE* xmf; + int size; + + xmf = fopen(filename, "rb"); + if(xmf == NULL) { + JX_DEBUG_ERR("Could not open input file"); + *ctx = NULL; + return 3; + } + + fseek(xmf, 0, SEEK_END); + size = ftell(xmf); + rewind(xmf); + if(size == -1) { + fclose(xmf); + JX_DEBUG_ERR("fseek() failed"); + *ctx = NULL; + return 4; + } + + char* data = malloc(size + 1); + if(fread(data, 1, size, xmf) < size) { + fclose(xmf); + JX_DEBUG_ERR("fread() failed"); + *ctx = NULL; + return 5; + } + + fclose(xmf); + + switch(jar_xm_create_context_safe(ctx, data, size, rate)) { + case 0: + break; + + case 1: + JX_DEBUG("could not create context: module is not sane\n"); + *ctx = NULL; + return 1; + break; + + case 2: + JX_FATAL("could not create context: malloc failed\n"); + return 2; + break; + + default: + JX_FATAL("could not create context: unknown error\n"); + return 6; + break; + } + + return 0; +} + +#endif /*end of JAR_XM_IMPLEMENTATION*/ +/*-------------------------------------------------------------------------------*/ + +#endif /*end of INCLUDE_JAR_XM_H*/ diff --git a/engine/gf_jar_mod.c b/engine/gf_jar_mod.c new file mode 100644 index 0000000..f7e8fd0 --- /dev/null +++ b/engine/gf_jar_mod.c @@ -0,0 +1,2 @@ +#define JAR_MOD_IMPLEMENTATION +#include diff --git a/engine/gf_jar_xm.c b/engine/gf_jar_xm.c new file mode 100644 index 0000000..9572ba4 --- /dev/null +++ b/engine/gf_jar_xm.c @@ -0,0 +1,2 @@ +#define JAR_XM_IMPLEMENTATION +#include diff --git a/engine/include/gf_input.h b/engine/include/gf_input.h index 97272be..cdf27f2 100644 --- a/engine/include/gf_input.h +++ b/engine/include/gf_input.h @@ -11,6 +11,7 @@ #include /* Type */ +#include /* Engine */ diff --git a/engine/include/gf_macro.h b/engine/include/gf_macro.h index bc46a79..b326680 100644 --- a/engine/include/gf_macro.h +++ b/engine/include/gf_macro.h @@ -113,6 +113,14 @@ #define GF_EXPOSE_FONT #endif +#ifndef GF_EXPOSE_INPUT +/** + * @~english + * @brief Expose input interface properties + */ +#define GF_EXPOSE_INPUT +#endif + #ifndef GF_EXPOSE_GRAPHIC /** * @~english diff --git a/engine/include/gf_type/compat.h b/engine/include/gf_type/compat.h new file mode 100644 index 0000000..3daaebd --- /dev/null +++ b/engine/include/gf_type/compat.h @@ -0,0 +1,34 @@ +/** + * @file gf_type/compat.h + * @~english + * @brief Type definitions for C99 types in C89 + */ + +#ifndef __GF_TYPE_COMPAT_H__ +#define __GF_TYPE_COMPAT_H__ + +#ifdef __STDC_VERSION__ +#if __STDC_VERSION__ >= 199901L +#define GF_IS_C99 +#endif +#endif + +#ifdef GF_IS_C99 +#include +#include + +#define gf_true true +#define gf_false false +typedef bool gf_bool_t; +typedef uint8_t gf_uint8_t; +typedef uint16_t gf_uint16_t; +typedef uint32_t gf_uint32_t; +typedef uint64_t gf_uint64_t; +typedef int8_t gf_int8_t; +typedef int16_t gf_int16_t; +typedef int32_t gf_int32_t; +typedef int64_t gf_int64_t; +#else +#endif + +#endif diff --git a/engine/include/gf_type/input.h b/engine/include/gf_type/input.h new file mode 100644 index 0000000..cc0135e --- /dev/null +++ b/engine/include/gf_type/input.h @@ -0,0 +1,24 @@ +/** + * @file gf_type/input.h + * @~english + * @brief Type definitions related to input interface + */ + +#ifndef __GF_TYPE_INPUT_H__ +#define __GF_TYPE_INPUT_H__ + +#include +#include + +#ifdef GF_EXPOSE_INPUT +/* External library */ + +/* Engine */ +#include +#include + +/* Standard */ +#else +#endif + +#endif diff --git a/engine/premake5.lua b/engine/premake5.lua index 8323ff0..6aed97f 100644 --- a/engine/premake5.lua +++ b/engine/premake5.lua @@ -286,7 +286,8 @@ project("GoldFish") "external/lua", "external/zlib", "external/miniaudio", - "external/stb" + "external/stb", + "external/jar", }) files({ "include/**.h", diff --git a/tool/format.sh b/tool/format.sh index 6fb59cb..22c40d1 100755 --- a/tool/format.sh +++ b/tool/format.sh @@ -1,2 +1,2 @@ #!/bin/sh -exec clang-format --verbose -i `find engine src "(" -name "*.c" -or -name "*.h" ")" -and -not -path "engine/external/*"` +exec clang-format --verbose -i `find engine src "(" -name "*.c" -or -name "*.h" ")" -and -not -path "engine/external/*"` engine/external/jar/*.h