Update jar_xm.h

update to version 0.31
This commit is contained in:
m4ntr0n1c 2021-04-04 20:46:28 -04:00 committed by GitHub
parent e6b9cfe959
commit efbe92cf9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

539
src/external/jar_xm.h vendored
View File

@ -18,6 +18,9 @@
// v0.2.3 2021-03-11 m4ntr0n1c: Fix tempo, bpm and volume on song stop / start / restart / loop // 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.4 2021-03-17 m4ntr0n1c: Sanitize code for readability
// v0.2.5 2021-03-22 m4ntr0n1c: Minor adjustments // 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: // USAGE:
// //
@ -56,9 +59,7 @@
#include <stdint.h> #include <stdint.h>
#define JAR_XM_DEBUG 0 #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_DEFENSIVE 1
#define JAR_XM_RAMPING 1
#define JAR_XM_RAYLIB 1 // set to 0 to disable the RayLib visualizer extension #define JAR_XM_RAYLIB 1 // set to 0 to disable the RayLib visualizer extension
// Allow custom memory allocators // 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 // * @param numsamples number of samples to generate
void jar_xm_generate_samples(jar_xm_context_t* ctx, float* output, size_t numsamples); 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 output buffer of 2*numsamples elements (A left and right value for each sample)
// * @param numsamples number of samples to generate // * @param numsamples number of samples to generate
void jar_xm_generate_samples_16bit(jar_xm_context_t* ctx, short* output, size_t numsamples) { 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); jar_xm_generate_samples(ctx, musicBuffer, numsamples);
if(output){ 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); 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 output buffer of 2*numsamples elements (A left and right value for each sample)
// * @param numsamples number of samples to generate // * @param numsamples number of samples to generate
void jar_xm_generate_samples_8bit(jar_xm_context_t* ctx, char* output, size_t numsamples) { 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); jar_xm_generate_samples(ctx, musicBuffer, numsamples);
if(output){ 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); JARXM_FREE(musicBuffer);
} }
@ -254,12 +255,10 @@ extern int __fail[-1];
#define TRACKER_NAME_LENGTH 20 #define TRACKER_NAME_LENGTH 20
#define PATTERN_ORDER_TABLE_LENGTH 256 #define PATTERN_ORDER_TABLE_LENGTH 256
#define NUM_NOTES 96 // from 1 to 96, where 1 = C-0 #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 #define MAX_NUM_ROWS 256
#if JAR_XM_RAMPING #define jar_xm_SAMPLE_RAMPING_POINTS 8
#define jar_xm_SAMPLE_RAMPING_POINTS 0x20
#endif
/* ----- Data types ----- */ /* ----- Data types ----- */
@ -306,7 +305,7 @@ typedef struct jar_xm_envelope_s jar_xm_envelope_t;
struct jar_xm_sample_s { struct jar_xm_sample_s {
char name[SAMPLE_NAME_LENGTH + 1]; char name[SAMPLE_NAME_LENGTH + 1];
int8_t bits; /* Either 8 or 16 */ int8_t bits; /* Either 8 or 16 */
int8_t stereo;
uint32_t length; uint32_t length;
uint32_t loop_start; uint32_t loop_start;
uint32_t loop_length; uint32_t loop_length;
@ -363,6 +362,8 @@ struct jar_xm_sample_s {
uint16_t num_channels; uint16_t num_channels;
uint16_t num_patterns; uint16_t num_patterns;
uint16_t num_instruments; uint16_t num_instruments;
uint16_t linear_interpolation;
uint16_t ramping;
jar_xm_frequency_type_t frequency_type; jar_xm_frequency_type_t frequency_type;
uint8_t pattern_table[PATTERN_ORDER_TABLE_LENGTH]; uint8_t pattern_table[PATTERN_ORDER_TABLE_LENGTH];
@ -433,14 +434,15 @@ struct jar_xm_sample_s {
uint64_t latest_trigger; uint64_t latest_trigger;
bool muted; 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. //* 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_panning;
float target_volume; float target_volume;
unsigned long frame_count; unsigned long frame_count;
float end_of_previous_sample[jar_xm_SAMPLE_RAMPING_POINTS]; float end_of_previous_sample_left[jar_xm_SAMPLE_RAMPING_POINTS];
#endif float end_of_previous_sample_right[jar_xm_SAMPLE_RAMPING_POINTS];
float curr_left;
float curr_right;
float actual_panning; float actual_panning;
float actual_volume; float actual_volume;
@ -460,10 +462,8 @@ struct jar_xm_sample_s {
uint16_t bpm; uint16_t bpm;
float global_volume; 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 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. */ float panning_ramp; /* Same for panning. */
#endif
uint8_t current_table_index; uint8_t current_table_index;
uint8_t current_row; 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->default_global_volume = 1.f;
ctx->global_volume = ctx->default_global_volume; ctx->global_volume = ctx->default_global_volume;
#if JAR_XM_RAMPING
ctx->volume_ramp = (1.f / 128.f); ctx->volume_ramp = (1.f / 128.f);
ctx->panning_ramp = (1.f / 128.f); ctx->panning_ramp = (1.f / 128.f);
#endif
for(uint8_t i = 0; i < ctx->module.num_channels; ++i) { for(uint8_t i = 0; i < ctx->module.num_channels; ++i) {
jar_xm_channel_context_t *ch = ctx->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_patterns = READ_U16(offset + 10);
mod->num_instruments = READ_U16(offset + 12); mod->num_instruments = READ_U16(offset + 12);
mod->patterns = (jar_xm_pattern_t*)mempool; 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 += mod->num_patterns * sizeof(jar_xm_pattern_t);
mempool = ALIGN_PTR(mempool, 16); mempool = ALIGN_PTR(mempool, 16);
mod->instruments = (jar_xm_instrument_t*)mempool; 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 */ /* Instrument header size */
offset += READ_U32(offset); 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 */ /* Read sample header */
jar_xm_sample_t* sample = instr->samples + j; 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_start = READ_U32(offset + 4);
sample->loop_length = READ_U32(offset + 8); sample->loop_length = READ_U32(offset + 8);
sample->loop_end = sample->loop_start + sample->loop_length; 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); sample->finetune = (int8_t)READ_U8(offset + 13);
uint8_t flags = READ_U8(offset + 14); uint8_t flags = READ_U8(offset + 14);
if((flags & 3) == 0) { switch (flags & 3) {
sample->loop_type = jar_xm_NO_LOOP; case 2:
} else if((flags & 3) == 1) { case 3:
sample->loop_type = jar_xm_FORWARD_LOOP;
} else {
sample->loop_type = jar_xm_PING_PONG_LOOP; sample->loop_type = jar_xm_PING_PONG_LOOP;
} case 1:
sample->bits = (flags & (1 << 4)) ? 16 : 8; sample->loop_type = jar_xm_FORWARD_LOOP;
sample->panning = (float)READ_U8(offset + 15) / (float)0xFF; 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); sample->relative_note = (int8_t)READ_U8(offset + 16);
READ_MEMCPY(sample->name, 18, SAMPLE_NAME_LENGTH); READ_MEMCPY(sample->name, 18, SAMPLE_NAME_LENGTH);
sample->data = (float*)mempool; 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 */ /* 8 bit sample */
mempool += sample->length * sizeof(float); 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; 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 */ /* Read sample data */
jar_xm_sample_t* sample = instr->samples + j; jar_xm_sample_t* sample = instr->samples + j;
uint32_t length = sample->length; int length = sample->length;
if(sample->bits == 16) { if (sample->stereo) {
int16_t v = 0; // Since it is stereo, we cut the sample in half (treated as single channel)
for(uint32_t k = 0; k < length; ++k) { div_t result = div(sample->length, 2);
v = v + (int16_t)READ_U16(offset + (k << 1)); if(sample->bits == 16) {
sample->data[k] = (float)v / (float)(1 << 15); int16_t v = 0;
} for(int k = 0; k < length; ++k) {
offset += sample->length << 1; 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 { } else {
int8_t v = 0; if(sample->bits == 16) {
for(uint32_t k = 0; k < length; ++k) { int16_t v = 0;
v = v + (int8_t)READ_U8(offset + k); for(int k = 0; k < length; ++k) {
sample->data[k] = (float)v / (float)(1 << 7); 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; return mempool;
} };
//------------------------------------------------------------------------------- //-------------------------------------------------------------------------------
//THE FOLLOWING IS FOR PLAYING //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_row(jar_xm_context_t*);
static void jar_xm_tick(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_next_of_sample(jar_xm_context_t*, jar_xm_channel_context_t*, int);
static void jar_xm_sample(jar_xm_context_t*, float*, float*); static void jar_xm_mixdown(jar_xm_context_t*, float*, float*);
#define jar_xm_TRIGGER_KEEP_VOLUME (1 << 0) #define jar_xm_TRIGGER_KEEP_VOLUME (1 << 0)
#define jar_xm_TRIGGER_KEEP_PERIOD (1 << 1) #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; if(ch->instrument == NULL || ch->instrument->vibrato_depth == 0) return;
jar_xm_instrument_t* instr = ch->instrument; jar_xm_instrument_t* instr = ch->instrument;
float sweep = 1.f; 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; 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; 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); 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) { static void jar_xm_panning_slide(jar_xm_channel_context_t* ch, uint8_t rawval) {
float f; if (rawval & 0xF0) {ch->panning += (float)((rawval & 0xF0 )>> 4) / (float)0xFF;};
if ((rawval & 0xF0) && (rawval & 0x0F)) { return; } /* outside boundaries, exit */ if (rawval & 0x0F) {ch->panning -= (float)(rawval & 0x0F) / (float)0xFF;};
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, uint8_t rawval) { static void jar_xm_volume_slide(jar_xm_channel_context_t* ch, uint8_t rawval) {
float f; if (rawval & 0xF0) {ch->volume += (float)((rawval & 0xF0) >> 4) / (float)0x40;};
if((rawval & 0xF0) && (rawval & 0x0F)) { return; } /* outside boundaries, exit */ if (rawval & 0x0F) {ch->volume -= (float)(rawval & 0x0F) / (float)0x40;};
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* a, jar_xm_envelope_point_t* b, uint16_t pos) { 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 */ /* 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) { 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(s->instrument > 0) {
if(HAS_TONE_PORTAMENTO(ch->current) && ch->instrument != NULL && ch->sample != NULL) { /* Tone portamento in effect */ 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); 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)) { 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; jar_xm_instrument_t* instr = ch->instrument;
if(HAS_TONE_PORTAMENTO(ch->current) && instr != NULL && ch->sample != NULL) { if(HAS_TONE_PORTAMENTO(ch->current) && instr != NULL && ch->sample != NULL) {
/* Tone portamento in effect */ /* Tone portamento in effect */
ch->note = s->note + ch->sample->relative_note + ch->sample->finetune / 128.f - 1.f; 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); 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); jar_xm_cut_note(ch);
} else { } else {
if(instr->sample_of_notes[s->note - 1] < instr->num_samples) { if(instr->sample_of_notes[s->note - 1] < instr->num_samples) {
#if JAR_XM_RAMPING if (mod->ramping) {
for(unsigned int z = 0; z < jar_xm_SAMPLE_RAMPING_POINTS; ++z) { for(int i = 0; i < jar_xm_SAMPLE_RAMPING_POINTS; ++i) {
ch->end_of_previous_sample[z] = jar_xm_next_of_sample(ch); jar_xm_next_of_sample(ctx, ch, i);
} }
ch->frame_count = 0; ch->frame_count = 0;
#endif };
ch->sample = instr->samples + instr->sample_of_notes[s->note - 1]; 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; ch->orig_note = ch->note = s->note + ch->sample->relative_note + ch->sample->finetune / 128.f - 1.f;
if(s->instrument > 0) { 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 */ } else { /* Ghost note: keep old volume */
jar_xm_trigger_note(ctx, ch, jar_xm_TRIGGER_KEEP_VOLUME); jar_xm_trigger_note(ctx, ch, jar_xm_TRIGGER_KEEP_VOLUME);
} }
} else { /* Bad sample (???) */ } else {
jar_xm_cut_note(ch); jar_xm_cut_note(ch);
} }
} }
} else if(s->note == NOTE_OFF) { /* Key Off */ } else if(s->note == NOTE_OFF) {
jar_xm_key_off(ch); 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 // Interpret Effect column
switch(s->effect_type) { switch(s->effect_type) {
case 1: /* 1xx: Portamento up */ 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 */ if(s->effect_param >> 4) { ch->tremolo_param = (s->effect_param & 0xF0) | (ch->tremolo_param & 0x0F); } /* Set tremolo speed */
break; break;
case 8: /* 8xx: Set panning */ case 8: /* 8xx: Set panning */
ch->panning = (float)s->effect_param / (float)0xFF; ch->panning = (float)s->effect_param / 255.f;
break; break;
case 9: /* 9xx: Sample offset */ 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); 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 */ switch (ch->sample->loop_type) {
ch->sample_position = -1; case jar_xm_NO_LOOP:
} else { if(final_offset >= ch->sample->length) { /* Pretend the sample dosen't loop and is done playing */
ch->sample_position = final_offset; 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; 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) { 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->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) { 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_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) { for(uint8_t i = 0; i < ctx->module.num_channels; ++i) {
jar_xm_channel_context_t* ch = ctx->channels + i; jar_xm_channel_context_t* ch = ctx->channels + i;
jar_xm_envelopes(ch); jar_xm_envelopes(ch);
@ -1715,32 +1738,58 @@ static void jar_xm_tick(jar_xm_context_t* ctx) {
jar_xm_update_frequency(ctx, ch); jar_xm_update_frequency(ctx, ch);
} }
if(ctx->current_tick > 0) { // THIS CHECK SHOULD NOT BE NECESSARY *********** // Effects in volumne column mostly handled on a per tick basis
switch(ch->current->volume_column >> 4) { switch(ch->current->volume_column & 0xF0) {
case 0x6: /* Volume slide down */ 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); jar_xm_volume_slide(ch, ch->current->volume_column & 0x0F);
break; break;
case 0x7: /* Volume slide up */ case 0x70: // Volume slide up
jar_xm_volume_slide(ch, ch->current->volume_column << 4); jar_xm_volume_slide(ch, ch->current->volume_column << 4);
break; 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; ch->vibrato_in_progress = false;
jar_xm_vibrato(ctx, ch, ch->vibrato_param, ch->vibrato_ticks++); jar_xm_vibrato(ctx, ch, ch->vibrato_param, ch->vibrato_ticks++);
break; 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); jar_xm_panning_slide(ch, ch->current->volume_column & 0x0F);
break; break;
case 0xE: /* Panning slide right */ case 0xE0: // Panning slide right
jar_xm_panning_slide(ch, ch->current->volume_column << 4); jar_xm_panning_slide(ch, ch->current->volume_column << 4);
break; 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); jar_xm_tone_portamento(ctx, ch);
break; break;
default: default:
break; 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) { switch(ch->current->effect_type) {
case 0: /* 0xy: Arpeggio */ case 0: /* 0xy: Arpeggio */
if(ch->current->effect_param > 0) { 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; if(ctx->current_tick == 0) break;
jar_xm_tremolo(ctx, ch, ch->tremolo_param, ch->tremolo_ticks++); jar_xm_tremolo(ctx, ch, ch->tremolo_param, ch->tremolo_ticks++);
break; break;
case 8: /* 8xy: Set panning */
break;
case 9: /* 9xy: Sample offset */
break;
case 0xA: /* Axy: Volume slide */ case 0xA: /* Axy: Volume slide */
if(ctx->current_tick == 0) break; if(ctx->current_tick == 0) break;
jar_xm_volume_slide(ch, ch->volume_slide_param); 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;
} }
break; break;
case 16: /* Fxy: Set tempo/BPM */
break;
case 17: /* Hxy: Global volume slide */ case 17: /* Hxy: Global volume slide */
if(ctx->current_tick == 0) break; if(ctx->current_tick == 0) break;
if((ch->global_volume_slide_param & 0xF0) && (ch->global_volume_slide_param & 0x0F)) { break; }; /* Invalid state */ 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 */ case 20: /* Kxx: Key off */
if(ctx->current_tick == ch->current->effect_param) { jar_xm_key_off(ch); }; if(ctx->current_tick == ch->current->effect_param) { jar_xm_key_off(ch); };
break; break;
case 21: /* Lxx: Set envelope position */
break;
case 25: /* Pxy: Panning slide */ case 25: /* Pxy: Panning slide */
if(ctx->current_tick == 0) break; if(ctx->current_tick == 0) break;
jar_xm_panning_slide(ch, ch->panning_slide_param); 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; volume *= ch->fadeout_volume * ch->volume_envelope_volume;
}; };
#if JAR_XM_RAMPING if (mod->ramping) {
ch->target_panning = panning; ch->target_panning = panning;
ch->target_volume = volume; ch->target_volume = volume;
#else } else {
ch->actual_panning = panning; ch->actual_panning = panning;
ch->actual_volume = volume; ch->actual_volume = volume;
#endif };
}; };
ctx->current_tick++; // ok so we understand that ticks increment within the row 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; 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); 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(ch->instrument == NULL || ch->sample == NULL || ch->sample_position < 0) {
#if JAR_XM_RAMPING ch->curr_left = 0.f;
if(ch->frame_count < jar_xm_SAMPLE_RAMPING_POINTS) { ch->curr_right = 0.f;
return jar_xm_LERP(ch->end_of_previous_sample[ch->frame_count], .0f, (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); 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;
return .0f;
}; };
if(ch->sample->length == 0) { if(ch->sample->length == 0) {
return .0f; return;
}; };
float u, v, t; float t = 0.f;
uint32_t a, b; uint32_t b = 0;
a = (uint32_t)ch->sample_position; /* This cast is fine, sample_position will not go above integer ranges */ if(mod->linear_interpolation) {
if(JAR_XM_LINEAR_INTERPOLATION) { b = ch->sample_position + 1;
b = a + 1; t = ch->sample_position - (uint32_t)ch->sample_position; /* Cheaper than fmodf(., 1.f) */
t = ch->sample_position - a; /* Cheaper than fmodf(., 1.f) */ };
}
u = ch->sample->data[a];
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) { switch(ch->sample->loop_type) {
case jar_xm_NO_LOOP: case jar_xm_NO_LOOP:
if(JAR_XM_LINEAR_INTERPOLATION) { if(mod->linear_interpolation) {
v = (b < ch->sample->length) ? ch->sample->data[b] : .0f; 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; 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; break;
case jar_xm_FORWARD_LOOP: case jar_xm_FORWARD_LOOP:
if(JAR_XM_LINEAR_INTERPOLATION) { if(mod->linear_interpolation) {
v = ch->sample->data[ (b == ch->sample->loop_end) ? ch->sample->loop_start : b ]; 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; 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; ch->sample_position -= ch->sample->loop_length;
}; };
if(ch->sample_position >= ch->sample->length) {
ch->sample_position = ch->sample->loop_start;
};
break; break;
case jar_xm_PING_PONG_LOOP: case jar_xm_PING_PONG_LOOP:
if(ch->ping) { if(ch->ping) {
ch->sample_position += ch->step; if(mod->linear_interpolation) {
} else { v_left = (b >= ch->sample->loop_end) ? ch->sample->data[(uint32_t)ch->sample_position] : ch->sample->data[b];
ch->sample_position -= ch->step; 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];
/* XXX: this may not work for very tight ping-pong loops (ie switches direction more than once per sample */ } else {
if(ch->ping) { v_right = v_left;
if(JAR_XM_LINEAR_INTERPOLATION) { };
v = (b >= ch->sample->loop_end) ? ch->sample->data[a] : ch->sample->data[b];
}; };
ch->sample_position += ch->step;
if(ch->sample_position >= ch->sample->loop_end) { if(ch->sample_position >= ch->sample->loop_end) {
ch->ping = false; ch->ping = false;
ch->sample_position = (ch->sample->loop_end << 1) - ch->sample_position; ch->sample_position = (ch->sample->loop_end << 1) - ch->sample_position;
}; };
/* sanity checking */
if(ch->sample_position >= ch->sample->length) { if(ch->sample_position >= ch->sample->length) {
ch->ping = false; ch->ping = false;
ch->sample_position -= ch->sample->length - 1; ch->sample_position -= ch->sample->length - 1;
}; };
} else { } else {
if(JAR_XM_LINEAR_INTERPOLATION) { if(mod->linear_interpolation) {
v = u; v_left = u_left;
u = (b == 1 || b - 2 <= ch->sample->loop_start) ? ch->sample->data[a] : ch->sample->data[b - 2]; 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) { if(ch->sample_position <= ch->sample->loop_start) {
ch->ping = true; ch->ping = true;
ch->sample_position = (ch->sample->loop_start << 1) - ch->sample_position; 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->ping = true;
ch->sample_position = .0f; ch->sample_position = .0f;
}; };
@ -1985,62 +2072,87 @@ static float jar_xm_next_of_sample(jar_xm_channel_context_t* ch) {
break; break;
default: default:
v = .0f; v_left = .0f;
v_right = .0f;
break; 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 (mod->ramping) {
if(ch->frame_count < jar_xm_SAMPLE_RAMPING_POINTS) { if(ch->frame_count < jar_xm_SAMPLE_RAMPING_POINTS) {
/* Smoothly transition between old and new sample. */ /* 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); 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);
#endif 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);
return endval; } 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) { if(ctx->remaining_samples_in_tick <= 0) {
jar_xm_tick(ctx); jar_xm_tick(ctx);
} };
ctx->remaining_samples_in_tick--; ctx->remaining_samples_in_tick--;
*left = 0.f; *left = 0.f;
*right = 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) { for(uint8_t i = 0; i < ctx->module.num_channels; ++i) {
jar_xm_channel_context_t* ch = ctx->channels + i; jar_xm_channel_context_t* ch = ctx->channels + i;
if(ch->instrument != NULL && ch->sample != NULL && ch->sample_position >= 0) { 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) { if(!ch->muted && !ch->instrument->muted) {
*left += fval * ch->actual_volume * (1.f - ch->actual_panning); *left += ch->curr_left * ch->actual_volume * (1.f - ch->actual_panning);
*right += fval * ch->actual_volume * ch->actual_panning; *right += ch->curr_right * ch->actual_volume * ch->actual_panning;
} };
#if JAR_XM_RAMPING
ch->frame_count++; if (mod->ramping) {
jar_xm_SLIDE_TOWARDS(ch->actual_volume, ch->target_volume, ctx->volume_ramp); ch->frame_count++;
jar_xm_SLIDE_TOWARDS(ch->actual_panning, ch->target_panning, ctx->panning_ramp); jar_xm_SLIDE_TOWARDS(ch->actual_volume, ch->target_volume, ctx->volume_ramp);
#endif jar_xm_SLIDE_TOWARDS(ch->actual_panning, ch->target_panning, ctx->panning_ramp);
} };
} };
};
if (ctx->global_volume != 1.0f) { if (ctx->global_volume != 1.0f) {
*left *= ctx->global_volume; *left *= ctx->global_volume;
*right *= 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(*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) { void jar_xm_generate_samples(jar_xm_context_t* ctx, float* output, size_t numsamples) {
if(ctx && output) { if(ctx && output) {
ctx->generated_samples += numsamples; ctx->generated_samples += numsamples;
for(size_t i = 0; i < numsamples; i++) { 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 jar_xm_get_remaining_samples(jar_xm_context_t* ctx) {
uint64_t total = 0; 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 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) { 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++) { for (uint16_t i = 0; i < jar_xm_get_number_of_channels(ctx); i++) {
jar_xm_cut_note(&ctx->channels[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); DrawRectangle(x, y, 8 * size, size, DARKGRAY);
}; };
jar_xm_pattern_slot_t *s = cur->slots + j * ctx->module.num_channels + i; 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->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) { if (s->instrument > 0) {
DrawText(TextFormat("%02X", s->instrument), x + size * 2, y, size, WHITE); DrawText(TextFormat("%02X", s->instrument), x + size * 2, y, size, WHITE);