From efbe92cf9d60c5de3e7a1002ab391187beeb7bf4 Mon Sep 17 00:00:00 2001 From: m4ntr0n1c Date: Sun, 4 Apr 2021 20:46:28 -0400 Subject: [PATCH] Update jar_xm.h update to version 0.31 --- src/external/jar_xm.h | 539 ++++++++++++++++++++++++++---------------- 1 file changed, 330 insertions(+), 209 deletions(-) diff --git a/src/external/jar_xm.h b/src/external/jar_xm.h index 14594735d..36d6fb3fc 100644 --- a/src/external/jar_xm.h +++ b/src/external/jar_xm.h @@ -18,6 +18,9 @@ // v0.2.3 2021-03-11 m4ntr0n1c: Fix tempo, bpm and volume on song stop / start / restart / loop // v0.2.4 2021-03-17 m4ntr0n1c: Sanitize code for readability // v0.2.5 2021-03-22 m4ntr0n1c: Minor adjustments +// v0.2.6 2021-04-01 m4ntr0n1c: Minor fixes and optimisation +// v0.3.0 2021-04-03 m4ntr0n1c: Addition of Stereo sample support, Linear Interpolation and Ramping now addressable options in code +// v0.3.1 2021-04-04 m4ntr0n1c: Volume effects column adjustments, sample offset handling adjustments // // USAGE: // @@ -56,9 +59,7 @@ #include #define JAR_XM_DEBUG 0 -#define JAR_XM_LINEAR_INTERPOLATION 0 // speed increase with decrease in quality #define JAR_XM_DEFENSIVE 1 -#define JAR_XM_RAMPING 1 #define JAR_XM_RAYLIB 1 // set to 0 to disable the RayLib visualizer extension // Allow custom memory allocators @@ -118,7 +119,7 @@ void jar_xm_free_context(jar_xm_context_t* ctx); // * @param numsamples number of samples to generate void jar_xm_generate_samples(jar_xm_context_t* ctx, float* output, size_t numsamples); -//** Play the module, resample from 32 bit to 16 bit, and put the sound samples in an output buffer. +//** Play the module, resample from float to 16 bit, and put the sound samples in an output buffer. // * @param output buffer of 2*numsamples elements (A left and right value for each sample) // * @param numsamples number of samples to generate void jar_xm_generate_samples_16bit(jar_xm_context_t* ctx, short* output, size_t numsamples) { @@ -126,12 +127,12 @@ void jar_xm_generate_samples_16bit(jar_xm_context_t* ctx, short* output, size_t jar_xm_generate_samples(ctx, musicBuffer, numsamples); if(output){ - for(int x=0;x<2*numsamples;x++) output[x] = musicBuffer[x] * SHRT_MAX; + for(int x=0;x<2*numsamples;x++) output[x] = (musicBuffer[x] * 32767.0f); // scale sample to signed small int } JARXM_FREE(musicBuffer); } -//** Play the module, resample from 32 bit to 8 bit, and put the sound samples in an output buffer. +//** Play the module, resample from float to 8 bit, and put the sound samples in an output buffer. // * @param output buffer of 2*numsamples elements (A left and right value for each sample) // * @param numsamples number of samples to generate void jar_xm_generate_samples_8bit(jar_xm_context_t* ctx, char* output, size_t numsamples) { @@ -139,7 +140,7 @@ void jar_xm_generate_samples_8bit(jar_xm_context_t* ctx, char* output, size_t nu jar_xm_generate_samples(ctx, musicBuffer, numsamples); if(output){ - for(int x=0;x<2*numsamples;x++) output[x] = musicBuffer[x] * CHAR_MAX; + for(int x=0;x<2*numsamples;x++) output[x] = (musicBuffer[x] * 127.0f); // scale sample to signed 8 bit } JARXM_FREE(musicBuffer); } @@ -254,12 +255,10 @@ extern int __fail[-1]; #define TRACKER_NAME_LENGTH 20 #define PATTERN_ORDER_TABLE_LENGTH 256 #define NUM_NOTES 96 // from 1 to 96, where 1 = C-0 -#define NUM_ENVELOPE_POINTS 12 +#define NUM_ENVELOPE_POINTS 12 // to be verified if 12 is the max #define MAX_NUM_ROWS 256 -#if JAR_XM_RAMPING -#define jar_xm_SAMPLE_RAMPING_POINTS 0x20 -#endif +#define jar_xm_SAMPLE_RAMPING_POINTS 8 /* ----- Data types ----- */ @@ -306,7 +305,7 @@ typedef struct jar_xm_envelope_s jar_xm_envelope_t; struct jar_xm_sample_s { char name[SAMPLE_NAME_LENGTH + 1]; int8_t bits; /* Either 8 or 16 */ - + int8_t stereo; uint32_t length; uint32_t loop_start; uint32_t loop_length; @@ -363,6 +362,8 @@ struct jar_xm_sample_s { uint16_t num_channels; uint16_t num_patterns; uint16_t num_instruments; + uint16_t linear_interpolation; + uint16_t ramping; jar_xm_frequency_type_t frequency_type; uint8_t pattern_table[PATTERN_ORDER_TABLE_LENGTH]; @@ -433,14 +434,15 @@ struct jar_xm_sample_s { uint64_t latest_trigger; bool 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 end_of_previous_sample_left[jar_xm_SAMPLE_RAMPING_POINTS]; + float end_of_previous_sample_right[jar_xm_SAMPLE_RAMPING_POINTS]; + float curr_left; + float curr_right; float actual_panning; float actual_volume; @@ -460,10 +462,8 @@ struct jar_xm_sample_s { uint16_t bpm; float global_volume; -#if JAR_XM_RAMPING float volume_ramp; /* How much is a channel final volume allowed to change per sample; this is used to avoid abrubt volume changes which manifest as "clicks" in the generated sound. */ float panning_ramp; /* Same for panning. */ -#endif uint8_t current_table_index; uint8_t current_row; @@ -560,10 +560,8 @@ int jar_xm_create_context_safe(jar_xm_context_t** ctxp, const char* moddata, siz ctx->default_global_volume = 1.f; ctx->global_volume = ctx->default_global_volume; -#if JAR_XM_RAMPING ctx->volume_ramp = (1.f / 128.f); ctx->panning_ramp = (1.f / 128.f); -#endif for(uint8_t i = 0; i < ctx->module.num_channels; ++i) { jar_xm_channel_context_t *ch = ctx->channels + i; @@ -800,6 +798,8 @@ char* jar_xm_load_module(jar_xm_context_t* ctx, const char* moddata, size_t modd mod->num_patterns = READ_U16(offset + 10); mod->num_instruments = READ_U16(offset + 12); mod->patterns = (jar_xm_pattern_t*)mempool; + mod->linear_interpolation = 0; // Linear interpolation can be set after loading + mod->ramping = 1; // ramping can be set after loading mempool += mod->num_patterns * sizeof(jar_xm_pattern_t); mempool = ALIGN_PTR(mempool, 16); mod->instruments = (jar_xm_instrument_t*)mempool; @@ -940,7 +940,7 @@ char* jar_xm_load_module(jar_xm_context_t* ctx, const char* moddata, size_t modd /* Instrument header size */ offset += READ_U32(offset); - for(uint16_t j = 0; j < instr->num_samples; ++j) { + for(int j = 0; j < instr->num_samples; ++j) { /* Read sample header */ jar_xm_sample_t* sample = instr->samples + j; @@ -948,19 +948,25 @@ char* jar_xm_load_module(jar_xm_context_t* ctx, const char* moddata, size_t modd 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->volume = (float)(READ_U8(offset + 12) << 2) / 256.f; + if (sample->volume > 1.0f) {sample->volume = 1.f;}; sample->finetune = (int8_t)READ_U8(offset + 13); 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 { + switch (flags & 3) { + case 2: + case 3: sample->loop_type = jar_xm_PING_PONG_LOOP; - } - sample->bits = (flags & (1 << 4)) ? 16 : 8; - sample->panning = (float)READ_U8(offset + 15) / (float)0xFF; + case 1: + sample->loop_type = jar_xm_FORWARD_LOOP; + break; + default: + sample->loop_type = jar_xm_NO_LOOP; + break; + }; + sample->bits = (flags & 0x10) ? 16 : 8; + sample->stereo = (flags & 0x20) ? 1 : 0; + sample->panning = (float)READ_U8(offset + 15) / 255.f; sample->relative_note = (int8_t)READ_U8(offset + 16); READ_MEMCPY(sample->name, 18, SAMPLE_NAME_LENGTH); sample->data = (float*)mempool; @@ -975,33 +981,69 @@ char* jar_xm_load_module(jar_xm_context_t* ctx, const char* moddata, size_t modd /* 8 bit sample */ mempool += sample->length * sizeof(float); } + // Adjust loop points to reflect half of the reported length (stereo) + if (sample->stereo && sample->loop_type != jar_xm_NO_LOOP) { + div_t lstart = div(READ_U32(offset + 4), 2); + sample->loop_start = lstart.quot; + div_t llength = div(READ_U32(offset + 8), 2); + sample->loop_length = llength.quot; + sample->loop_end = sample->loop_start + sample->loop_length; + }; + offset += sample_header_size; } - for(uint16_t j = 0; j < instr->num_samples; ++j) { + // Read all samples and convert them to float values + for(int j = 0; j < instr->num_samples; ++j) { /* Read sample data */ jar_xm_sample_t* sample = instr->samples + j; - uint32_t length = sample->length; - if(sample->bits == 16) { - int16_t v = 0; - for(uint32_t k = 0; k < length; ++k) { - v = v + (int16_t)READ_U16(offset + (k << 1)); - sample->data[k] = (float)v / (float)(1 << 15); - } - offset += sample->length << 1; + int length = sample->length; + if (sample->stereo) { + // Since it is stereo, we cut the sample in half (treated as single channel) + div_t result = div(sample->length, 2); + if(sample->bits == 16) { + int16_t v = 0; + for(int k = 0; k < length; ++k) { + if (k == result.quot) { v = 0;}; + v = v + (int16_t)READ_U16(offset + (k << 1)); + sample->data[k] = (float) v / 32768.f ;//* sign; + if(sample->data[k] < -1.0) {sample->data[k] = -1.0;} else if(sample->data[k] > 1.0) {sample->data[k] = 1.0;}; + } + offset += sample->length << 1; + } else { + int8_t v = 0; + for(int k = 0; k < length; ++k) { + if (k == result.quot) { v = 0;}; + v = v + (int8_t)READ_U8(offset + k); + sample->data[k] = (float)v / 128.f ;//* sign; + if(sample->data[k] < -1.0) {sample->data[k] = -1.0;} else if(sample->data[k] > 1.0) {sample->data[k] = 1.0;}; + } + offset += sample->length; + }; + sample->length = result.quot; } else { - int8_t v = 0; - for(uint32_t k = 0; k < length; ++k) { - v = v + (int8_t)READ_U8(offset + k); - sample->data[k] = (float)v / (float)(1 << 7); + if(sample->bits == 16) { + int16_t v = 0; + for(int k = 0; k < length; ++k) { + v = v + (int16_t)READ_U16(offset + (k << 1)); + sample->data[k] = (float) v / 32768.f ;//* sign; + if(sample->data[k] < -1.0) {sample->data[k] = -1.0;} else if(sample->data[k] > 1.0) {sample->data[k] = 1.0;}; + } + offset += sample->length << 1; + } else { + int8_t v = 0; + for(int k = 0; k < length; ++k) { + v = v + (int8_t)READ_U8(offset + k); + sample->data[k] = (float)v / 128.f ;//* sign; + if(sample->data[k] < -1.0) {sample->data[k] = -1.0;} else if(sample->data[k] > 1.0) {sample->data[k] = 1.0;}; + } + offset += sample->length; } - offset += sample->length; } - } - } - + }; + }; return mempool; -} +}; //------------------------------------------------------------------------------- //THE FOLLOWING IS FOR PLAYING @@ -1036,8 +1078,8 @@ 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*); +static void jar_xm_next_of_sample(jar_xm_context_t*, jar_xm_channel_context_t*, int); +static void jar_xm_mixdown(jar_xm_context_t*, float*, float*); #define jar_xm_TRIGGER_KEEP_VOLUME (1 << 0) #define jar_xm_TRIGGER_KEEP_PERIOD (1 << 1) @@ -1117,7 +1159,7 @@ static void jar_xm_autovibrato(jar_xm_context_t* ctx, jar_xm_channel_context_t* 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) { sweep = jar_xm_LERP(0.f, 1.f, (float)ch->autovibrato_ticks / (float)instr->vibrato_sweep); } // ?? WHY ?? + if(ch->autovibrato_ticks < instr->vibrato_sweep) { 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); @@ -1171,32 +1213,14 @@ static void jar_xm_pitch_slide(jar_xm_context_t* ctx, jar_xm_channel_context_t* } static void jar_xm_panning_slide(jar_xm_channel_context_t* ch, uint8_t rawval) { - float f; - if ((rawval & 0xF0) && (rawval & 0x0F)) { return; } /* outside boundaries, exit */ - 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); - } -} + if (rawval & 0xF0) {ch->panning += (float)((rawval & 0xF0 )>> 4) / (float)0xFF;}; + if (rawval & 0x0F) {ch->panning -= (float)(rawval & 0x0F) / (float)0xFF;}; +}; static void jar_xm_volume_slide(jar_xm_channel_context_t* ch, uint8_t rawval) { - float f; - if((rawval & 0xF0) && (rawval & 0x0F)) { return; } /* outside boundaries, exit */ - 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); - } -} + if (rawval & 0xF0) {ch->volume += (float)((rawval & 0xF0) >> 4) / (float)0x40;}; + if (rawval & 0x0F) {ch->volume -= (float)(rawval & 0x0F) / (float)0x40;}; +}; static float jar_xm_envelope_lerp(jar_xm_envelope_point_t* a, jar_xm_envelope_point_t* b, uint16_t pos) { /* Linear interpolation between two envelope points */ @@ -1305,6 +1329,7 @@ static void jar_xm_update_frequency(jar_xm_context_t* ctx, jar_xm_channel_contex } 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) { + jar_xm_module_t* mod = &(ctx->module); if(s->instrument > 0) { if(HAS_TONE_PORTAMENTO(ch->current) && ch->instrument != NULL && ch->sample != NULL) { /* Tone portamento in effect */ jar_xm_trigger_note(ctx, ch, jar_xm_TRIGGER_KEEP_PERIOD | jar_xm_TRIGGER_KEEP_SAMPLE_POSITION); @@ -1322,22 +1347,22 @@ static void jar_xm_handle_note_and_instrument(jar_xm_context_t* ctx, jar_xm_chan } if(NOTE_IS_VALID(s->note)) { - /* Yes, the real note number is s->note -1. Try finding THAT in any of the specs! :-) */ + // note value is s->note -1 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 */ + } else if(instr == NULL || ch->instrument->num_samples == 0) { /* Issue on 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 + if (mod->ramping) { + for(int i = 0; i < jar_xm_SAMPLE_RAMPING_POINTS; ++i) { + jar_xm_next_of_sample(ctx, ch, i); + } + ch->frame_count = 0; + }; 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) { @@ -1345,43 +1370,14 @@ static void jar_xm_handle_note_and_instrument(jar_xm_context_t* ctx, jar_xm_chan } else { /* Ghost note: keep old volume */ jar_xm_trigger_note(ctx, ch, jar_xm_TRIGGER_KEEP_VOLUME); } - } else { /* Bad sample (???) */ + } else { jar_xm_cut_note(ch); } } - } else if(s->note == NOTE_OFF) { /* Key Off */ + } else if(s->note == NOTE_OFF) { jar_xm_key_off(ch); } - // Interpret volume column - 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; - } - // Interpret Effect column switch(s->effect_type) { case 1: /* 1xx: Portamento up */ @@ -1408,15 +1404,39 @@ static void jar_xm_handle_note_and_instrument(jar_xm_context_t* ctx, jar_xm_chan if(s->effect_param >> 4) { ch->tremolo_param = (s->effect_param & 0xF0) | (ch->tremolo_param & 0x0F); } /* Set tremolo speed */ break; case 8: /* 8xx: Set panning */ - ch->panning = (float)s->effect_param / (float)0xFF; + ch->panning = (float)s->effect_param / 255.f; break; case 9: /* 9xx: Sample offset */ - if(ch->sample != NULL && NOTE_IS_VALID(s->note)) { + if(ch->sample != 0) { //&& NOTE_IS_VALID(s->note)) { 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; - } else { - ch->sample_position = final_offset; + switch (ch->sample->loop_type) { + case jar_xm_NO_LOOP: + if(final_offset >= ch->sample->length) { /* Pretend the sample dosen't loop and is done playing */ + ch->sample_position = -1; + } else { + ch->sample_position = final_offset; + } + break; + case jar_xm_FORWARD_LOOP: + if (final_offset >= ch->sample->loop_end) { + ch->sample_position -= ch->sample->loop_length; + } else if(final_offset >= ch->sample->length) { + ch->sample_position = ch->sample->loop_start; + } else { + ch->sample_position = final_offset; + } + break; + case jar_xm_PING_PONG_LOOP: + if(final_offset >= ch->sample->loop_end) { + ch->ping = false; + ch->sample_position = (ch->sample->loop_end << 1) - ch->sample_position; + } else if(final_offset >= ch->sample->length) { + ch->ping = false; + ch->sample_position -= ch->sample->length - 1; + } else { + ch->sample_position = final_offset; + }; + break; } } break; @@ -1595,6 +1615,8 @@ static void jar_xm_trigger_note(jar_xm_context_t* ctx, jar_xm_channel_context_t* static void jar_xm_cut_note(jar_xm_channel_context_t* ch) { ch->volume = .0f; /* NB: this is not the same as Key Off */ +// ch->curr_left = .0f; +// ch->curr_right = .0f; } static void jar_xm_key_off(jar_xm_channel_context_t* ch) { @@ -1700,6 +1722,7 @@ static void jar_xm_tick(jar_xm_context_t* ctx) { jar_xm_row(ctx); // We have processed all ticks and we run the row } + jar_xm_module_t* mod = &(ctx->module); for(uint8_t i = 0; i < ctx->module.num_channels; ++i) { jar_xm_channel_context_t* ch = ctx->channels + i; jar_xm_envelopes(ch); @@ -1715,32 +1738,58 @@ static void jar_xm_tick(jar_xm_context_t* ctx) { jar_xm_update_frequency(ctx, ch); } - if(ctx->current_tick > 0) { // THIS CHECK SHOULD NOT BE NECESSARY *********** - switch(ch->current->volume_column >> 4) { - case 0x6: /* Volume slide down */ + // Effects in volumne column mostly handled on a per tick basis + switch(ch->current->volume_column & 0xF0) { + case 0x50: // Checks for volume = 64 + if(ch->current->volume_column != 0x50) break; + case 0x10: // Set volume 0-15 + case 0x20: // Set volume 16-32 + case 0x30: // Set volume 32-48 + case 0x40: // Set volume 48-64 + ch->volume = (float)(ch->current->volume_column - 16) / 64.0f; + break; + case 0x60: // Volume slide down jar_xm_volume_slide(ch, ch->current->volume_column & 0x0F); break; - case 0x7: /* Volume slide up */ + case 0x70: // Volume slide up jar_xm_volume_slide(ch, ch->current->volume_column << 4); break; - case 0xB: /* Vibrato */ + case 0x80: // Fine volume slide down + jar_xm_volume_slide(ch, ch->current->volume_column & 0x0F); + break; + case 0x90: // Fine volume slide up + jar_xm_volume_slide(ch, ch->current->volume_column << 4); + break; + case 0xA0: // Set vibrato speed + ch->vibrato_param = (ch->vibrato_param & 0x0F) | ((ch->current->volume_column & 0x0F) << 4); + break; + case 0xB0: // Vibrato ch->vibrato_in_progress = false; jar_xm_vibrato(ctx, ch, ch->vibrato_param, ch->vibrato_ticks++); break; - case 0xD: /* Panning slide left */ + case 0xC0: // Set panning + if(!ctx->current_tick ) { + ch->panning = (float)(ch->current->volume_column & 0x0F) / 15.0f; + } + break; + case 0xD0: // Panning slide left jar_xm_panning_slide(ch, ch->current->volume_column & 0x0F); break; - case 0xE: /* Panning slide right */ + case 0xE0: // Panning slide right jar_xm_panning_slide(ch, ch->current->volume_column << 4); break; - case 0xF: /* Tone portamento */ + case 0xF0: // Tone portamento + if(!ctx->current_tick ) { + if(ch->current->volume_column & 0x0F) { ch->tone_portamento_param = ((ch->current->volume_column & 0x0F) << 4) | (ch->current->volume_column & 0x0F); } + }; jar_xm_tone_portamento(ctx, ch); break; default: break; } - }; + // Only some standard effects handled on a per tick basis + // see jar_xm_handle_note_and_instrument for all effects handling on a per row basis switch(ch->current->effect_type) { case 0: /* 0xy: Arpeggio */ if(ch->current->effect_param > 0) { @@ -1802,6 +1851,10 @@ static void jar_xm_tick(jar_xm_context_t* ctx) { if(ctx->current_tick == 0) break; jar_xm_tremolo(ctx, ch, ch->tremolo_param, ch->tremolo_ticks++); break; + case 8: /* 8xy: Set panning */ + break; + case 9: /* 9xy: Sample offset */ + break; case 0xA: /* Axy: Volume slide */ if(ctx->current_tick == 0) break; jar_xm_volume_slide(ch, ch->volume_slide_param); @@ -1831,7 +1884,8 @@ static void jar_xm_tick(jar_xm_context_t* ctx) { break; } break; - + case 16: /* Fxy: Set tempo/BPM */ + 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)) { break; }; /* Invalid state */ @@ -1849,6 +1903,8 @@ static void jar_xm_tick(jar_xm_context_t* ctx) { case 20: /* Kxx: Key off */ if(ctx->current_tick == ch->current->effect_param) { jar_xm_key_off(ch); }; break; + case 21: /* Lxx: Set envelope position */ + break; case 25: /* Pxy: Panning slide */ if(ctx->current_tick == 0) break; jar_xm_panning_slide(ch, ch->panning_slide_param); @@ -1883,13 +1939,13 @@ static void jar_xm_tick(jar_xm_context_t* ctx) { 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 + if (mod->ramping) { + ch->target_panning = panning; + ch->target_volume = volume; + } else { + ch->actual_panning = panning; + ch->actual_volume = volume; + }; }; ctx->current_tick++; // ok so we understand that ticks increment within the row @@ -1899,85 +1955,116 @@ static void jar_xm_tick(jar_xm_context_t* ctx) { ctx->extra_ticks = 0; }; - /* FT2 manual says number of ticks / second = BPM * 0.4 */ + // Number of ticks / second = BPM * 0.4 ctx->remaining_samples_in_tick += (float)ctx->rate / ((float)ctx->bpm * 0.4f); - -// THIS SHOULD BE HERE BUT CURRENTLY NOT POSSIBLE ************************* -// if(ctx->current_tick == 0) { - // We have processed all ticks and we run the row -// jar_xm_row(ctx); -// }; }; -static float jar_xm_next_of_sample(jar_xm_channel_context_t* ch) { +static void jar_xm_next_of_sample(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, int previous) { + jar_xm_module_t* mod = &(ctx->module); + +// ch->curr_left = 0.f; +// ch->curr_right = 0.f; 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); + ch->curr_left = 0.f; + ch->curr_right = 0.f; + if (mod->ramping) { + if (ch->frame_count < jar_xm_SAMPLE_RAMPING_POINTS) { + if (previous > -1) { + ch->end_of_previous_sample_left[previous] = jar_xm_LERP(ch->end_of_previous_sample_left[ch->frame_count], ch->curr_left, (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + ch->end_of_previous_sample_right[previous] = jar_xm_LERP(ch->end_of_previous_sample_right[ch->frame_count], ch->curr_right, (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + } else { + ch->curr_left = jar_xm_LERP(ch->end_of_previous_sample_left[ch->frame_count], ch->curr_left, (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + ch->curr_right = jar_xm_LERP(ch->end_of_previous_sample_right[ch->frame_count], ch->curr_right, (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + }; + }; }; -#endif - return .0f; + return; }; if(ch->sample->length == 0) { - return .0f; + return; }; - float u, v, t; - uint32_t a, b; - a = (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]; + float t = 0.f; + uint32_t b = 0; + if(mod->linear_interpolation) { + b = ch->sample_position + 1; + t = ch->sample_position - (uint32_t)ch->sample_position; /* Cheaper than fmodf(., 1.f) */ + }; + float u_left, u_right; + u_left = ch->sample->data[(uint32_t)ch->sample_position]; + if (ch->sample->stereo) { + u_right = ch->sample->data[(uint32_t)ch->sample_position + ch->sample->length]; + } else { + u_right = u_left; + }; + float v_left = 0.f, v_right = 0.f; 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; + if(mod->linear_interpolation) { + v_left = (b < ch->sample->length) ? ch->sample->data[b] : .0f; + if (ch->sample->stereo) { + v_right = (b < ch->sample->length) ? ch->sample->data[b + ch->sample->length] : .0f; + } else { + v_right = v_left; + }; }; ch->sample_position += ch->step; - if(ch->sample_position >= ch->sample->length) { ch->sample_position = -1; } + if(ch->sample_position >= ch->sample->length) { ch->sample_position = -1; } // stop playing this sample 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 ]; + if(mod->linear_interpolation) { + v_left = ch->sample->data[ (b == ch->sample->loop_end) ? ch->sample->loop_start : b ]; + if (ch->sample->stereo) { + v_right = ch->sample->data[ (b == ch->sample->loop_end) ? ch->sample->loop_start + ch->sample->length : b + ch->sample->length]; + } else { + v_right = v_left; + }; }; ch->sample_position += ch->step; - while(ch->sample_position >= ch->sample->loop_end) { + if (ch->sample_position >= ch->sample->loop_end) { ch->sample_position -= ch->sample->loop_length; }; + if(ch->sample_position >= ch->sample->length) { + ch->sample_position = ch->sample->loop_start; + }; 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(mod->linear_interpolation) { + v_left = (b >= ch->sample->loop_end) ? ch->sample->data[(uint32_t)ch->sample_position] : ch->sample->data[b]; + if (ch->sample->stereo) { + v_right = (b >= ch->sample->loop_end) ? ch->sample->data[(uint32_t)ch->sample_position + ch->sample->length] : ch->sample->data[b + ch->sample->length]; + } else { + v_right = v_left; + }; }; + ch->sample_position += ch->step; if(ch->sample_position >= ch->sample->loop_end) { ch->ping = false; ch->sample_position = (ch->sample->loop_end << 1) - ch->sample_position; }; - /* sanity checking */ if(ch->sample_position >= ch->sample->length) { ch->ping = 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(mod->linear_interpolation) { + v_left = u_left; + v_right = u_right; + u_left = (b == 1 || b - 2 <= ch->sample->loop_start) ? ch->sample->data[(uint32_t)ch->sample_position] : ch->sample->data[b - 2]; + if (ch->sample->stereo) { + u_right = (b == 1 || b - 2 <= ch->sample->loop_start) ? ch->sample->data[(uint32_t)ch->sample_position + ch->sample->length] : ch->sample->data[b + ch->sample->length - 2]; + } else { + u_right = u_left; + }; }; + ch->sample_position -= ch->step; if(ch->sample_position <= ch->sample->loop_start) { ch->ping = true; ch->sample_position = (ch->sample->loop_start << 1) - ch->sample_position; }; - if (ch->sample_position <= .0f) { /* sanity check */ + if (ch->sample_position <= .0f) { ch->ping = true; ch->sample_position = .0f; }; @@ -1985,62 +2072,87 @@ static float jar_xm_next_of_sample(jar_xm_channel_context_t* ch) { break; default: - v = .0f; + v_left = .0f; + v_right = .0f; break; - } + }; - float endval = JAR_XM_LINEAR_INTERPOLATION ? jar_xm_LERP(u, v, t) : u; + float endval_left = mod->linear_interpolation ? jar_xm_LERP(u_left, v_left, t) : u_left; + float endval_right = mod->linear_interpolation ? jar_xm_LERP(u_right, v_right, t) : u_right; -#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; -} + if (mod->ramping) { + if(ch->frame_count < jar_xm_SAMPLE_RAMPING_POINTS) { + /* Smoothly transition between old and new sample. */ + if (previous > -1) { + ch->end_of_previous_sample_left[previous] = jar_xm_LERP(ch->end_of_previous_sample_left[ch->frame_count], endval_left, (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + ch->end_of_previous_sample_right[previous] = jar_xm_LERP(ch->end_of_previous_sample_right[ch->frame_count], endval_right, (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + } else { + ch->curr_left = jar_xm_LERP(ch->end_of_previous_sample_left[ch->frame_count], endval_left, (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + ch->curr_right = jar_xm_LERP(ch->end_of_previous_sample_right[ch->frame_count], endval_right, (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + }; + }; + }; -static void jar_xm_sample(jar_xm_context_t* ctx, float* left, float* right) { + if (previous > -1) { + ch->end_of_previous_sample_left[previous] = endval_left; + ch->end_of_previous_sample_right[previous] = endval_right; + } else { + ch->curr_left = endval_left; + ch->curr_right = endval_right; + }; +}; + +// gather all channel audio into stereo float +static void jar_xm_mixdown(jar_xm_context_t* ctx, float* left, float* right) { + jar_xm_module_t* mod = &(ctx->module); + 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; } + if(ctx->max_loop_count > 0 && ctx->loop_count > ctx->max_loop_count) { return; } for(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) { - const float fval = jar_xm_next_of_sample(ch); + jar_xm_next_of_sample(ctx, ch, -1); 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 - } - } + *left += ch->curr_left * ch->actual_volume * (1.f - ch->actual_panning); + *right += ch->curr_right * ch->actual_volume * ch->actual_panning; + }; + + if (mod->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); + }; + }; + }; if (ctx->global_volume != 1.0f) { *left *= ctx->global_volume; *right *= ctx->global_volume; }; - // apply brick wall limiter when audio goes beyong bounderies + + // experimental +// float counter = (float)ctx->generated_samples * 0.0001f +// *left = tan(&left + sin(counter)); +// *right = tan(&right + cos(counter)); + + // apply brick wall limiter when audio goes beyond bounderies if(*left < -1.0) {*left = -1.0;} else if(*left > 1.0) {*left = 1.0;}; - if(*right < -1.0) {*right = -1.0;} else if(*right > 1.0) {*right = 1.0;}; -} + if(*right < -1.0) {*right = -1.0;} else if(*right > 1.0) {*right = 1.0;}; +}; 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)); - } - } -} + jar_xm_mixdown(ctx, output + (2 * i), output + (2 * i + 1)); + }; + }; +}; uint64_t jar_xm_get_remaining_samples(jar_xm_context_t* ctx) { uint64_t total = 0; @@ -2149,6 +2261,15 @@ void jar_xm_reset(jar_xm_context_t* ctx) { ctx->global_volume = ctx->default_global_volume; // reset to file default value } + +void jar_xm_flip_linear_interpolation(jar_xm_context_t* ctx) { + if (ctx->module.linear_interpolation) { + ctx->module.linear_interpolation = 0; + } else { + ctx->module.linear_interpolation = 1; + } +} + void jar_xm_table_jump(jar_xm_context_t* ctx, int table_ptr) { for (uint16_t i = 0; i < jar_xm_get_number_of_channels(ctx); i++) { jar_xm_cut_note(&ctx->channels[i]); @@ -2320,7 +2441,7 @@ void jar_xm_debug(jar_xm_context_t *ctx) { DrawRectangle(x, y, 8 * size, size, DARKGRAY); }; jar_xm_pattern_slot_t *s = cur->slots + j * ctx->module.num_channels + i; - jar_xm_channel_context_t *ch = ctx->channels + i; + // jar_xm_channel_context_t *ch = ctx->channels + i; if (s->note > 0) {DrawText(TextFormat("%s%s", xm_note_chr(s->note), xm_octave_chr(s->note) ), x, y, size, WHITE);} else {DrawText("...", x, y, size, GRAY);}; if (s->instrument > 0) { DrawText(TextFormat("%02X", s->instrument), x + size * 2, y, size, WHITE);