Update jar_xm.h
update to version 0.31
This commit is contained in:
parent
e6b9cfe959
commit
efbe92cf9d
485
src/external/jar_xm.h
vendored
485
src/external/jar_xm.h
vendored
|
|
@ -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->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) {
|
if(sample->bits == 16) {
|
||||||
int16_t v = 0;
|
int16_t v = 0;
|
||||||
for(uint32_t k = 0; k < length; ++k) {
|
for(int k = 0; k < length; ++k) {
|
||||||
|
if (k == result.quot) { v = 0;};
|
||||||
v = v + (int16_t)READ_U16(offset + (k << 1));
|
v = v + (int16_t)READ_U16(offset + (k << 1));
|
||||||
sample->data[k] = (float)v / (float)(1 << 15);
|
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;
|
offset += sample->length << 1;
|
||||||
} else {
|
} else {
|
||||||
int8_t v = 0;
|
int8_t v = 0;
|
||||||
for(uint32_t k = 0; k < length; ++k) {
|
for(int k = 0; k < length; ++k) {
|
||||||
|
if (k == result.quot) { v = 0;};
|
||||||
v = v + (int8_t)READ_U8(offset + k);
|
v = v + (int8_t)READ_U8(offset + k);
|
||||||
sample->data[k] = (float)v / (float)(1 << 7);
|
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 {
|
||||||
|
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;
|
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,16 +1404,40 @@ 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);
|
||||||
|
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 */
|
if(final_offset >= ch->sample->length) { /* Pretend the sample dosen't loop and is done playing */
|
||||||
ch->sample_position = -1;
|
ch->sample_position = -1;
|
||||||
} else {
|
} else {
|
||||||
ch->sample_position = final_offset;
|
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;
|
||||||
case 0xA: /* Axy: Volume slide */
|
case 0xA: /* Axy: Volume slide */
|
||||||
|
|
@ -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;
|
||||||
|
ch->curr_right = 0.f;
|
||||||
|
if (mod->ramping) {
|
||||||
if (ch->frame_count < jar_xm_SAMPLE_RAMPING_POINTS) {
|
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);
|
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) {
|
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) {
|
||||||
|
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 {
|
} else {
|
||||||
ch->sample_position -= ch->step;
|
v_right = v_left;
|
||||||
}
|
|
||||||
/* 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];
|
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
static void jar_xm_sample(jar_xm_context_t* ctx, float* left, float* right) {
|
|
||||||
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
|
|
||||||
|
if (mod->ramping) {
|
||||||
ch->frame_count++;
|
ch->frame_count++;
|
||||||
jar_xm_SLIDE_TOWARDS(ch->actual_volume, ch->target_volume, ctx->volume_ramp);
|
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);
|
jar_xm_SLIDE_TOWARDS(ch->actual_panning, ch->target_panning, ctx->panning_ramp);
|
||||||
#endif
|
};
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
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);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user