From cbe2d9a941f14e64eda48c2e1043da1765378b0e Mon Sep 17 00:00:00 2001 From: Romain Tisserand Date: Fri, 4 Aug 2017 15:05:29 +0200 Subject: [PATCH 1/2] Preliminary MOD/S3M/XM support through ibmx library (part of micromod repository) --- Makefile.common | 5 + Makefile.griffin | 1 + audio/audio_driver.c | 8 + deps/ibxm/README | 21 + deps/ibxm/ibxm.c | 1922 +++++++++++++++++++ deps/ibxm/ibxm.h | 71 + deps/ibxm/licence.txt | 37 + file_path_special.h | 3 + file_path_str.c | 9 + griffin/griffin.c | 4 + libretro-common/audio/audio_mixer.c | 195 ++ libretro-common/include/audio/audio_mixer.h | 4 +- menu/menu_displaylist.c | 5 + msg_hash.c | 11 + msg_hash.h | 4 + paths.c | 7 + qb/config.params.sh | 1 + tasks/task_audio_mixer.c | 30 + tasks/task_file_transfer.c | 1 + tasks/tasks_internal.h | 1 + 20 files changed, 2339 insertions(+), 1 deletion(-) create mode 100644 deps/ibxm/README create mode 100644 deps/ibxm/ibxm.c create mode 100644 deps/ibxm/ibxm.h create mode 100644 deps/ibxm/licence.txt diff --git a/Makefile.common b/Makefile.common index bdeaf1779b..f7f4459735 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1324,6 +1324,11 @@ ifeq ($(HAVE_RBMP), 1) OBJ += $(LIBRETRO_COMM_DIR)/formats/bmp/rbmp.o endif +ifeq ($(HAVE_IBXM), 1) + DEFINES += -DHAVE_IBXM + OBJ += $(DEPS_DIR)/ibxm/ibxm.o +endif + OBJ += $(LIBRETRO_COMM_DIR)/formats/bmp/rbmp_encode.o \ $(LIBRETRO_COMM_DIR)/formats/json/jsonsax.o \ $(LIBRETRO_COMM_DIR)/formats/json/jsonsax_full.o \ diff --git a/Makefile.griffin b/Makefile.griffin index 201ca49a7d..f940471241 100644 --- a/Makefile.griffin +++ b/Makefile.griffin @@ -133,6 +133,7 @@ endif HAVE_RJPEG := 1 HAVE_RBMP := 1 HAVE_RTGA := 1 + HAVE_IBXM := 1 HAVE_OVERLAY := 1 HAVE_ZLIB := 1 WANT_ZLIB := 1 diff --git a/audio/audio_driver.c b/audio/audio_driver.c index 5fcb808491..83e8303110 100644 --- a/audio/audio_driver.c +++ b/audio/audio_driver.c @@ -914,6 +914,11 @@ bool audio_driver_mixer_extension_supported(const char *ext) #ifdef HAVE_STB_VORBIS string_list_append(str_list, "ogg", attr); +#endif +#ifdef HAVE_IBXM + string_list_append(str_list, "mod", attr); + string_list_append(str_list, "s3m", attr); + string_list_append(str_list, "xm", attr); #endif string_list_append(str_list, "wav", attr); @@ -1008,6 +1013,9 @@ bool audio_driver_mixer_add_stream(audio_mixer_stream_params_t *params) case AUDIO_MIXER_TYPE_OGG: handle = audio_mixer_load_ogg(buf, (int32_t)params->bufsize); break; + case AUDIO_MIXER_TYPE_MOD: + handle = audio_mixer_load_mod(buf, (int32_t)params->bufsize); + break; case AUDIO_MIXER_TYPE_NONE: free(buf); return false; diff --git a/deps/ibxm/README b/deps/ibxm/README new file mode 100644 index 0000000000..7acec1a9b5 --- /dev/null +++ b/deps/ibxm/README @@ -0,0 +1,21 @@ + +Micromod (c)2017 mumart@gmail.com + +A good-quality player library for the ProTracker MOD music format +for Javascript (HTML5 Web Audio), Java, ANSI C (SDL) and Pascal (SDL). + +Also hosted here is IBXM, a player library for the ProTracker MOD, +Scream Tracker 3 S3M, and FastTracker 2 XM music formats for Javascript +(HTML5 Web Audio), Java and ANSI C. + +The Java version of Micromod contains a powerful command-line tool for +the creation of MOD files from textual MT files and WAV samples. +There is some basic documentation built-in to the tool and some example +MT files are contained in the songs directory. Some knowledge of the +ProTracker MOD format, audio synthesis and "tracking" is assumed. + +If you have any questions or feedback please feel free to +contact me at the above email address! + +Cheers, +Martin diff --git a/deps/ibxm/ibxm.c b/deps/ibxm/ibxm.c new file mode 100644 index 0000000000..b7c1786e4a --- /dev/null +++ b/deps/ibxm/ibxm.c @@ -0,0 +1,1922 @@ + +#include "stdlib.h" +#include "string.h" + +#include "ibxm.h" + +const char *IBXM_VERSION = "ibxm/ac mod/xm/s3m replay 20170704 (c)mumart@gmail.com"; + +static const int FP_SHIFT = 15, FP_ONE = 32768, FP_MASK = 32767; + +static const int exp2_table[] = { + 32768, 32946, 33125, 33305, 33486, 33667, 33850, 34034, + 34219, 34405, 34591, 34779, 34968, 35158, 35349, 35541, + 35734, 35928, 36123, 36319, 36516, 36715, 36914, 37114, + 37316, 37518, 37722, 37927, 38133, 38340, 38548, 38757, + 38968, 39180, 39392, 39606, 39821, 40037, 40255, 40473, + 40693, 40914, 41136, 41360, 41584, 41810, 42037, 42265, + 42495, 42726, 42958, 43191, 43425, 43661, 43898, 44137, + 44376, 44617, 44859, 45103, 45348, 45594, 45842, 46091, + 46341, 46593, 46846, 47100, 47356, 47613, 47871, 48131, + 48393, 48655, 48920, 49185, 49452, 49721, 49991, 50262, + 50535, 50810, 51085, 51363, 51642, 51922, 52204, 52488, + 52773, 53059, 53347, 53637, 53928, 54221, 54515, 54811, + 55109, 55408, 55709, 56012, 56316, 56622, 56929, 57238, + 57549, 57861, 58176, 58491, 58809, 59128, 59449, 59772, + 60097, 60423, 60751, 61081, 61413, 61746, 62081, 62419, + 62757, 63098, 63441, 63785, 64132, 64480, 64830, 65182, + 65536 +}; + +static const short sine_table[] = { + 0, 24, 49, 74, 97, 120, 141, 161, 180, 197, 212, 224, 235, 244, 250, 253, + 255, 253, 250, 244, 235, 224, 212, 197, 180, 161, 141, 120, 97, 74, 49, 24 +}; + +struct note { + unsigned char key, instrument, volume, effect, param; +}; + +struct channel { + struct replay *replay; + struct instrument *instrument; + struct sample *sample; + struct note note; + int id, key_on, random_seed, pl_row; + int sample_off, sample_idx, sample_fra, freq, ampl, pann; + int volume, panning, fadeout_vol, vol_env_tick, pan_env_tick; + int period, porta_period, retrig_count, fx_count, av_count; + int porta_up_param, porta_down_param, tone_porta_param, offset_param; + int fine_porta_up_param, fine_porta_down_param, xfine_porta_param; + int arpeggio_param, vol_slide_param, gvol_slide_param, pan_slide_param; + int fine_vslide_up_param, fine_vslide_down_param; + int retrig_volume, retrig_ticks, tremor_on_ticks, tremor_off_ticks; + int vibrato_type, vibrato_phase, vibrato_speed, vibrato_depth; + int tremolo_type, tremolo_phase, tremolo_speed, tremolo_depth; + int tremolo_add, vibrato_add, arpeggio_add; +}; + +struct replay { + int sample_rate, interpolation, global_vol; + int seq_pos, break_pos, row, next_row, tick; + int speed, tempo, pl_count, pl_chan; + int *ramp_buf; + char **play_count; + struct channel *channels; + struct module *module; +}; + +static int exp_2( int x ) { + int c, m, y; + int x0 = ( x & FP_MASK ) >> ( FP_SHIFT - 7 ); + c = exp2_table[ x0 ]; + m = exp2_table[ x0 + 1 ] - c; + y = ( m * ( x & ( FP_MASK >> 7 ) ) >> 8 ) + c; + return ( y << FP_SHIFT ) >> ( FP_SHIFT - ( x >> FP_SHIFT ) ); +} + +static int log_2( int x ) { + int step; + int y = 16 << FP_SHIFT; + for( step = y; step > 0; step >>= 1 ) { + if( exp_2( y - step ) >= x ) { + y -= step; + } + } + return y; +} + +static char* data_ascii( struct data *data, int offset, int length, char *dest ) { + int idx, chr; + memset( dest, 32, length ); + if( offset > data->length ) { + offset = data->length; + } + if( offset + length > data->length ) { + length = data->length - offset; + } + for( idx = 0; idx < length; idx++ ) { + chr = data->buffer[ offset + idx ] & 0xFF; + if( chr > 32 ) { + dest[ idx ] = chr; + } + } + return dest; +} + +static int data_s8( struct data *data, int offset ) { + int value = 0; + if( offset < data->length ) { + value = data->buffer[ offset ]; + value = ( value & 0x7F ) - ( value & 0x80 ); + } + return value; +} + +static int data_u8( struct data *data, int offset ) { + int value = 0; + if( offset < data->length ) { + value = data->buffer[ offset ] & 0xFF; + } + return value; +} + +static int data_u16be( struct data *data, int offset ) { + int value = 0; + if( offset + 1 < data->length ) { + value = ( ( data->buffer[ offset ] & 0xFF ) << 8 ) + | ( data->buffer[ offset + 1 ] & 0xFF ); + } + return value; +} + +static int data_u16le( struct data *data, int offset ) { + int value = 0; + if( offset + 1 < data->length ) { + value = ( data->buffer[ offset ] & 0xFF ) + | ( ( data->buffer[ offset + 1 ] & 0xFF ) << 8 ); + } + return value; +} + +static unsigned int data_u32le( struct data *data, int offset ) { + unsigned int value = 0; + if( offset + 3 < data->length ) { + value = ( data->buffer[ offset ] & 0xFF ) + | ( ( data->buffer[ offset + 1 ] & 0xFF ) << 8 ) + | ( ( data->buffer[ offset + 2 ] & 0xFF ) << 16 ) + | ( ( data->buffer[ offset + 3 ] & 0xFF ) << 24 ); + } + return value; +} + +static void data_sam_s8( struct data *data, int offset, int count, short *dest ) { + int idx, amp, length = data->length; + char *buffer = data->buffer; + if( offset > length ) { + offset = length; + } + if( offset + count > length ) { + count = length - offset; + } + for( idx = 0; idx < count; idx++ ) { + amp = ( buffer[ offset + idx ] & 0xFF ) << 8; + dest[ idx ] = ( amp & 0x7FFF ) - ( amp & 0x8000 ); + } +} + +static void data_sam_s16le( struct data *data, int offset, int count, short *dest ) { + int idx, amp, length = data->length; + char *buffer = data->buffer; + if( offset > length ) { + offset = length; + } + if( offset + count * 2 > length ) { + count = ( length - offset ) / 2; + } + for( idx = 0; idx < count; idx++ ) { + amp = ( buffer[ offset + idx * 2 ] & 0xFF ) | ( buffer[ offset + idx * 2 + 1 ] << 8 ); + dest[ idx ] = ( amp & 0x7FFF ) - ( amp & 0x8000 ); + } +} + +static int envelope_next_tick( struct envelope *envelope, int tick, int key_on ) { + tick++; + if( envelope->looped && tick >= envelope->loop_end_tick ) { + tick = envelope->loop_start_tick; + } + if( envelope->sustain && key_on && tick >= envelope->sustain_tick ) { + tick = envelope->sustain_tick; + } + return tick; +} + +static int envelope_calculate_ampl( struct envelope *envelope, int tick ) { + int idx, point, dt, da; + int ampl = envelope->points_ampl[ envelope->num_points - 1 ]; + if( tick < envelope->points_tick[ envelope->num_points - 1 ] ) { + point = 0; + for( idx = 1; idx < envelope->num_points; idx++ ) { + if( envelope->points_tick[ idx ] <= tick ) { + point = idx; + } + } + dt = envelope->points_tick[ point + 1 ] - envelope->points_tick[ point ]; + da = envelope->points_ampl[ point + 1 ] - envelope->points_ampl[ point ]; + ampl = envelope->points_ampl[ point ]; + ampl += ( ( da << 24 ) / dt ) * ( tick - envelope->points_tick[ point ] ) >> 24; + } + return ampl; +} + +static void sample_ping_pong( struct sample *sample ) { + int idx; + int loop_start = sample->loop_start; + int loop_length = sample->loop_length; + int loop_end = loop_start + loop_length; + short *sample_data = sample->data; + short *new_data = calloc( loop_end + loop_length + 1, sizeof( short ) ); + if( new_data ) { + memcpy( new_data, sample_data, loop_end * sizeof( short ) ); + for( idx = 0; idx < loop_length; idx++ ) { + new_data[ loop_end + idx ] = sample_data[ loop_end - idx - 1 ]; + } + free( sample->data ); + sample->data = new_data; + sample->loop_length *= 2; + sample->data[ loop_start + sample->loop_length ] = sample->data[ loop_start ]; + } +} + +/* Deallocate the specified module. */ +void dispose_module( struct module *module ) { + int idx, sam; + struct instrument *instrument; + free( module->default_panning ); + free( module->sequence ); + if( module->patterns ) { + for( idx = 0; idx < module->num_patterns; idx++ ) { + free( module->patterns[ idx ].data ); + } + free( module->patterns ); + } + if( module->instruments ) { + for( idx = 0; idx <= module->num_instruments; idx++ ) { + instrument = &module->instruments[ idx ]; + if( instrument->samples ) { + for( sam = 0; sam < instrument->num_samples; sam++ ) { + free( instrument->samples[ sam ].data ); + } + free( instrument->samples ); + } + } + free( module->instruments ); + } + free( module ); +} + +static struct module* module_load_xm( struct data *data, char *message ) { + int delta_env, offset, next_offset, idx, entry; + int num_rows, num_notes, pat_data_len, pat_data_offset; + int sam, sam_head_offset, sam_data_bytes, sam_data_samples; + int num_samples, sam_loop_start, sam_loop_length, amp; + int note, flags, key, ins, vol, fxc, fxp; + int point, point_tick, point_offset; + int looped, ping_pong, sixteen_bit; + char ascii[ 16 ], *pattern_data; + struct instrument *instrument; + struct sample *sample; + struct module *module = calloc( 1, sizeof( struct module ) ); + if( module ) { + if( data_u16le( data, 58 ) != 0x0104 ) { + strcpy( message, "XM format version must be 0x0104!" ); + dispose_module( module ); + return NULL; + } + data_ascii( data, 17, 20, module->name ); + delta_env = !memcmp( data_ascii( data, 38, 15, ascii ), "DigiBooster Pro", 15 ); + offset = 60 + data_u32le( data, 60 ); + module->sequence_len = data_u16le( data, 64 ); + module->restart_pos = data_u16le( data, 66 ); + module->num_channels = data_u16le( data, 68 ); + module->num_patterns = data_u16le( data, 70 ); + module->num_instruments = data_u16le( data, 72 ); + module->linear_periods = data_u16le( data, 74 ) & 0x1; + module->default_gvol = 64; + module->default_speed = data_u16le( data, 76 ); + module->default_tempo = data_u16le( data, 78 ); + module->c2_rate = 8363; + module->gain = 64; + module->default_panning = calloc( module->num_channels, sizeof( unsigned char ) ); + if( !module->default_panning ) { + dispose_module( module ); + return NULL; + } + for( idx = 0; idx < module->num_channels; idx++ ) { + module->default_panning[ idx ] = 128; + } + module->sequence = calloc( module->sequence_len, sizeof( unsigned char ) ); + if( !module->sequence ) { + dispose_module( module ); + return NULL; + } + for( idx = 0; idx < module->sequence_len; idx++ ) { + entry = data_u8( data, 80 + idx ); + module->sequence[ idx ] = entry < module->num_patterns ? entry : 0; + } + module->patterns = calloc( module->num_patterns, sizeof( struct pattern ) ); + if( !module->patterns ) { + dispose_module( module ); + return NULL; + } + for( idx = 0; idx < module->num_patterns; idx++ ) { + if( data_u8( data, offset + 4 ) ) { + strcpy( message, "Unknown pattern packing type!" ); + dispose_module( module ); + return NULL; + } + num_rows = data_u16le( data, offset + 5 ); + if( num_rows < 1 ) { + num_rows = 1; + } + pat_data_len = data_u16le( data, offset + 7 ); + offset += data_u32le( data, offset ); + next_offset = offset + pat_data_len; + num_notes = num_rows * module->num_channels; + pattern_data = calloc( num_notes, 5 ); + if( !pattern_data ) { + dispose_module( module ); + return NULL; + } + module->patterns[ idx ].num_channels = module->num_channels; + module->patterns[ idx ].num_rows = num_rows; + module->patterns[ idx ].data = pattern_data; + if( pat_data_len > 0 ) { + pat_data_offset = 0; + for( note = 0; note < num_notes; note++ ) { + flags = data_u8( data, offset ); + if( ( flags & 0x80 ) == 0 ) { + flags = 0x1F; + } else { + offset++; + } + key = ( flags & 0x01 ) > 0 ? data_u8( data, offset++ ) : 0; + pattern_data[ pat_data_offset++ ] = key; + ins = ( flags & 0x02 ) > 0 ? data_u8( data, offset++ ) : 0; + pattern_data[ pat_data_offset++ ] = ins; + vol = ( flags & 0x04 ) > 0 ? data_u8( data, offset++ ) : 0; + pattern_data[ pat_data_offset++ ] = vol; + fxc = ( flags & 0x08 ) > 0 ? data_u8( data, offset++ ) : 0; + fxp = ( flags & 0x10 ) > 0 ? data_u8( data, offset++ ) : 0; + if( fxc >= 0x40 ) { + fxc = fxp = 0; + } + pattern_data[ pat_data_offset++ ] = fxc; + pattern_data[ pat_data_offset++ ] = fxp; + } + } + offset = next_offset; + } + module->instruments = calloc( module->num_instruments + 1, sizeof( struct instrument ) ); + if( !module->instruments ) { + dispose_module( module ); + return NULL; + } + instrument = &module->instruments[ 0 ]; + instrument->samples = calloc( 1, sizeof( struct sample ) ); + if( !instrument->samples ) { + dispose_module( module ); + return NULL; + } + for( ins = 1; ins <= module->num_instruments; ins++ ) { + instrument = &module->instruments[ ins ]; + data_ascii( data, offset + 4, 22, instrument->name ); + num_samples = data_u16le( data, offset + 27 ); + instrument->num_samples = ( num_samples > 0 ) ? num_samples : 1; + instrument->samples = calloc( instrument->num_samples, sizeof( struct sample ) ); + if( !instrument->samples ) { + dispose_module( module ); + return NULL; + } + if( num_samples > 0 ) { + for( key = 0; key < 96; key++ ) { + instrument->key_to_sample[ key + 1 ] = data_u8( data, offset + 33 + key ); + } + point_tick = 0; + for( point = 0; point < 12; point++ ) { + point_offset = offset + 129 + ( point * 4 ); + point_tick = ( delta_env ? point_tick : 0 ) + data_u16le( data, point_offset ); + instrument->vol_env.points_tick[ point ] = point_tick; + instrument->vol_env.points_ampl[ point ] = data_u16le( data, point_offset + 2 ); + } + point_tick = 0; + for( point = 0; point < 12; point++ ) { + point_offset = offset + 177 + ( point * 4 ); + point_tick = ( delta_env ? point_tick : 0 ) + data_u16le( data, point_offset ); + instrument->pan_env.points_tick[ point ] = point_tick; + instrument->pan_env.points_ampl[ point ] = data_u16le( data, point_offset + 2 ); + } + instrument->vol_env.num_points = data_u8( data, offset + 225 ); + if( instrument->vol_env.num_points > 12 ) { + instrument->vol_env.num_points = 0; + } + instrument->pan_env.num_points = data_u8( data, offset + 226 ); + if( instrument->pan_env.num_points > 12 ) { + instrument->pan_env.num_points = 0; + } + instrument->vol_env.sustain_tick = instrument->vol_env.points_tick[ data_u8( data, offset + 227 ) & 0xF ]; + instrument->vol_env.loop_start_tick = instrument->vol_env.points_tick[ data_u8( data, offset + 228 ) & 0xF ]; + instrument->vol_env.loop_end_tick = instrument->vol_env.points_tick[ data_u8( data, offset + 229 ) & 0xF ]; + instrument->pan_env.sustain_tick = instrument->pan_env.points_tick[ data_u8( data, offset + 230 ) & 0xF ]; + instrument->pan_env.loop_start_tick = instrument->pan_env.points_tick[ data_u8( data, offset + 231 ) & 0xF ]; + instrument->pan_env.loop_end_tick = instrument->pan_env.points_tick[ data_u8( data, offset + 232 ) & 0xF ]; + instrument->vol_env.enabled = instrument->vol_env.num_points > 0 && ( data_u8( data, offset + 233 ) & 0x1 ); + instrument->vol_env.sustain = ( data_u8( data, offset + 233 ) & 0x2 ) > 0; + instrument->vol_env.looped = ( data_u8( data, offset + 233 ) & 0x4 ) > 0; + instrument->pan_env.enabled = instrument->pan_env.num_points > 0 && ( data_u8( data, offset + 234 ) & 0x1 ); + instrument->pan_env.sustain = ( data_u8( data, offset + 234 ) & 0x2 ) > 0; + instrument->pan_env.looped = ( data_u8( data, offset + 234 ) & 0x4 ) > 0; + instrument->vib_type = data_u8( data, offset + 235 ); + instrument->vib_sweep = data_u8( data, offset + 236 ); + instrument->vib_depth = data_u8( data, offset + 237 ); + instrument->vib_rate = data_u8( data, offset + 238 ); + instrument->vol_fadeout = data_u16le( data, offset + 239 ); + } + offset += data_u32le( data, offset ); + sam_head_offset = offset; + offset += num_samples * 40; + for( sam = 0; sam < num_samples; sam++ ) { + sample = &instrument->samples[ sam ]; + sam_data_bytes = data_u32le( data, sam_head_offset ); + sam_loop_start = data_u32le( data, sam_head_offset + 4 ); + sam_loop_length = data_u32le( data, sam_head_offset + 8 ); + sample->volume = data_u8( data, sam_head_offset + 12 ); + sample->fine_tune = data_s8( data, sam_head_offset + 13 ); + looped = ( data_u8( data, sam_head_offset + 14 ) & 0x3 ) > 0; + ping_pong = ( data_u8( data, sam_head_offset + 14 ) & 0x2 ) > 0; + sixteen_bit = ( data_u8( data, sam_head_offset + 14 ) & 0x10 ) > 0; + sample->panning = data_u8( data, sam_head_offset + 15 ) + 1; + sample->rel_note = data_s8( data, sam_head_offset + 16 ); + data_ascii( data, sam_head_offset + 18, 22, sample->name ); + sam_head_offset += 40; + sam_data_samples = sam_data_bytes; + if( sixteen_bit ) { + sam_data_samples = sam_data_samples >> 1; + sam_loop_start = sam_loop_start >> 1; + sam_loop_length = sam_loop_length >> 1; + } + if( !looped || ( sam_loop_start + sam_loop_length ) > sam_data_samples ) { + sam_loop_start = sam_data_samples; + sam_loop_length = 0; + } + sample->loop_start = sam_loop_start; + sample->loop_length = sam_loop_length; + sample->data = calloc( sam_data_samples + 1, sizeof( short ) ); + if( sample->data ) { + if( sixteen_bit ) { + data_sam_s16le( data, offset, sam_data_samples, sample->data ); + } else { + data_sam_s8( data, offset, sam_data_samples, sample->data ); + } + amp = 0; + for( idx = 0; idx < sam_data_samples; idx++ ) { + amp = amp + sample->data[ idx ]; + amp = ( amp & 0x7FFF ) - ( amp & 0x8000 ); + sample->data[ idx ] = amp; + } + sample->data[ sam_loop_start + sam_loop_length ] = sample->data[ sam_loop_start ]; + if( ping_pong ) { + sample_ping_pong( sample ); + } + } else { + dispose_module( module ); + return NULL; + } + offset += sam_data_bytes; + } + } + } + return module; +} + +static struct module* module_load_s3m( struct data *data, char *message ) { + int idx, module_data_idx, inst_offset, flags; + int version, sixteen_bit, tune, signed_samples; + int stereo_mode, default_pan, channel_map[ 32 ]; + int sample_offset, sample_length, loop_start, loop_length; + int pat_offset, note_offset, row, chan, token; + int key, ins, volume, effect, param, panning; + char *pattern_data; + struct instrument *instrument; + struct sample *sample; + struct module *module = calloc( 1, sizeof( struct module ) ); + if( module ) { + data_ascii( data, 0, 28, module->name ); + module->sequence_len = data_u16le( data, 32 ); + module->num_instruments = data_u16le( data, 34 ); + module->num_patterns = data_u16le( data, 36 ); + flags = data_u16le( data, 38 ); + version = data_u16le( data, 40 ); + module->fast_vol_slides = ( ( flags & 0x40 ) == 0x40 ) || version == 0x1300; + signed_samples = data_u16le( data, 42 ) == 1; + if( data_u32le( data, 44 ) != 0x4d524353 ) { + strcpy( message, "Not an S3M file!" ); + dispose_module( module ); + return NULL; + } + module->default_gvol = data_u8( data, 48 ); + module->default_speed = data_u8( data, 49 ); + module->default_tempo = data_u8( data, 50 ); + module->c2_rate = 8363; + module->gain = data_u8( data, 51 ) & 0x7F; + stereo_mode = ( data_u8( data, 51 ) & 0x80 ) == 0x80; + default_pan = data_u8( data, 53 ) == 0xFC; + for( idx = 0; idx < 32; idx++ ) { + channel_map[ idx ] = -1; + if( data_u8( data, 64 + idx ) < 16 ) { + channel_map[ idx ] = module->num_channels++; + } + } + module->sequence = calloc( module->sequence_len, sizeof( unsigned char ) ); + if( !module->sequence ){ + dispose_module( module ); + return NULL; + } + for( idx = 0; idx < module->sequence_len; idx++ ) { + module->sequence[ idx ] = data_u8( data, 96 + idx ); + } + module_data_idx = 96 + module->sequence_len; + module->instruments = calloc( module->num_instruments + 1, sizeof( struct instrument ) ); + if( !module->instruments ) { + dispose_module( module ); + return NULL; + } + instrument = &module->instruments[ 0 ]; + instrument->num_samples = 1; + instrument->samples = calloc( 1, sizeof( struct sample ) ); + if( !instrument->samples ) { + dispose_module( module ); + return NULL; + } + for( ins = 1; ins <= module->num_instruments; ins++ ) { + instrument = &module->instruments[ ins ]; + instrument->num_samples = 1; + instrument->samples = calloc( 1, sizeof( struct sample ) ); + if( !instrument->samples ) { + dispose_module( module ); + return NULL; + } + sample = &instrument->samples[ 0 ]; + inst_offset = data_u16le( data, module_data_idx ) << 4; + module_data_idx += 2; + data_ascii( data, inst_offset + 48, 28, instrument->name ); + if( data_u8( data, inst_offset ) == 1 && data_u16le( data, inst_offset + 76 ) == 0x4353 ) { + sample_offset = ( data_u8( data, inst_offset + 13 ) << 20 ) + + ( data_u16le( data, inst_offset + 14 ) << 4 ); + sample_length = data_u32le( data, inst_offset + 16 ); + loop_start = data_u32le( data, inst_offset + 20 ); + loop_length = data_u32le( data, inst_offset + 24 ) - loop_start; + sample->volume = data_u8( data, inst_offset + 28 ); + if( data_u8( data, inst_offset + 30 ) != 0 ) { + strcpy( message, "Packed samples not supported!" ); + dispose_module( module ); + return NULL; + } + if( loop_start + loop_length > sample_length ) { + loop_length = sample_length - loop_start; + } + if( loop_length < 1 || !( data_u8( data, inst_offset + 31 ) & 0x1 ) ) { + loop_start = sample_length; + loop_length = 0; + } + sample->loop_start = loop_start; + sample->loop_length = loop_length; + /* stereo = data_u8( data, inst_offset + 31 ) & 0x2; */ + sixteen_bit = data_u8( data, inst_offset + 31 ) & 0x4; + tune = ( log_2( data_u32le( data, inst_offset + 32 ) ) - log_2( module->c2_rate ) ) * 12; + sample->rel_note = tune >> FP_SHIFT; + sample->fine_tune = ( tune & FP_MASK ) >> ( FP_SHIFT - 7 ); + sample->data = calloc( sample_length + 1, sizeof( short ) ); + if( sample->data ) { + if( sixteen_bit ) { + data_sam_s16le( data, sample_offset, sample_length, sample->data ); + } else { + data_sam_s8( data, sample_offset, sample_length, sample->data ); + } + if( !signed_samples ) { + for( idx = 0; idx < sample_length; idx++ ) { + sample->data[ idx ] = ( sample->data[ idx ] & 0xFFFF ) - 32768; + } + } + sample->data[ loop_start + loop_length ] = sample->data[ loop_start ]; + } else { + dispose_module( module ); + return NULL; + } + } + } + module->patterns = calloc( module->num_patterns, sizeof( struct pattern ) ); + if( !module->patterns ) { + dispose_module( module ); + return NULL; + } + for( idx = 0; idx < module->num_patterns; idx++ ) { + module->patterns[ idx ].num_channels = module->num_channels; + module->patterns[ idx ].num_rows = 64; + pattern_data = calloc( module->num_channels * 64, 5 ); + if( !pattern_data ) { + dispose_module( module ); + return NULL; + } + module->patterns[ idx ].data = pattern_data; + pat_offset = ( data_u16le( data, module_data_idx ) << 4 ) + 2; + row = 0; + while( row < 64 ) { + token = data_u8( data, pat_offset++ ); + if( token ) { + key = ins = 0; + if( ( token & 0x20 ) == 0x20 ) { + /* Key + Instrument.*/ + key = data_u8( data, pat_offset++ ); + ins = data_u8( data, pat_offset++ ); + if( key < 0xFE ) { + key = ( key >> 4 ) * 12 + ( key & 0xF ) + 1; + } else if( key == 0xFF ) { + key = 0; + } + } + volume = 0; + if( ( token & 0x40 ) == 0x40 ) { + /* Volume Column.*/ + volume = ( data_u8( data, pat_offset++ ) & 0x7F ) + 0x10; + if( volume > 0x50 ) { + volume = 0; + } + } + effect = param = 0; + if( ( token & 0x80 ) == 0x80 ) { + /* Effect + Param.*/ + effect = data_u8( data, pat_offset++ ); + param = data_u8( data, pat_offset++ ); + if( effect < 1 || effect >= 0x40 ) { + effect = param = 0; + } else if( effect > 0 ) { + effect += 0x80; + } + } + chan = channel_map[ token & 0x1F ]; + if( chan >= 0 ) { + note_offset = ( row * module->num_channels + chan ) * 5; + pattern_data[ note_offset ] = key; + pattern_data[ note_offset + 1 ] = ins; + pattern_data[ note_offset + 2 ] = volume; + pattern_data[ note_offset + 3 ] = effect; + pattern_data[ note_offset + 4 ] = param; + } + } else { + row++; + } + } + module_data_idx += 2; + } + module->default_panning = calloc( module->num_channels, sizeof( unsigned char ) ); + if( module->default_panning ) { + for( chan = 0; chan < 32; chan++ ) { + if( channel_map[ chan ] >= 0 ) { + panning = 7; + if( stereo_mode ) { + panning = 12; + if( data_u8( data, 64 + chan ) < 8 ) { + panning = 3; + } + } + if( default_pan ) { + flags = data_u8( data, module_data_idx + chan ); + if( ( flags & 0x20 ) == 0x20 ) { + panning = flags & 0xF; + } + } + module->default_panning[ channel_map[ chan ] ] = panning * 17; + } + } + } else { + dispose_module( module ); + return NULL; + } + } + return module; +} + +static struct module* module_load_mod( struct data *data, char *message ) { + int idx, pat, module_data_idx, pat_data_len, pat_data_idx; + int period, key, ins, effect, param, fine_tune; + int sample_length, loop_start, loop_length; + char *pattern_data; + struct instrument *instrument; + struct sample *sample; + struct module *module = calloc( 1, sizeof( struct module ) ); + if( module ) { + data_ascii( data, 0, 20, module->name ); + module->sequence_len = data_u8( data, 950 ) & 0x7F; + module->restart_pos = data_u8( data, 951 ) & 0x7F; + if( module->restart_pos >= module->sequence_len ) { + module->restart_pos = 0; + } + module->sequence = calloc( 128, sizeof( unsigned char ) ); + if( !module->sequence ){ + dispose_module( module ); + return NULL; + } + for( idx = 0; idx < 128; idx++ ) { + pat = data_u8( data, 952 + idx ) & 0x7F; + module->sequence[ idx ] = pat; + if( pat >= module->num_patterns ) { + module->num_patterns = pat + 1; + } + } + switch( data_u16be( data, 1082 ) ) { + case 0x4b2e: /* M.K. */ + case 0x4b21: /* M!K! */ + case 0x5434: /* FLT4 */ + module->num_channels = 4; + module->c2_rate = 8287; + module->gain = 64; + break; + case 0x484e: /* xCHN */ + module->num_channels = data_u8( data, 1080 ) - 48; + module->c2_rate = 8363; + module->gain = 32; + break; + case 0x4348: /* xxCH */ + module->num_channels = ( data_u8( data, 1080 ) - 48 ) * 10; + module->num_channels += data_u8( data, 1081 ) - 48; + module->c2_rate = 8363; + module->gain = 32; + break; + default: + strcpy( message, "MOD Format not recognised!" ); + dispose_module( module ); + return NULL; + } + module->default_gvol = 64; + module->default_speed = 6; + module->default_tempo = 125; + module->default_panning = calloc( module->num_channels, sizeof( unsigned char ) ); + if( !module->default_panning ) { + dispose_module( module ); + return NULL; + } + for( idx = 0; idx < module->num_channels; idx++ ) { + module->default_panning[ idx ] = 51; + if( ( idx & 3 ) == 1 || ( idx & 3 ) == 2 ) { + module->default_panning[ idx ] = 204; + } + } + module_data_idx = 1084; + module->patterns = calloc( module->num_patterns, sizeof( struct pattern ) ); + if( !module->patterns ) { + dispose_module( module ); + return NULL; + } + pat_data_len = module->num_channels * 64 * 5; + for( pat = 0; pat < module->num_patterns; pat++ ) { + module->patterns[ pat ].num_channels = module->num_channels; + module->patterns[ pat ].num_rows = 64; + pattern_data = calloc( 1, pat_data_len ); + if( !pattern_data ) { + dispose_module( module ); + return NULL; + } + module->patterns[ pat ].data = pattern_data; + for( pat_data_idx = 0; pat_data_idx < pat_data_len; pat_data_idx += 5 ) { + period = ( data_u8( data, module_data_idx ) & 0xF ) << 8; + period = ( period | data_u8( data, module_data_idx + 1 ) ) * 4; + if( period >= 112 && period <= 6848 ) { + key = -12 * log_2( ( period << FP_SHIFT ) / 29021 ); + key = ( key + ( key & ( FP_ONE >> 1 ) ) ) >> FP_SHIFT; + pattern_data[ pat_data_idx ] = key; + } + ins = ( data_u8( data, module_data_idx + 2 ) & 0xF0 ) >> 4; + ins = ins | ( data_u8( data, module_data_idx ) & 0x10 ); + pattern_data[ pat_data_idx + 1 ] = ins; + effect = data_u8( data, module_data_idx + 2 ) & 0x0F; + param = data_u8( data, module_data_idx + 3 ); + if( param == 0 && ( effect < 3 || effect == 0xA ) ) { + effect = 0; + } + if( param == 0 && ( effect == 5 || effect == 6 ) ) { + effect -= 2; + } + if( effect == 8 && module->num_channels == 4 ) { + effect = param = 0; + } + pattern_data[ pat_data_idx + 3 ] = effect; + pattern_data[ pat_data_idx + 4 ] = param; + module_data_idx += 4; + } + } + module->num_instruments = 31; + module->instruments = calloc( module->num_instruments + 1, sizeof( struct instrument ) ); + if( !module->instruments ) { + dispose_module( module ); + return NULL; + } + instrument = &module->instruments[ 0 ]; + instrument->num_samples = 1; + instrument->samples = calloc( 1, sizeof( struct sample ) ); + if( !instrument->samples ) { + dispose_module( module ); + return NULL; + } + for( ins = 1; ins <= module->num_instruments; ins++ ) { + instrument = &module->instruments[ ins ]; + instrument->num_samples = 1; + instrument->samples = calloc( 1, sizeof( struct sample ) ); + if( !instrument->samples ) { + dispose_module( module ); + return NULL; + } + sample = &instrument->samples[ 0 ]; + data_ascii( data, ins * 30 - 10, 22, instrument->name ); + sample_length = data_u16be( data, ins * 30 + 12 ) * 2; + fine_tune = ( data_u8( data, ins * 30 + 14 ) & 0xF ) << 4; + sample->fine_tune = ( fine_tune & 0x7F ) - ( fine_tune & 0x80 ); + sample->volume = data_u8( data, ins * 30 + 15 ) & 0x7F; + if( sample->volume > 64 ) { + sample->volume = 64; + } + loop_start = data_u16be( data, ins * 30 + 16 ) * 2; + loop_length = data_u16be( data, ins * 30 + 18 ) * 2; + if( loop_start + loop_length > sample_length ) { + loop_length = sample_length - loop_start; + } + if( loop_length < 4 ) { + loop_start = sample_length; + loop_length = 0; + } + sample->loop_start = loop_start; + sample->loop_length = loop_length; + sample->data = calloc( sample_length + 1, sizeof( short ) ); + if( sample->data ) { + data_sam_s8( data, module_data_idx, sample_length, sample->data ); + sample->data[ loop_start + loop_length ] = sample->data[ loop_start ]; + } else { + dispose_module( module ); + return NULL; + } + module_data_idx += sample_length; + } + } + return module; +} + +/* Allocate and initialize a module from the specified data, returns NULL on error. + Message should point to a 64-character buffer to receive error messages. */ +struct module* module_load( struct data *data, char *message ) { + char ascii[ 16 ]; + struct module* module; + if( !memcmp( data_ascii( data, 0, 16, ascii ), "Extended Module:", 16 ) ) { + module = module_load_xm( data, message ); + } else if( !memcmp( data_ascii( data, 44, 4, ascii ), "SCRM", 4 ) ) { + module = module_load_s3m( data, message ); + } else { + module = module_load_mod( data, message ); + } + return module; +} + +static void pattern_get_note( struct pattern *pattern, int row, int chan, struct note *dest ) { + int offset = ( row * pattern->num_channels + chan ) * 5; + if( offset >= 0 && row < pattern->num_rows && chan < pattern->num_channels ) { + dest->key = pattern->data[ offset ]; + dest->instrument = pattern->data[ offset + 1 ]; + dest->volume = pattern->data[ offset + 2 ]; + dest->effect = pattern->data[ offset + 3 ]; + dest->param = pattern->data[ offset + 4 ]; + } else { + memset( dest, 0, sizeof( struct note ) ); + } +} + +static void channel_init( struct channel *channel, struct replay *replay, int idx ) { + memset( channel, 0, sizeof( struct channel ) ); + channel->replay = replay; + channel->id = idx; + channel->panning = replay->module->default_panning[ idx ]; + channel->instrument = &replay->module->instruments[ 0 ]; + channel->sample = &channel->instrument->samples[ 0 ]; + channel->random_seed = ( idx + 1 ) * 0xABCDEF; +} + +static void channel_volume_slide( struct channel *channel ) { + int up = channel->vol_slide_param >> 4; + int down = channel->vol_slide_param & 0xF; + if( down == 0xF && up > 0 ) { + /* Fine slide up.*/ + if( channel->fx_count == 0 ) { + channel->volume += up; + } + } else if( up == 0xF && down > 0 ) { + /* Fine slide down.*/ + if( channel->fx_count == 0 ) { + channel->volume -= down; + } + } else if( channel->fx_count > 0 || channel->replay->module->fast_vol_slides ) { + /* Normal.*/ + channel->volume += up - down; + } + if( channel->volume > 64 ) { + channel->volume = 64; + } + if( channel->volume < 0 ) { + channel->volume = 0; + } +} + +static void channel_porta_up( struct channel *channel, int param ) { + switch( param & 0xF0 ) { + case 0xE0: /* Extra-fine porta.*/ + if( channel->fx_count == 0 ) { + channel->period -= param & 0xF; + } + break; + case 0xF0: /* Fine porta.*/ + if( channel->fx_count == 0 ) { + channel->period -= ( param & 0xF ) << 2; + } + break; + default:/* Normal porta.*/ + if( channel->fx_count > 0 ) { + channel->period -= param << 2; + } + break; + } + if( channel->period < 0 ) { + channel->period = 0; + } +} + +static void channel_porta_down( struct channel *channel, int param ) { + if( channel->period > 0 ) { + switch( param & 0xF0 ) { + case 0xE0: /* Extra-fine porta.*/ + if( channel->fx_count == 0 ) { + channel->period += param & 0xF; + } + break; + case 0xF0: /* Fine porta.*/ + if( channel->fx_count == 0 ) { + channel->period += ( param & 0xF ) << 2; + } + break; + default:/* Normal porta.*/ + if( channel->fx_count > 0 ) { + channel->period += param << 2; + } + break; + } + if( channel->period > 65535 ) { + channel->period = 65535; + } + } +} + +static void channel_tone_porta( struct channel *channel ) { + if( channel->period > 0 ) { + if( channel->period < channel->porta_period ) { + channel->period += channel->tone_porta_param << 2; + if( channel->period > channel->porta_period ) { + channel->period = channel->porta_period; + } + } else { + channel->period -= channel->tone_porta_param << 2; + if( channel->period < channel->porta_period ) { + channel->period = channel->porta_period; + } + } + } +} + +static int channel_waveform( struct channel *channel, int phase, int type ) { + int amplitude = 0; + switch( type ) { + default: /* Sine. */ + amplitude = sine_table[ phase & 0x1F ]; + if( ( phase & 0x20 ) > 0 ) { + amplitude = -amplitude; + } + break; + case 6: /* Saw Up.*/ + amplitude = ( ( ( phase + 0x20 ) & 0x3F ) << 3 ) - 255; + break; + case 1: case 7: /* Saw Down. */ + amplitude = 255 - ( ( ( phase + 0x20 ) & 0x3F ) << 3 ); + break; + case 2: case 5: /* Square. */ + amplitude = ( phase & 0x20 ) > 0 ? 255 : -255; + break; + case 3: case 8: /* Random. */ + amplitude = ( channel->random_seed >> 20 ) - 255; + channel->random_seed = ( channel->random_seed * 65 + 17 ) & 0x1FFFFFFF; + break; + } + return amplitude; +} + +static void channel_vibrato( struct channel *channel, int fine ) { + int wave = channel_waveform( channel, channel->vibrato_phase, channel->vibrato_type & 0x3 ); + channel->vibrato_add = wave * channel->vibrato_depth >> ( fine ? 7 : 5 ); +} + +static void channel_tremolo( struct channel *channel ) { + int wave = channel_waveform( channel, channel->tremolo_phase, channel->tremolo_type & 0x3 ); + channel->tremolo_add = wave * channel->tremolo_depth >> 6; +} + +static void channel_tremor( struct channel *channel ) { + if( channel->retrig_count >= channel->tremor_on_ticks ) { + channel->tremolo_add = -64; + } + if( channel->retrig_count >= ( channel->tremor_on_ticks + channel->tremor_off_ticks ) ) { + channel->tremolo_add = channel->retrig_count = 0; + } +} + +static void channel_retrig_vol_slide( struct channel *channel ) { + if( channel->retrig_count >= channel->retrig_ticks ) { + channel->retrig_count = channel->sample_idx = channel->sample_fra = 0; + switch( channel->retrig_volume ) { + case 0x1: channel->volume = channel->volume - 1; break; + case 0x2: channel->volume = channel->volume - 2; break; + case 0x3: channel->volume = channel->volume - 4; break; + case 0x4: channel->volume = channel->volume - 8; break; + case 0x5: channel->volume = channel->volume - 16; break; + case 0x6: channel->volume = channel->volume * 2 / 3; break; + case 0x7: channel->volume = channel->volume >> 1; break; + case 0x8: /* ? */ break; + case 0x9: channel->volume = channel->volume + 1; break; + case 0xA: channel->volume = channel->volume + 2; break; + case 0xB: channel->volume = channel->volume + 4; break; + case 0xC: channel->volume = channel->volume + 8; break; + case 0xD: channel->volume = channel->volume + 16; break; + case 0xE: channel->volume = channel->volume * 3 / 2; break; + case 0xF: channel->volume = channel->volume << 1; break; + } + if( channel->volume < 0 ) { + channel->volume = 0; + } + if( channel->volume > 64 ) { + channel->volume = 64; + } + } +} + +static void channel_trigger( struct channel *channel ) { + int key, sam, porta, period, fine_tune, ins = channel->note.instrument; + struct sample *sample; + if( ins > 0 && ins <= channel->replay->module->num_instruments ) { + channel->instrument = &channel->replay->module->instruments[ ins ]; + key = channel->note.key < 97 ? channel->note.key : 0; + sam = channel->instrument->key_to_sample[ key ]; + sample = &channel->instrument->samples[ sam ]; + channel->volume = sample->volume >= 64 ? 64 : sample->volume & 0x3F; + if( sample->panning > 0 ) { + channel->panning = ( sample->panning - 1 ) & 0xFF; + } + if( channel->period > 0 && sample->loop_length > 1 ) { + /* Amiga trigger.*/ + channel->sample = sample; + } + channel->sample_off = 0; + channel->vol_env_tick = channel->pan_env_tick = 0; + channel->fadeout_vol = 32768; + channel->key_on = 1; + } + if( channel->note.effect == 0x09 || channel->note.effect == 0x8F ) { + /* Set Sample Offset. */ + if( channel->note.param > 0 ) { + channel->offset_param = channel->note.param; + } + channel->sample_off = channel->offset_param << 8; + } + if( channel->note.volume >= 0x10 && channel->note.volume < 0x60 ) { + channel->volume = channel->note.volume < 0x50 ? channel->note.volume - 0x10 : 64; + } + switch( channel->note.volume & 0xF0 ) { + case 0x80: /* Fine Vol Down.*/ + channel->volume -= channel->note.volume & 0xF; + if( channel->volume < 0 ) { + channel->volume = 0; + } + break; + case 0x90: /* Fine Vol Up.*/ + channel->volume += channel->note.volume & 0xF; + if( channel->volume > 64 ) { + channel->volume = 64; + } + break; + case 0xA0: /* Set Vibrato Speed.*/ + if( ( channel->note.volume & 0xF ) > 0 ) { + channel->vibrato_speed = channel->note.volume & 0xF; + } + break; + case 0xB0: /* Vibrato.*/ + if( ( channel->note.volume & 0xF ) > 0 ) { + channel->vibrato_depth = channel->note.volume & 0xF; + } + channel_vibrato( channel, 0 ); + break; + case 0xC0: /* Set Panning.*/ + channel->panning = ( channel->note.volume & 0xF ) * 17; + break; + case 0xF0: /* Tone Porta.*/ + if( ( channel->note.volume & 0xF ) > 0 ) { + channel->tone_porta_param = channel->note.volume & 0xF; + } + break; + } + if( channel->note.key > 0 ) { + if( channel->note.key > 96 ) { + channel->key_on = 0; + } else { + porta = ( channel->note.volume & 0xF0 ) == 0xF0 || + channel->note.effect == 0x03 || channel->note.effect == 0x05 || + channel->note.effect == 0x87 || channel->note.effect == 0x8C; + if( !porta ) { + ins = channel->instrument->key_to_sample[ channel->note.key ]; + channel->sample = &channel->instrument->samples[ ins ]; + } + fine_tune = channel->sample->fine_tune; + if( channel->note.effect == 0x75 || channel->note.effect == 0xF2 ) { + /* Set Fine Tune. */ + fine_tune = ( ( channel->note.param & 0xF ) << 4 ) - 128; + } + key = channel->note.key + channel->sample->rel_note; + if( key < 1 ) { + key = 1; + } + if( key > 120 ) { + key = 120; + } + period = ( key << 6 ) + ( fine_tune >> 1 ); + if( channel->replay->module->linear_periods ) { + channel->porta_period = 7744 - period; + } else { + channel->porta_period = 29021 * exp_2( ( period << FP_SHIFT ) / -768 ) >> FP_SHIFT; + } + if( !porta ) { + channel->period = channel->porta_period; + channel->sample_idx = channel->sample_off; + channel->sample_fra = 0; + if( channel->vibrato_type < 4 ) { + channel->vibrato_phase = 0; + } + if( channel->tremolo_type < 4 ) { + channel->tremolo_phase = 0; + } + channel->retrig_count = channel->av_count = 0; + } + } + } +} + +static void channel_update_envelopes( struct channel *channel ) { + if( channel->instrument->vol_env.enabled ) { + if( !channel->key_on ) { + channel->fadeout_vol -= channel->instrument->vol_fadeout; + if( channel->fadeout_vol < 0 ) { + channel->fadeout_vol = 0; + } + } + channel->vol_env_tick = envelope_next_tick( &channel->instrument->vol_env, + channel->vol_env_tick, channel->key_on ); + } + if( channel->instrument->pan_env.enabled ) { + channel->pan_env_tick = envelope_next_tick( &channel->instrument->pan_env, + channel->pan_env_tick, channel->key_on ); + } +} + +static void channel_auto_vibrato( struct channel *channel ) { + int sweep, rate, type, wave; + int depth = channel->instrument->vib_depth & 0x7F; + if( depth > 0 ) { + sweep = channel->instrument->vib_sweep & 0x7F; + rate = channel->instrument->vib_rate & 0x7F; + type = channel->instrument->vib_type; + if( channel->av_count < sweep ) { + depth = depth * channel->av_count / sweep; + } + wave = channel_waveform( channel, channel->av_count * rate >> 2, type + 4 ); + channel->vibrato_add += wave * depth >> 8; + channel->av_count++; + } +} + +static void channel_calculate_freq( struct channel *channel ) { + int per = channel->period + channel->vibrato_add; + if( channel->replay->module->linear_periods ) { + per = per - ( channel->arpeggio_add << 6 ); + if( per < 28 || per > 7680 ) { + per = 7680; + } + channel->freq = ( ( channel->replay->module->c2_rate >> 4 ) + * exp_2( ( ( 4608 - per ) << FP_SHIFT ) / 768 ) ) >> ( FP_SHIFT - 4 ); + } else { + if( per > 29021 ) { + per = 29021; + } + per = ( per << FP_SHIFT ) / exp_2( ( channel->arpeggio_add << FP_SHIFT ) / 12 ); + if( per < 28 ) { + per = 29021; + } + channel->freq = channel->replay->module->c2_rate * 1712 / per; + } +} + +static void channel_calculate_ampl( struct channel *channel ) { + int vol, range, env_pan = 32, env_vol = channel->key_on ? 64 : 0; + if( channel->instrument->vol_env.enabled ) { + env_vol = envelope_calculate_ampl( &channel->instrument->vol_env, channel->vol_env_tick ); + } + vol = channel->volume + channel->tremolo_add; + if( vol > 64 ) { + vol = 64; + } + if( vol < 0 ) { + vol = 0; + } + vol = ( vol * channel->replay->module->gain * FP_ONE ) >> 13; + vol = ( vol * channel->fadeout_vol ) >> 15; + channel->ampl = ( vol * channel->replay->global_vol * env_vol ) >> 12; + if( channel->instrument->pan_env.enabled ) { + env_pan = envelope_calculate_ampl( &channel->instrument->pan_env, channel->pan_env_tick ); + } + range = ( channel->panning < 128 ) ? channel->panning : ( 255 - channel->panning ); + channel->pann = channel->panning + ( range * ( env_pan - 32 ) >> 5 ); +} + +static void channel_tick( struct channel *channel ) { + channel->vibrato_add = 0; + channel->fx_count++; + channel->retrig_count++; + if( !( channel->note.effect == 0x7D && channel->fx_count <= channel->note.param ) ) { + switch( channel->note.volume & 0xF0 ) { + case 0x60: /* Vol Slide Down.*/ + channel->volume -= channel->note.volume & 0xF; + if( channel->volume < 0 ) { + channel->volume = 0; + } + break; + case 0x70: /* Vol Slide Up.*/ + channel->volume += channel->note.volume & 0xF; + if( channel->volume > 64 ) { + channel->volume = 64; + } + break; + case 0xB0: /* Vibrato.*/ + channel->vibrato_phase += channel->vibrato_speed; + channel_vibrato( channel, 0 ); + break; + case 0xD0: /* Pan Slide Left.*/ + channel->panning -= channel->note.volume & 0xF; + if( channel->panning < 0 ) { + channel->panning = 0; + } + break; + case 0xE0: /* Pan Slide Right.*/ + channel->panning += channel->note.volume & 0xF; + if( channel->panning > 255 ) { + channel->panning = 255; + } + break; + case 0xF0: /* Tone Porta.*/ + channel_tone_porta( channel ); + break; + } + } + switch( channel->note.effect ) { + case 0x01: case 0x86: /* Porta Up. */ + channel_porta_up( channel, channel->porta_up_param ); + break; + case 0x02: case 0x85: /* Porta Down. */ + channel_porta_down( channel, channel->porta_down_param ); + break; + case 0x03: case 0x87: /* Tone Porta. */ + channel_tone_porta( channel ); + break; + case 0x04: case 0x88: /* Vibrato. */ + channel->vibrato_phase += channel->vibrato_speed; + channel_vibrato( channel, 0 ); + break; + case 0x05: case 0x8C: /* Tone Porta + Vol Slide. */ + channel_tone_porta( channel ); + channel_volume_slide( channel ); + break; + case 0x06: case 0x8B: /* Vibrato + Vol Slide. */ + channel->vibrato_phase += channel->vibrato_speed; + channel_vibrato( channel, 0 ); + channel_volume_slide( channel ); + break; + case 0x07: case 0x92: /* Tremolo. */ + channel->tremolo_phase += channel->tremolo_speed; + channel_tremolo( channel ); + break; + case 0x0A: case 0x84: /* Vol Slide. */ + channel_volume_slide( channel ); + break; + case 0x11: /* Global Volume Slide. */ + channel->replay->global_vol = channel->replay->global_vol + + ( channel->gvol_slide_param >> 4 ) + - ( channel->gvol_slide_param & 0xF ); + if( channel->replay->global_vol < 0 ) { + channel->replay->global_vol = 0; + } + if( channel->replay->global_vol > 64 ) { + channel->replay->global_vol = 64; + } + break; + case 0x19: /* Panning Slide. */ + channel->panning = channel->panning + + ( channel->pan_slide_param >> 4 ) + - ( channel->pan_slide_param & 0xF ); + if( channel->panning < 0 ) { + channel->panning = 0; + } + if( channel->panning > 255 ) { + channel->panning = 255; + } + break; + case 0x1B: case 0x91: /* Retrig + Vol Slide. */ + channel_retrig_vol_slide( channel ); + break; + case 0x1D: case 0x89: /* Tremor. */ + channel_tremor( channel ); + break; + case 0x79: /* Retrig. */ + if( channel->fx_count >= channel->note.param ) { + channel->fx_count = 0; + channel->sample_idx = channel->sample_fra = 0; + } + break; + case 0x7C: case 0xFC: /* Note Cut. */ + if( channel->note.param == channel->fx_count ) { + channel->volume = 0; + } + break; + case 0x7D: case 0xFD: /* Note Delay. */ + if( channel->note.param == channel->fx_count ) { + channel_trigger( channel ); + } + break; + case 0x8A: /* Arpeggio. */ + if( channel->fx_count == 1 ) { + channel->arpeggio_add = channel->arpeggio_param >> 4; + } else if( channel->fx_count == 2 ) { + channel->arpeggio_add = channel->arpeggio_param & 0xF; + } else { + channel->arpeggio_add = channel->fx_count = 0; + } + break; + case 0x95: /* Fine Vibrato. */ + channel->vibrato_phase += channel->vibrato_speed; + channel_vibrato( channel, 1 ); + break; + } + channel_auto_vibrato( channel ); + channel_calculate_freq( channel ); + channel_calculate_ampl( channel ); + channel_update_envelopes( channel ); +} + +static void channel_row( struct channel *channel, struct note *note ) { + channel->note = *note; + channel->retrig_count++; + channel->vibrato_add = channel->tremolo_add = channel->arpeggio_add = channel->fx_count = 0; + if( !( ( note->effect == 0x7D || note->effect == 0xFD ) && note->param > 0 ) ) { + /* Not note delay.*/ + channel_trigger( channel ); + } + switch( channel->note.effect ) { + case 0x01: case 0x86: /* Porta Up. */ + if( channel->note.param > 0 ) { + channel->porta_up_param = channel->note.param; + } + channel_porta_up( channel, channel->porta_up_param ); + break; + case 0x02: case 0x85: /* Porta Down. */ + if( channel->note.param > 0 ) { + channel->porta_down_param = channel->note.param; + } + channel_porta_down( channel, channel->porta_down_param ); + break; + case 0x03: case 0x87: /* Tone Porta. */ + if( channel->note.param > 0 ) { + channel->tone_porta_param = channel->note.param; + } + break; + case 0x04: case 0x88: /* Vibrato. */ + if( ( channel->note.param >> 4 ) > 0 ) { + channel->vibrato_speed = channel->note.param >> 4; + } + if( ( channel->note.param & 0xF ) > 0 ) { + channel->vibrato_depth = channel->note.param & 0xF; + } + channel_vibrato( channel, 0 ); + break; + case 0x05: case 0x8C: /* Tone Porta + Vol Slide. */ + if( channel->note.param > 0 ) { + channel->vol_slide_param = channel->note.param; + } + channel_volume_slide( channel ); + break; + case 0x06: case 0x8B: /* Vibrato + Vol Slide. */ + if( channel->note.param > 0 ) { + channel->vol_slide_param = channel->note.param; + } + channel_vibrato( channel, 0 ); + channel_volume_slide( channel ); + break; + case 0x07: case 0x92: /* Tremolo. */ + if( ( channel->note.param >> 4 ) > 0 ) { + channel->tremolo_speed = channel->note.param >> 4; + } + if( ( channel->note.param & 0xF ) > 0 ) { + channel->tremolo_depth = channel->note.param & 0xF; + } + channel_tremolo( channel ); + break; + case 0x08: /* Set Panning.*/ + channel->panning = ( channel->note.param < 128 ) ? ( channel->note.param << 1 ) : 255; + break; + case 0x0A: case 0x84: /* Vol Slide. */ + if( channel->note.param > 0 ) { + channel->vol_slide_param = channel->note.param; + } + channel_volume_slide( channel ); + break; + case 0x0C: /* Set Volume. */ + channel->volume = channel->note.param >= 64 ? 64 : channel->note.param & 0x3F; + break; + case 0x10: case 0x96: /* Set Global Volume. */ + channel->replay->global_vol = channel->note.param >= 64 ? 64 : channel->note.param & 0x3F; + break; + case 0x11: /* Global Volume Slide. */ + if( channel->note.param > 0 ) { + channel->gvol_slide_param = channel->note.param; + } + break; + case 0x14: /* Key Off. */ + channel->key_on = 0; + break; + case 0x15: /* Set Envelope Tick. */ + channel->vol_env_tick = channel->pan_env_tick = channel->note.param & 0xFF; + break; + case 0x19: /* Panning Slide. */ + if( channel->note.param > 0 ) { + channel->pan_slide_param = channel->note.param; + } + break; + case 0x1B: case 0x91: /* Retrig + Vol Slide. */ + if( ( channel->note.param >> 4 ) > 0 ) { + channel->retrig_volume = channel->note.param >> 4; + } + if( ( channel->note.param & 0xF ) > 0 ) { + channel->retrig_ticks = channel->note.param & 0xF; + } + channel_retrig_vol_slide( channel ); + break; + case 0x1D: case 0x89: /* Tremor. */ + if( ( channel->note.param >> 4 ) > 0 ) { + channel->tremor_on_ticks = channel->note.param >> 4; + } + if( ( channel->note.param & 0xF ) > 0 ) { + channel->tremor_off_ticks = channel->note.param & 0xF; + } + channel_tremor( channel ); + break; + case 0x21: /* Extra Fine Porta. */ + if( channel->note.param > 0 ) { + channel->xfine_porta_param = channel->note.param; + } + switch( channel->xfine_porta_param & 0xF0 ) { + case 0x10: + channel_porta_up( channel, 0xE0 | ( channel->xfine_porta_param & 0xF ) ); + break; + case 0x20: + channel_porta_down( channel, 0xE0 | ( channel->xfine_porta_param & 0xF ) ); + break; + } + break; + case 0x71: /* Fine Porta Up. */ + if( channel->note.param > 0 ) { + channel->fine_porta_up_param = channel->note.param; + } + channel_porta_up( channel, 0xF0 | ( channel->fine_porta_up_param & 0xF ) ); + break; + case 0x72: /* Fine Porta Down. */ + if( channel->note.param > 0 ) { + channel->fine_porta_down_param = channel->note.param; + } + channel_porta_down( channel, 0xF0 | ( channel->fine_porta_down_param & 0xF ) ); + break; + case 0x74: case 0xF3: /* Set Vibrato Waveform. */ + if( channel->note.param < 8 ) { + channel->vibrato_type = channel->note.param; + } + break; + case 0x77: case 0xF4: /* Set Tremolo Waveform. */ + if( channel->note.param < 8 ) { + channel->tremolo_type = channel->note.param; + } + break; + case 0x7A: /* Fine Vol Slide Up. */ + if( channel->note.param > 0 ) { + channel->fine_vslide_up_param = channel->note.param; + } + channel->volume += channel->fine_vslide_up_param; + if( channel->volume > 64 ) { + channel->volume = 64; + } + break; + case 0x7B: /* Fine Vol Slide Down. */ + if( channel->note.param > 0 ) { + channel->fine_vslide_down_param = channel->note.param; + } + channel->volume -= channel->fine_vslide_down_param; + if( channel->volume < 0 ) { + channel->volume = 0; + } + break; + case 0x7C: case 0xFC: /* Note Cut. */ + if( channel->note.param <= 0 ) { + channel->volume = 0; + } + break; + case 0x8A: /* Arpeggio. */ + if( channel->note.param > 0 ) { + channel->arpeggio_param = channel->note.param; + } + break; + case 0x95: /* Fine Vibrato.*/ + if( ( channel->note.param >> 4 ) > 0 ) { + channel->vibrato_speed = channel->note.param >> 4; + } + if( ( channel->note.param & 0xF ) > 0 ) { + channel->vibrato_depth = channel->note.param & 0xF; + } + channel_vibrato( channel, 1 ); + break; + case 0xF8: /* Set Panning. */ + channel->panning = channel->note.param * 17; + break; + } + channel_auto_vibrato( channel ); + channel_calculate_freq( channel ); + channel_calculate_ampl( channel ); + channel_update_envelopes( channel ); +} + +static void channel_resample( struct channel *channel, int *mix_buf, + int offset, int count, int sample_rate, int interpolate ) { + struct sample *sample = channel->sample; + int l_gain, r_gain, sam_idx, sam_fra, step; + int loop_len, loop_end, out_idx, out_end, y, m, c; + short *sample_data = channel->sample->data; + if( channel->ampl > 0 ) { + l_gain = channel->ampl * ( 255 - channel->pann ) >> 8; + r_gain = channel->ampl * channel->pann >> 8; + sam_idx = channel->sample_idx; + sam_fra = channel->sample_fra; + step = ( channel->freq << ( FP_SHIFT - 3 ) ) / ( sample_rate >> 3 ); + loop_len = sample->loop_length; + loop_end = sample->loop_start + loop_len; + out_idx = offset * 2; + out_end = ( offset + count ) * 2; + if( interpolate ) { + while( out_idx < out_end ) { + if( sam_idx >= loop_end ) { + if( loop_len > 1 ) { + while( sam_idx >= loop_end ) { + sam_idx -= loop_len; + } + } else { + break; + } + } + c = sample_data[ sam_idx ]; + m = sample_data[ sam_idx + 1 ] - c; + y = ( ( m * sam_fra ) >> FP_SHIFT ) + c; + mix_buf[ out_idx++ ] += ( y * l_gain ) >> FP_SHIFT; + mix_buf[ out_idx++ ] += ( y * r_gain ) >> FP_SHIFT; + sam_fra += step; + sam_idx += sam_fra >> FP_SHIFT; + sam_fra &= FP_MASK; + } + } else { + while( out_idx < out_end ) { + if( sam_idx >= loop_end ) { + if( loop_len > 1 ) { + while( sam_idx >= loop_end ) { + sam_idx -= loop_len; + } + } else { + break; + } + } + y = sample_data[ sam_idx ]; + mix_buf[ out_idx++ ] += ( y * l_gain ) >> FP_SHIFT; + mix_buf[ out_idx++ ] += ( y * r_gain ) >> FP_SHIFT; + sam_fra += step; + sam_idx += sam_fra >> FP_SHIFT; + sam_fra &= FP_MASK; + } + } + } +} + +static void channel_update_sample_idx( struct channel *channel, int count, int sample_rate ) { + struct sample *sample = channel->sample; + int step = ( channel->freq << ( FP_SHIFT - 3 ) ) / ( sample_rate >> 3 ); + channel->sample_fra += step * count; + channel->sample_idx += channel->sample_fra >> FP_SHIFT; + if( channel->sample_idx > sample->loop_start ) { + if( sample->loop_length > 1 ) { + channel->sample_idx = sample->loop_start + + ( channel->sample_idx - sample->loop_start ) % sample->loop_length; + } else { + channel->sample_idx = sample->loop_start; + } + } + channel->sample_fra &= FP_MASK; +} + +static void replay_row( struct replay *replay ) { + int idx, count; + struct note note; + struct pattern *pattern; + struct channel *channel; + struct module *module = replay->module; + if( replay->next_row < 0 ) { + replay->break_pos = replay->seq_pos + 1; + replay->next_row = 0; + } + if( replay->break_pos >= 0 ) { + if( replay->break_pos >= module->sequence_len ) { + replay->break_pos = replay->next_row = 0; + } + while( module->sequence[ replay->break_pos ] >= module->num_patterns ) { + replay->break_pos++; + if( replay->break_pos >= module->sequence_len ) { + replay->break_pos = replay->next_row = 0; + } + } + replay->seq_pos = replay->break_pos; + for( idx = 0; idx < module->num_channels; idx++ ) { + replay->channels[ idx ].pl_row = 0; + } + replay->break_pos = -1; + } + pattern = &module->patterns[ module->sequence[ replay->seq_pos ] ]; + replay->row = replay->next_row; + if( replay->row >= pattern->num_rows ) { + replay->row = 0; + } + if( replay->play_count && replay->play_count[ 0 ] ) { + count = replay->play_count[ replay->seq_pos ][ replay->row ]; + if( replay->pl_count < 0 && count < 127 ) { + replay->play_count[ replay->seq_pos ][ replay->row ] = count + 1; + } + } + replay->next_row = replay->row + 1; + if( replay->next_row >= pattern->num_rows ) { + replay->next_row = -1; + } + for( idx = 0; idx < module->num_channels; idx++ ) { + channel = &replay->channels[ idx ]; + pattern_get_note( pattern, replay->row, idx, ¬e ); + if( note.effect == 0xE ) { + note.effect = 0x70 | ( note.param >> 4 ); + note.param &= 0xF; + } + if( note.effect == 0x93 ) { + note.effect = 0xF0 | ( note.param >> 4 ); + note.param &= 0xF; + } + if( note.effect == 0 && note.param > 0 ) { + note.effect = 0x8A; + } + channel_row( channel, ¬e ); + switch( note.effect ) { + case 0x81: /* Set Speed. */ + if( note.param > 0 ) { + replay->tick = replay->speed = note.param; + } + break; + case 0xB: case 0x82: /* Pattern Jump.*/ + if( replay->pl_count < 0 ) { + replay->break_pos = note.param; + replay->next_row = 0; + } + break; + case 0xD: case 0x83: /* Pattern Break.*/ + if( replay->pl_count < 0 ) { + if( replay->break_pos < 0 ) { + replay->break_pos = replay->seq_pos + 1; + } + replay->next_row = ( note.param >> 4 ) * 10 + ( note.param & 0xF ); + } + break; + case 0xF: /* Set Speed/Tempo.*/ + if( note.param > 0 ) { + if( note.param < 32 ) { + replay->tick = replay->speed = note.param; + } else { + replay->tempo = note.param; + } + } + break; + case 0x94: /* Set Tempo.*/ + if( note.param > 32 ) { + replay->tempo = note.param; + } + break; + case 0x76: case 0xFB : /* Pattern Loop.*/ + if( note.param == 0 ) { + /* Set loop marker on this channel. */ + channel->pl_row = replay->row; + } + if( channel->pl_row < replay->row && replay->break_pos < 0 ) { + /* Marker valid. */ + if( replay->pl_count < 0 ) { + /* Not already looping, begin. */ + replay->pl_count = note.param; + replay->pl_chan = idx; + } + if( replay->pl_chan == idx ) { + /* Next Loop.*/ + if( replay->pl_count == 0 ) { + /* Loop finished. Invalidate current marker. */ + channel->pl_row = replay->row + 1; + } else { + /* Loop. */ + replay->next_row = channel->pl_row; + } + replay->pl_count--; + } + } + break; + case 0x7E: case 0xFE: /* Pattern Delay.*/ + replay->tick = replay->speed + replay->speed * note.param; + break; + } + } +} + +static int replay_tick( struct replay *replay ) { + int idx, num_channels, count = 1; + if( --replay->tick <= 0 ) { + replay->tick = replay->speed; + replay_row( replay ); + } else { + num_channels = replay->module->num_channels; + for( idx = 0; idx < num_channels; idx++ ) { + channel_tick( &replay->channels[ idx ] ); + } + } + if( replay->play_count && replay->play_count[ 0 ] ) { + count = replay->play_count[ replay->seq_pos ][ replay->row ] - 1; + } + return count; +} + +static int module_init_play_count( struct module *module, char **play_count ) { + int idx, pat, rows, len = 0; + for( idx = 0; idx < module->sequence_len; idx++ ) { + pat = module->sequence[ idx ]; + rows = ( pat < module->num_patterns ) ? module->patterns[ pat ].num_rows : 0; + if( play_count ) { + play_count[ idx ] = play_count[ 0 ] ? &play_count[ 0 ][ len ] : NULL; + } + len += rows; + } + return len; +} + +/* Set the pattern in the sequence to play. The tempo is reset to the default. */ +void replay_set_sequence_pos( struct replay *replay, int pos ) { + int idx; + struct module *module = replay->module; + if( pos >= module->sequence_len ) { + pos = 0; + } + replay->break_pos = pos; + replay->next_row = 0; + replay->tick = 1; + replay->global_vol = module->default_gvol; + replay->speed = module->default_speed > 0 ? module->default_speed : 6; + replay->tempo = module->default_tempo > 0 ? module->default_tempo : 125; + replay->pl_count = replay->pl_chan = -1; + if( replay->play_count ) { + free( replay->play_count[ 0 ] ); + free( replay->play_count ); + } + replay->play_count = calloc( module->sequence_len, sizeof( char * ) ); + if( replay->play_count ) { + replay->play_count[ 0 ] = calloc( module_init_play_count( module, NULL ), sizeof( char ) ); + module_init_play_count( module, replay->play_count ); + } + for( idx = 0; idx < module->num_channels; idx++ ) { + channel_init( &replay->channels[ idx ], replay, idx ); + } + memset( replay->ramp_buf, 0, 128 * sizeof( int ) ); + replay_tick( replay ); +} + +/* Deallocate the specified replay. */ +void dispose_replay( struct replay *replay ) { + if( replay->play_count ) { + free( replay->play_count[ 0 ] ); + free( replay->play_count ); + } + free( replay->ramp_buf ); + free( replay->channels ); + free( replay ); +} + +/* Allocate and initialize a replay with the specified sampling rate and interpolation. */ +struct replay* new_replay( struct module *module, int sample_rate, int interpolation ) { + struct replay *replay = calloc( 1, sizeof( struct replay ) ); + if( replay ) { + replay->module = module; + replay->sample_rate = sample_rate; + replay->interpolation = interpolation; + replay->ramp_buf = calloc( 128, sizeof( int ) ); + replay->channels = calloc( module->num_channels, sizeof( struct channel ) ); + if( replay->ramp_buf && replay->channels ) { + replay_set_sequence_pos( replay, 0 ); + } else { + dispose_replay( replay ); + replay = NULL; + } + } + return replay; +} + +static int calculate_tick_len( int tempo, int sample_rate ) { + return ( sample_rate * 5 ) / ( tempo * 2 ); +} + +/* Returns the length of the output buffer required by replay_get_audio(). */ +int calculate_mix_buf_len( int sample_rate ) { + return ( calculate_tick_len( 32, sample_rate ) + 65 ) * 4; +} + +/* Returns the song duration in samples at the current sampling rate. */ +int replay_calculate_duration( struct replay *replay ) { + int count = 0, duration = 0; + replay_set_sequence_pos( replay, 0 ); + while( count < 1 ) { + duration += calculate_tick_len( replay->tempo, replay->sample_rate ); + count = replay_tick( replay ); + } + replay_set_sequence_pos( replay, 0 ); + return duration; +} + +/* Seek to approximately the specified sample position. + The actual sample position reached is returned. */ +int replay_seek( struct replay *replay, int sample_pos ) { + int idx, tick_len, current_pos = 0; + replay_set_sequence_pos( replay, 0 ); + tick_len = calculate_tick_len( replay->tempo, replay->sample_rate ); + while( ( sample_pos - current_pos ) >= tick_len ) { + for( idx = 0; idx < replay->module->num_channels; idx++ ) { + channel_update_sample_idx( &replay->channels[ idx ], + tick_len * 2, replay->sample_rate * 2 ); + } + current_pos += tick_len; + replay_tick( replay ); + tick_len = calculate_tick_len( replay->tempo, replay->sample_rate ); + } + return current_pos; +} + +static void replay_volume_ramp( struct replay *replay, int *mix_buf, int tick_len ) { + int idx, a1, a2, ramp_rate = 256 * 2048 / replay->sample_rate; + for( idx = 0, a1 = 0; a1 < 256; idx += 2, a1 += ramp_rate ) { + a2 = 256 - a1; + mix_buf[ idx ] = ( mix_buf[ idx ] * a1 + replay->ramp_buf[ idx ] * a2 ) >> 8; + mix_buf[ idx + 1 ] = ( mix_buf[ idx + 1 ] * a1 + replay->ramp_buf[ idx + 1 ] * a2 ) >> 8; + } + memcpy( replay->ramp_buf, &mix_buf[ tick_len * 2 ], 128 * sizeof( int ) ); +} + +/* 2:1 downsampling with simple but effective anti-aliasing. Buf must contain count * 2 + 1 stereo samples. */ +static void downsample( int *buf, int count ) { + int idx, out_idx, out_len = count * 2; + for( idx = 0, out_idx = 0; out_idx < out_len; idx += 4, out_idx += 2 ) { + buf[ out_idx ] = ( buf[ idx ] >> 2 ) + ( buf[ idx + 2 ] >> 1 ) + ( buf[ idx + 4 ] >> 2 ); + buf[ out_idx + 1 ] = ( buf[ idx + 1 ] >> 2 ) + ( buf[ idx + 3 ] >> 1 ) + ( buf[ idx + 5 ] >> 2 ); + } +} + +/* Generates audio and returns the number of stereo samples written into mix_buf. */ +int replay_get_audio( struct replay *replay, int *mix_buf ) { + struct channel *channel; + int idx, num_channels, tick_len = calculate_tick_len( replay->tempo, replay->sample_rate ); + /* Clear output buffer. */ + memset( mix_buf, 0, ( tick_len + 65 ) * 4 * sizeof( int ) ); + /* Resample. */ + num_channels = replay->module->num_channels; + for( idx = 0; idx < num_channels; idx++ ) { + channel = &replay->channels[ idx ]; + channel_resample( channel, mix_buf, 0, ( tick_len + 65 ) * 2, + replay->sample_rate * 2, replay->interpolation ); + channel_update_sample_idx( channel, tick_len * 2, replay->sample_rate * 2 ); + } + downsample( mix_buf, tick_len + 64 ); + replay_volume_ramp( replay, mix_buf, tick_len ); + replay_tick( replay ); + return tick_len; +} diff --git a/deps/ibxm/ibxm.h b/deps/ibxm/ibxm.h new file mode 100644 index 0000000000..980403b39f --- /dev/null +++ b/deps/ibxm/ibxm.h @@ -0,0 +1,71 @@ + +/* ibxm/ac mod/xm/s3m replay (c)mumart@gmail.com */ + +#ifndef __IBXM_H__ +#define __IBXM_H__ + +const char *IBXM_VERSION; + +struct data { + char *buffer; + int length; +}; + +struct sample { + char name[ 32 ]; + int loop_start, loop_length; + short volume, panning, rel_note, fine_tune, *data; +}; + +struct envelope { + char enabled, sustain, looped, num_points; + short sustain_tick, loop_start_tick, loop_end_tick; + short points_tick[ 16 ], points_ampl[ 16 ]; +}; + +struct instrument { + int num_samples, vol_fadeout; + char name[ 32 ], key_to_sample[ 97 ]; + char vib_type, vib_sweep, vib_depth, vib_rate; + struct envelope vol_env, pan_env; + struct sample *samples; +}; + +struct pattern { + int num_channels, num_rows; + char *data; +}; + +struct module { + char name[ 32 ]; + int num_channels, num_instruments; + int num_patterns, sequence_len, restart_pos; + int default_gvol, default_speed, default_tempo, c2_rate, gain; + int linear_periods, fast_vol_slides; + unsigned char *default_panning, *sequence; + struct pattern *patterns; + struct instrument *instruments; +}; + +/* Allocate and initialize a module from the specified data, returns NULL on error. + Message should point to a 64-character buffer to receive error messages. */ +struct module* module_load( struct data *data, char *message ); +/* Deallocate the specified module. */ +void dispose_module( struct module *module ); +/* Allocate and initialize a replay with the specified module and sampling rate. */ +struct replay* new_replay( struct module *module, int sample_rate, int interpolation ); +/* Deallocate the specified replay. */ +void dispose_replay( struct replay *replay ); +/* Returns the song duration in samples at the current sampling rate. */ +int replay_calculate_duration( struct replay *replay ); +/* Seek to approximately the specified sample position. + The actual sample position reached is returned. */ +int replay_seek( struct replay *replay, int sample_pos ); +/* Set the pattern in the sequence to play. The tempo is reset to the default. */ +void replay_set_sequence_pos( struct replay *replay, int pos ); +/* Generates audio and returns the number of stereo samples written into mix_buf. */ +int replay_get_audio( struct replay *replay, int *mix_buf ); +/* Returns the length of the output buffer required by replay_get_audio(). */ +int calculate_mix_buf_len( int sample_rate ); + +#endif diff --git a/deps/ibxm/licence.txt b/deps/ibxm/licence.txt new file mode 100644 index 0000000000..1001af7c4d --- /dev/null +++ b/deps/ibxm/licence.txt @@ -0,0 +1,37 @@ +--- +Copyright (c) 2015, Martin Cameron +All rights reserved. + +Redistribution and use in source and binary forms, with or +without modification, are permitted provided that the +following conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + + * Redistributions in binary form must reproduce the + above copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + + * Neither the name of the organization nor the names of + its contributors may be used to endorse or promote + products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +--- diff --git a/file_path_special.h b/file_path_special.h index ef72101d49..8e0576da6f 100644 --- a/file_path_special.h +++ b/file_path_special.h @@ -82,6 +82,9 @@ enum file_path_enum FILE_PATH_7Z_EXTENSION, FILE_PATH_OGG_EXTENSION, FILE_PATH_WAV_EXTENSION, + FILE_PATH_MOD_EXTENSION, + FILE_PATH_S3M_EXTENSION, + FILE_PATH_XM_EXTENSION, FILE_PATH_CONFIG_EXTENSION, FILE_PATH_CORE_INFO_EXTENSION }; diff --git a/file_path_str.c b/file_path_str.c index c66af6d97a..4f6ae0bc30 100644 --- a/file_path_str.c +++ b/file_path_str.c @@ -107,6 +107,15 @@ const char *file_path_str(enum file_path_enum enum_idx) case FILE_PATH_WAV_EXTENSION: str = ".wav"; break; + case FILE_PATH_MOD_EXTENSION: + str = ".mod"; + break; + case FILE_PATH_S3M_EXTENSION: + str = ".s3m"; + break; + case FILE_PATH_XM_EXTENSION: + str = ".xm"; + break; case FILE_PATH_JPEG_EXTENSION: str = ".jpeg"; break; diff --git a/griffin/griffin.c b/griffin/griffin.c index bba86c377c..77570f40f9 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -14,6 +14,8 @@ * If not, see . */ +#define HAVE_IBXM 1 + #if defined(HAVE_CG) || defined(HAVE_HLSL) || defined(HAVE_GLSL) #define HAVE_SHADERS #endif @@ -350,6 +352,8 @@ VIDEO DRIVER #include "../gfx/drivers/gdi_gfx.c" #endif +#include "../deps/ibxm/ibxm.c" + /*============================================================ FONTS ============================================================ */ diff --git a/libretro-common/audio/audio_mixer.c b/libretro-common/audio/audio_mixer.c index 8afea64b78..29ab4cd441 100644 --- a/libretro-common/audio/audio_mixer.c +++ b/libretro-common/audio/audio_mixer.c @@ -46,6 +46,10 @@ #include "../../deps/stb/stb_vorbis.h" #endif +#ifdef HAVE_IBXM +#include "../../deps/ibxm/ibxm.h" +#endif + #define AUDIO_MIXER_MAX_VOICES 8 #define AUDIO_MIXER_TEMP_OGG_BUFFER 8192 @@ -70,6 +74,15 @@ struct audio_mixer_sound const void* data; } ogg; #endif + +#ifdef HAVE_IBXM + struct + { + /* mod/s3m/xm */ + unsigned size; + const void* data; + } mod; +#endif } types; }; @@ -103,6 +116,18 @@ struct audio_mixer_voice const retro_resampler_t *resampler; } ogg; #endif + +#ifdef HAVE_IBXM + struct + { + /* mod/s3m/xm */ + unsigned position; + unsigned samples; + unsigned buf_samples; + int* buffer; + struct replay* stream; + } mod; +#endif } types; }; @@ -319,6 +344,24 @@ audio_mixer_sound_t* audio_mixer_load_ogg(void *buffer, int32_t size) #endif } +audio_mixer_sound_t* audio_mixer_load_mod(void *buffer, int32_t size) +{ +#ifdef HAVE_IBXM + audio_mixer_sound_t* sound = (audio_mixer_sound_t*)calloc(1, sizeof(*sound)); + + if (!sound) + return NULL; + + sound->type = AUDIO_MIXER_TYPE_MOD; + sound->types.mod.size = size; + sound->types.mod.data = buffer; + + return sound; +#else + return NULL; +#endif +} + void audio_mixer_destroy(audio_mixer_sound_t* sound) { void *handle = NULL; @@ -337,6 +380,13 @@ void audio_mixer_destroy(audio_mixer_sound_t* sound) handle = (void*)sound->types.ogg.data; if (handle) free(handle); +#endif + break; + case AUDIO_MIXER_TYPE_MOD: +#ifdef HAVE_IBXM + handle = (void*)sound->types.mod.data; + if (handle) + free(handle); #endif break; case AUDIO_MIXER_TYPE_NONE: @@ -413,6 +463,65 @@ error: } #endif +#ifdef HAVE_IBXM +static bool audio_mixer_play_mod( + audio_mixer_sound_t* sound, + audio_mixer_voice_t* voice, + bool repeat, float volume, + audio_mixer_stop_cb_t stop_cb) +{ + int buf_samples = 0; + int samples = 0; + void *mod_buffer = NULL; + struct module* module = NULL; + struct replay* replay = NULL; + struct data data; + char message[64]; + + data.buffer = (char*)sound->types.ogg.data; + data.length = sound->types.ogg.size; + module = module_load(&data, message); + if (module==NULL) { + printf("audio_mixer_play_mod module_load() failed with error: %s\n", message); + goto error; + } + + replay = new_replay( module, s_rate, 1); + if (replay==NULL) { + printf("audio_mixer_play_mod new_replay() failed\n"); + goto error; + } + + buf_samples = calculate_mix_buf_len(s_rate); + mod_buffer = memalign_alloc(16, ((buf_samples + 15) & ~15) * sizeof(int)); + if (!mod_buffer) { + printf("audio_mixer_play_mod cannot allocate mod_buffer !\n"); + goto error; + } + + samples = replay_calculate_duration(replay); + if (!samples) { + printf("audio_mixer_play_mod cannot retrieve duration !\n"); + goto error; + } + + voice->types.mod.buffer = mod_buffer; + voice->types.mod.buf_samples = samples; + voice->types.mod.stream = replay; + voice->types.mod.position = 0; + voice->types.mod.samples = samples; + + return true; + +error: + if (mod_buffer) + memalign_free(mod_buffer); + dispose_module(module); + return false; + +} +#endif + audio_mixer_voice_t* audio_mixer_play(audio_mixer_sound_t* sound, bool repeat, float volume, audio_mixer_stop_cb_t stop_cb) { @@ -440,6 +549,11 @@ audio_mixer_voice_t* audio_mixer_play(audio_mixer_sound_t* sound, bool repeat, case AUDIO_MIXER_TYPE_OGG: #ifdef HAVE_STB_VORBIS res = audio_mixer_play_ogg(sound, voice, repeat, volume, stop_cb); +#endif + break; + case AUDIO_MIXER_TYPE_MOD: +#ifdef HAVE_IBXM + res = audio_mixer_play_mod(sound, voice, repeat, volume, stop_cb); #endif break; case AUDIO_MIXER_TYPE_NONE: @@ -608,6 +722,83 @@ again: } #endif +#ifdef HAVE_IBXM +static void audio_mixer_mix_mod(float* buffer, size_t num_frames, + audio_mixer_voice_t* voice, + float volume) +{ + int i; + unsigned temp_samples = 0; + unsigned buf_free = num_frames * 2; + int* pcm = NULL; + + if (voice->types.mod.position == voice->types.mod.samples) + { +again: + temp_samples = replay_get_audio( voice->types.mod.stream, voice->types.mod.buffer ); + + if (temp_samples == 0) + { + if (voice->repeat) + { + if (voice->stop_cb) + voice->stop_cb(voice->sound, AUDIO_MIXER_SOUND_REPEATED); + + replay_seek( voice->types.mod.stream, 0); + goto again; + } + else + { + if (voice->stop_cb) + voice->stop_cb(voice->sound, AUDIO_MIXER_SOUND_FINISHED); + + voice->type = AUDIO_MIXER_TYPE_NONE; + return; + } + } + + voice->types.mod.position = 0; + voice->types.mod.samples = temp_samples; + } + + pcm = voice->types.mod.buffer + voice->types.mod.position; + + float samplef = 0.0f; + int samplei = 0; + if (voice->types.mod.samples < buf_free) + { + for (i = voice->types.mod.samples; i != 0; i--) + { + samplei = *pcm++ * volume; + samplef = (float)((int)samplei + 32768) / 65535.0f; + samplef = samplef * 2.0f - 1.0f; + *buffer++ = samplef; + samplei = *pcm++ * volume; + samplef = (float)((int)samplei + 32768) / 65535.0f; + samplef = samplef * 2.0f - 1.0f; + *buffer++ = samplef; + } + + buf_free -= voice->types.mod.samples; + goto again; + } + else + { + int i; + for (i = buf_free; i != 0; --i ) + { + samplei = *pcm++ * volume; + samplef = (float)((int)samplei + 32768) / 65535.0f; + samplef = samplef * 2.0f - 1.0f; + *buffer++ = samplef; + } + + voice->types.mod.position += buf_free; + voice->types.mod.samples -= buf_free; + } +} +#endif + void audio_mixer_mix(float* buffer, size_t num_frames, float volume_override, bool override) { unsigned i; @@ -631,6 +822,10 @@ void audio_mixer_mix(float* buffer, size_t num_frames, float volume_override, bo case AUDIO_MIXER_TYPE_OGG: #ifdef HAVE_STB_VORBIS audio_mixer_mix_ogg(buffer, num_frames, voice, volume); +#endif + case AUDIO_MIXER_TYPE_MOD: +#ifdef HAVE_IBXM + audio_mixer_mix_mod(buffer, num_frames, voice, volume); #endif break; case AUDIO_MIXER_TYPE_NONE: diff --git a/libretro-common/include/audio/audio_mixer.h b/libretro-common/include/audio/audio_mixer.h index f9044bd3d6..3df9c64931 100644 --- a/libretro-common/include/audio/audio_mixer.h +++ b/libretro-common/include/audio/audio_mixer.h @@ -40,7 +40,8 @@ enum audio_mixer_type { AUDIO_MIXER_TYPE_NONE = 0, AUDIO_MIXER_TYPE_WAV, - AUDIO_MIXER_TYPE_OGG + AUDIO_MIXER_TYPE_OGG, + AUDIO_MIXER_TYPE_MOD }; typedef struct audio_mixer_sound audio_mixer_sound_t; @@ -59,6 +60,7 @@ void audio_mixer_done(void); audio_mixer_sound_t* audio_mixer_load_wav(void *buffer, int32_t size); audio_mixer_sound_t* audio_mixer_load_ogg(void *buffer, int32_t size); +audio_mixer_sound_t* audio_mixer_load_mod(void *buffer, int32_t size); void audio_mixer_destroy(audio_mixer_sound_t* sound); diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index fa3124660b..d7e4f98dd9 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -6424,6 +6424,11 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data) #endif #ifdef HAVE_RTGA string_list_append(str_list, "tga", attr); +#endif +#ifdef HAVE_IBXM + string_list_append(str_list, "mod", attr); + string_list_append(str_list, "s3m", attr); + string_list_append(str_list, "xm", attr); #endif string_list_join_concat(info->exts, sizeof(info->exts), str_list, "|"); string_list_free(str_list); diff --git a/msg_hash.c b/msg_hash.c index ae141c16ae..d0ccaa8b5c 100644 --- a/msg_hash.c +++ b/msg_hash.c @@ -169,6 +169,9 @@ uint32_t msg_hash_calculate(const char *s) #define MENU_VALUE_FILE_MP3 0x0b889135U #define MENU_VALUE_FILE_FLAC 0x7c96d67bU #define MENU_VALUE_FILE_OGG 0x0b8898c2U +#define MENU_VALUE_FILE_MOD 0x0b889145U +#define MENU_VALUE_FILE_S3M 0x0b88a318U +#define MENU_VALUE_FILE_XM 0x00597a2aU #define MENU_VALUE_FILE_FLV 0x0b88732dU #define MENU_VALUE_FILE_WAV 0x0b88ba13U #define MENU_VALUE_FILE_MOV 0x0b889157U @@ -347,6 +350,14 @@ enum msg_file_type msg_hash_to_file_type(uint32_t hash) case MENU_VALUE_FILE_WMA: return FILE_TYPE_WMA; #endif +#ifdef HAVE_IBXM + case MENU_VALUE_FILE_MOD: + return FILE_TYPE_MOD; + case MENU_VALUE_FILE_S3M: + return FILE_TYPE_S3M; + case MENU_VALUE_FILE_XM: + return FILE_TYPE_XM; +#endif #ifdef HAVE_IMAGEVIEWER case MENU_VALUE_FILE_JPG: case MENU_VALUE_FILE_JPG_CAPS: diff --git a/msg_hash.h b/msg_hash.h index 802833c220..3ad50489c9 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -127,6 +127,10 @@ enum msg_file_type FILE_TYPE_TGA, FILE_TYPE_BMP, + FILE_TYPE_MOD, + FILE_TYPE_S3M, + FILE_TYPE_XM, + FILE_TYPE_CUE, FILE_TYPE_ISO, FILE_TYPE_LUTRO, diff --git a/paths.c b/paths.c index c820e0027f..b8ba14c0b5 100644 --- a/paths.c +++ b/paths.c @@ -744,6 +744,13 @@ enum rarch_content_type path_is_media_type(const char *path) case FILE_TYPE_BMP: return RARCH_CONTENT_IMAGE; #endif +#ifdef HAVE_IBXM + case FILE_TYPE_MOD: + case FILE_TYPE_S3M: + case FILE_TYPE_XM: + return RARCH_CONTENT_MUSIC; +#endif + case FILE_TYPE_NONE: default: break; diff --git a/qb/config.params.sh b/qb/config.params.sh index 9386d6b960..2b4eb22efb 100644 --- a/qb/config.params.sh +++ b/qb/config.params.sh @@ -71,6 +71,7 @@ HAVE_FREETYPE=auto # FreeType support HAVE_STB_FONT=yes # stb_truetype font support HAVE_STB_IMAGE=yes # stb image loading support HAVE_STB_VORBIS=yes # stb vorbis support +HAVE_IBXM=yes # IBXM support HAVE_XVIDEO=auto # XVideo support HAVE_PYTHON=no # Python 3 support for shaders C89_PYTHON=no diff --git a/tasks/task_audio_mixer.c b/tasks/task_audio_mixer.c index cbf0b4c6fa..ee5d70f0c1 100644 --- a/tasks/task_audio_mixer.c +++ b/tasks/task_audio_mixer.c @@ -99,6 +99,28 @@ static void task_audio_mixer_handle_upload_ogg(void *task_data, free(user_data); } +static void task_audio_mixer_handle_upload_mod(void *task_data, + void *user_data, const char *err) +{ + audio_mixer_stream_params_t params; + nbio_buf_t *img = (nbio_buf_t*)task_data; + + if (!img) + return; + + params.volume = 1.0f; + params.type = AUDIO_MIXER_TYPE_MOD; + params.state = AUDIO_STREAM_STATE_PLAYING; + params.buf = img->buf; + params.bufsize = img->bufsize; + params.cb = NULL; + + audio_driver_mixer_add_stream(¶ms); + + free(img); + free(user_data); +} + static void task_audio_mixer_handle_upload_wav(void *task_data, void *user_data, const char *err) { @@ -191,6 +213,14 @@ bool task_push_audio_mixer_load(const char *fullpath, retro_task_callback_t cb, nbio->type = NBIO_TYPE_OGG; t->callback = task_audio_mixer_handle_upload_ogg; } + else if ( strstr(fullpath, file_path_str(FILE_PATH_MOD_EXTENSION)) || + strstr(fullpath, file_path_str(FILE_PATH_S3M_EXTENSION)) || + strstr(fullpath, file_path_str(FILE_PATH_XM_EXTENSION))) + { + image->type = AUDIO_MIXER_TYPE_MOD; + nbio->type = NBIO_TYPE_MOD; + t->callback = task_audio_mixer_handle_upload_mod; + } nbio->data = (struct audio_mixer_handle*)image; nbio->is_finished = false; diff --git a/tasks/task_file_transfer.c b/tasks/task_file_transfer.c index 317152e7d9..de00534f4d 100644 --- a/tasks/task_file_transfer.c +++ b/tasks/task_file_transfer.c @@ -104,6 +104,7 @@ void task_file_load_handler(retro_task_t *task) task_set_finished(task, true); break; case NBIO_TYPE_OGG: + case NBIO_TYPE_MOD: case NBIO_TYPE_WAV: if (!task_audio_mixer_load_handler(task)) task_set_finished(task, true); diff --git a/tasks/tasks_internal.h b/tasks/tasks_internal.h index bb74a2d3a3..390d9d49be 100644 --- a/tasks/tasks_internal.h +++ b/tasks/tasks_internal.h @@ -74,6 +74,7 @@ enum nbio_type NBIO_TYPE_TGA, NBIO_TYPE_BMP, NBIO_TYPE_OGG, + NBIO_TYPE_MOD, NBIO_TYPE_WAV }; From d865c4cdcf6a09e169433017753d06c609e0da21 Mon Sep 17 00:00:00 2001 From: Romain Tisserand Date: Fri, 4 Aug 2017 16:09:53 +0200 Subject: [PATCH 2/2] First working prototype. --- libretro-common/audio/audio_mixer.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/libretro-common/audio/audio_mixer.c b/libretro-common/audio/audio_mixer.c index 29ab4cd441..073ba59d76 100644 --- a/libretro-common/audio/audio_mixer.c +++ b/libretro-common/audio/audio_mixer.c @@ -506,10 +506,10 @@ static bool audio_mixer_play_mod( } voice->types.mod.buffer = mod_buffer; - voice->types.mod.buf_samples = samples; + voice->types.mod.buf_samples = buf_samples; voice->types.mod.stream = replay; voice->types.mod.position = 0; - voice->types.mod.samples = samples; + voice->types.mod.samples = 0;//samples; return true; @@ -736,7 +736,8 @@ static void audio_mixer_mix_mod(float* buffer, size_t num_frames, { again: temp_samples = replay_get_audio( voice->types.mod.stream, voice->types.mod.buffer ); - + temp_samples *= 2; // stereo + if (temp_samples == 0) { if (voice->repeat) @@ -760,7 +761,6 @@ again: voice->types.mod.position = 0; voice->types.mod.samples = temp_samples; } - pcm = voice->types.mod.buffer + voice->types.mod.position; float samplef = 0.0f; @@ -773,10 +773,6 @@ again: samplef = (float)((int)samplei + 32768) / 65535.0f; samplef = samplef * 2.0f - 1.0f; *buffer++ = samplef; - samplei = *pcm++ * volume; - samplef = (float)((int)samplei + 32768) / 65535.0f; - samplef = samplef * 2.0f - 1.0f; - *buffer++ = samplef; } buf_free -= voice->types.mod.samples;