mirror of
https://github.com/ZDoom/qzdoom.git
synced 2025-02-15 08:41:46 +00:00
559 lines
14 KiB
C
559 lines
14 KiB
C
/* _______ ____ __ ___ ___
|
|
* \ _ \ \ / \ / \ \ / / ' ' '
|
|
* | | \ \ | | || | \/ | . .
|
|
* | | | | | | || ||\ /| |
|
|
* | | | | | | || || \/ | | ' ' '
|
|
* | | | | | | || || | | . .
|
|
* | |_/ / \ \__// || | |
|
|
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
|
|
* / \
|
|
* / . \
|
|
* readamf.c - Code to read a DSMI AMF module from / / \ \
|
|
* an open file. | < / \_
|
|
* | \/ /\ /
|
|
* By Chris Moeller. \_ / > /
|
|
* | \ / /
|
|
* | ' /
|
|
* \__/
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include "dumb.h"
|
|
#include "internal/it.h"
|
|
|
|
|
|
|
|
static void it_amf_process_track( IT_ENTRY *entry_table, unsigned char *track, int rows, int channels )
|
|
{
|
|
int last_instrument = 0;
|
|
int tracksize = track[ 0 ] + ( track[ 1 ] << 8 ) + ( track[ 2 ] << 16 );
|
|
track += 3;
|
|
while ( tracksize-- ) {
|
|
unsigned int row = track[ 0 ];
|
|
unsigned int command = track[ 1 ];
|
|
unsigned int argument = track[ 2 ];
|
|
IT_ENTRY * entry = entry_table + row * channels;
|
|
if ( row >= ( unsigned int ) rows ) break;
|
|
if ( command < 0x7F ) {
|
|
entry->mask |= IT_ENTRY_NOTE | IT_ENTRY_INSTRUMENT | IT_ENTRY_VOLPAN;
|
|
entry->note = command;
|
|
if ( ! entry->instrument ) entry->instrument = last_instrument;
|
|
entry->volpan = argument;
|
|
}
|
|
else if ( command == 0x7F ) {
|
|
signed char row_delta = ( signed char ) argument;
|
|
int row_source = ( int ) row + ( int ) row_delta;
|
|
if ( row_source >= 0 && row_source < ( int ) rows ) {
|
|
*entry = entry_table[ row_source * channels ];
|
|
}
|
|
}
|
|
else if ( command == 0x80 ) {
|
|
entry->mask |= IT_ENTRY_INSTRUMENT;
|
|
last_instrument = argument + 1;
|
|
entry->instrument = last_instrument;
|
|
}
|
|
else if ( command == 0x83 ) {
|
|
entry->mask |= IT_ENTRY_VOLPAN;
|
|
entry->volpan = argument;
|
|
}
|
|
else {
|
|
unsigned int effect = command & 0x7F;
|
|
unsigned int effectvalue = argument;
|
|
switch (effect) {
|
|
case 0x01: effect = IT_SET_SPEED; break;
|
|
|
|
case 0x02: effect = IT_VOLUME_SLIDE;
|
|
case 0x0A: if ( effect == 0x0A ) effect = IT_VOLSLIDE_TONEPORTA;
|
|
case 0x0B: if ( effect == 0x0B ) effect = IT_VOLSLIDE_VIBRATO;
|
|
if ( effectvalue & 0x80 ) effectvalue = ( -( signed char ) effectvalue ) & 0x0F;
|
|
else effectvalue = ( effectvalue & 0x0F ) << 4;
|
|
break;
|
|
|
|
case 0x04:
|
|
if ( effectvalue & 0x80 ) {
|
|
effect = IT_PORTAMENTO_UP;
|
|
effectvalue = ( -( signed char ) effectvalue ) & 0x7F;
|
|
}
|
|
else {
|
|
effect = IT_PORTAMENTO_DOWN;
|
|
}
|
|
break;
|
|
|
|
case 0x06: effect = IT_TONE_PORTAMENTO; break;
|
|
|
|
case 0x07: effect = IT_TREMOR; break;
|
|
|
|
case 0x08: effect = IT_ARPEGGIO; break;
|
|
|
|
case 0x09: effect = IT_VIBRATO; break;
|
|
|
|
case 0x0C: effect = IT_BREAK_TO_ROW; break;
|
|
|
|
case 0x0D: effect = IT_JUMP_TO_ORDER; break;
|
|
|
|
case 0x0F: effect = IT_RETRIGGER_NOTE; break;
|
|
|
|
case 0x10: effect = IT_SET_SAMPLE_OFFSET; break;
|
|
|
|
case 0x11:
|
|
if ( effectvalue ) {
|
|
effect = IT_VOLUME_SLIDE;
|
|
if ( effectvalue & 0x80 )
|
|
effectvalue = 0xF0 | ( ( -( signed char ) effectvalue ) & 0x0F );
|
|
else
|
|
effectvalue = 0x0F | ( ( effectvalue & 0x0F ) << 4 );
|
|
}
|
|
else
|
|
effect = 0;
|
|
break;
|
|
|
|
case 0x12:
|
|
case 0x16:
|
|
if ( effectvalue ) {
|
|
int mask = ( effect == 0x16 ) ? 0xE0 : 0xF0;
|
|
effect = ( effectvalue & 0x80 ) ? IT_PORTAMENTO_UP : IT_PORTAMENTO_DOWN;
|
|
if ( effectvalue & 0x80 )
|
|
effectvalue = mask | ( ( -( signed char ) effectvalue ) & 0x0F );
|
|
else
|
|
effectvalue = mask | ( effectvalue & 0x0F );
|
|
}
|
|
else
|
|
effect = 0;
|
|
break;
|
|
|
|
case 0x13:
|
|
effect = IT_S;
|
|
effectvalue = EFFECT_VALUE( IT_S_NOTE_DELAY, effectvalue & 0x0F );
|
|
break;
|
|
|
|
case 0x14:
|
|
effect = IT_S;
|
|
effectvalue = EFFECT_VALUE( IT_S_DELAYED_NOTE_CUT, effectvalue & 0x0F );
|
|
break;
|
|
|
|
case 0x15: effect = IT_SET_SONG_TEMPO; break;
|
|
|
|
case 0x17:
|
|
effectvalue = ( effectvalue + 64 ) & 0x7F;
|
|
if ( entry->mask & IT_ENTRY_EFFECT ) {
|
|
if ( !( entry->mask & IT_ENTRY_VOLPAN ) ) {
|
|
entry->mask |= IT_ENTRY_VOLPAN;
|
|
entry->volpan = ( effectvalue / 2 ) + 128;
|
|
}
|
|
effect = 0;
|
|
}
|
|
else {
|
|
effect = IT_SET_PANNING;
|
|
}
|
|
break;
|
|
|
|
default: effect = effectvalue = 0;
|
|
}
|
|
if ( effect ) {
|
|
entry->mask |= IT_ENTRY_EFFECT;
|
|
entry->effect = effect;
|
|
entry->effectvalue = effectvalue;
|
|
}
|
|
}
|
|
track += 3;
|
|
}
|
|
}
|
|
|
|
static int it_amf_process_pattern( IT_PATTERN *pattern, IT_ENTRY *entry_table, int rows, int channels )
|
|
{
|
|
int i, j;
|
|
int n_entries = rows;
|
|
IT_ENTRY * entry;
|
|
|
|
pattern->n_rows = rows;
|
|
|
|
for ( i = 0, j = channels * rows; i < j; i++ ) {
|
|
if ( entry_table[ i ].mask ) {
|
|
n_entries++;
|
|
}
|
|
}
|
|
|
|
pattern->n_entries = n_entries;
|
|
|
|
pattern->entry = entry = malloc( n_entries * sizeof( IT_ENTRY ) );
|
|
if ( !entry ) {
|
|
return -1;
|
|
}
|
|
|
|
for ( i = 0; i < rows; i++ ) {
|
|
for ( j = 0; j < channels; j++ ) {
|
|
if ( entry_table[ i * channels + j ].mask ) {
|
|
*entry = entry_table[ i * channels + j ];
|
|
entry->channel = j;
|
|
entry++;
|
|
}
|
|
}
|
|
IT_SET_END_ROW( entry );
|
|
entry++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int it_amf_read_sample_header( IT_SAMPLE *sample, DUMBFILE *f, int * offset, int ver )
|
|
{
|
|
int exists;
|
|
|
|
exists = dumbfile_getc( f );
|
|
|
|
dumbfile_getnc( (char *) sample->name, 32, f );
|
|
sample->name[32] = 0;
|
|
|
|
dumbfile_getnc( (char *) sample->filename, 13, f );
|
|
sample->filename[13] = 0;
|
|
|
|
*offset = dumbfile_igetl( f );
|
|
sample->length = dumbfile_igetl( f );
|
|
sample->C5_speed = dumbfile_igetw( f );
|
|
sample->default_volume = dumbfile_getc( f );
|
|
sample->global_volume = 64;
|
|
if ( sample->default_volume > 64 ) sample->default_volume = 64;
|
|
|
|
if ( ver >= 11 ) {
|
|
sample->loop_start = dumbfile_igetl( f );
|
|
sample->loop_end = dumbfile_igetl( f );
|
|
} else {
|
|
sample->loop_start = dumbfile_igetw( f );
|
|
sample->loop_end = sample->length;
|
|
}
|
|
|
|
if ( sample->length <= 0 ) {
|
|
sample->flags = 0;
|
|
return 0;
|
|
}
|
|
|
|
sample->flags = exists == 1 ? IT_SAMPLE_EXISTS : 0;
|
|
|
|
sample->default_pan = 0;
|
|
sample->finetune = 0;
|
|
|
|
if ( sample->loop_end > sample->loop_start + 2 && sample->loop_end <= sample->length )
|
|
sample->flags |= IT_SAMPLE_LOOP;
|
|
|
|
sample->vibrato_speed = 0;
|
|
sample->vibrato_depth = 0;
|
|
sample->vibrato_rate = 0;
|
|
sample->vibrato_waveform = 0; // do we have to set _all_ these?
|
|
sample->max_resampling_quality = -1;
|
|
|
|
return dumbfile_error(f);
|
|
}
|
|
|
|
|
|
|
|
static int it_amf_read_sample_data( IT_SAMPLE *sample, DUMBFILE *f )
|
|
{
|
|
int i, read_length = 0;
|
|
|
|
sample->data = malloc( sample->length );
|
|
|
|
if ( !sample->data )
|
|
return -1;
|
|
|
|
if ( sample->length )
|
|
read_length = dumbfile_getnc( sample->data, sample->length, f );
|
|
|
|
for ( i = 0; i < read_length; i++ ) {
|
|
( ( char * ) sample->data )[ i ] ^= 0x80;
|
|
}
|
|
|
|
for ( i = read_length; i < sample->length; i++ ) {
|
|
( ( char * ) sample->data )[ i ] = 0;
|
|
}
|
|
|
|
return 0; /* Sometimes the last sample is truncated :( */
|
|
}
|
|
|
|
static DUMB_IT_SIGDATA *it_amf_load_sigdata(DUMBFILE *f, int * version)
|
|
{
|
|
DUMB_IT_SIGDATA *sigdata;
|
|
int i, j, ver, ntracks, realntracks, nchannels;
|
|
|
|
int maxsampleseekpos = 0;
|
|
int sampleseekpos[256];
|
|
|
|
unsigned short *orderstotracks;
|
|
unsigned short *trackmap;
|
|
unsigned int tracksize[256];
|
|
|
|
unsigned char **track;
|
|
|
|
static const char sig[] = "AMF";
|
|
|
|
char signature [3];
|
|
|
|
if ( dumbfile_getnc( signature, 3, f ) != 3 ||
|
|
memcmp( signature, sig, 3 ) ) {
|
|
return NULL;
|
|
}
|
|
|
|
*version = ver = dumbfile_getc( f );
|
|
if ( ver < 10 || ver > 14) {
|
|
return NULL;
|
|
}
|
|
|
|
sigdata = malloc(sizeof(*sigdata));
|
|
if (!sigdata) {
|
|
return NULL;
|
|
}
|
|
|
|
dumbfile_getnc( (char *) sigdata->name, 32, f );
|
|
sigdata->name[ 32 ] = 0;
|
|
sigdata->n_samples = dumbfile_getc( f );
|
|
sigdata->n_orders = dumbfile_getc( f );
|
|
ntracks = dumbfile_igetw( f );
|
|
nchannels = dumbfile_getc( f );
|
|
|
|
if ( dumbfile_error( f ) ||
|
|
sigdata->n_samples < 1 || sigdata->n_samples > 255 ||
|
|
sigdata->n_orders < 1 || sigdata->n_orders > 255 ||
|
|
! ntracks ||
|
|
nchannels < 1 || nchannels > 32 ) {
|
|
free( sigdata );
|
|
return NULL;
|
|
}
|
|
|
|
sigdata->n_pchannels = nchannels;
|
|
|
|
memset( sigdata->channel_volume, 64, DUMB_IT_N_CHANNELS );
|
|
|
|
if ( ver >= 11 ) {
|
|
int nchannels = ( ver >= 13 ) ? 32 : 16;
|
|
for ( i = 0; i < nchannels; i++ ) {
|
|
signed char panpos = dumbfile_getc( f );
|
|
int pan = ( panpos + 64 ) / 2;
|
|
if ( pan < 0 ) pan = 0;
|
|
else if ( pan > 64 ) pan = IT_SURROUND;
|
|
sigdata->channel_pan[ i ] = pan;
|
|
}
|
|
}
|
|
else {
|
|
int sep = 32 * dumb_it_default_panning_separation / 100;
|
|
for ( i = 0; i < 16; i++ ) {
|
|
sigdata->channel_pan[ i ] = ( dumbfile_getc( f ) & 1 ) ? 32 - sep : 32 + sep;
|
|
}
|
|
}
|
|
|
|
sigdata->tempo = 125;
|
|
sigdata->speed = 6;
|
|
if ( ver >= 13 ) {
|
|
i = dumbfile_getc( f );
|
|
if ( i >= 32 ) sigdata->tempo = i;
|
|
i = dumbfile_getc( f );
|
|
if ( i <= 32 ) sigdata->speed = i;
|
|
}
|
|
|
|
sigdata->order = malloc( sigdata->n_orders );
|
|
if ( !sigdata->order ) {
|
|
free( sigdata );
|
|
return NULL;
|
|
}
|
|
|
|
orderstotracks = malloc( sigdata->n_orders * nchannels * sizeof( unsigned short ) );
|
|
if ( !orderstotracks ) {
|
|
free( sigdata->order );
|
|
free( sigdata );
|
|
return NULL;
|
|
}
|
|
|
|
for ( i = 0; i < sigdata->n_orders; i++ ) {
|
|
sigdata->order[ i ] = i;
|
|
tracksize[ i ] = 64;
|
|
if ( ver >= 14 ) {
|
|
tracksize[ i ] = dumbfile_igetw( f );
|
|
}
|
|
for ( j = 0; j < nchannels; j++ ) {
|
|
orderstotracks[ i * nchannels + j ] = dumbfile_igetw( f );
|
|
}
|
|
}
|
|
|
|
if ( dumbfile_error( f ) ) {
|
|
free( orderstotracks );
|
|
free( sigdata->order );
|
|
free( sigdata );
|
|
return NULL;
|
|
}
|
|
|
|
sigdata->sample = malloc( sigdata->n_samples * sizeof( *sigdata->sample ) );
|
|
if ( !sigdata->sample ) {
|
|
free( orderstotracks );
|
|
free( sigdata->order );
|
|
free( sigdata );
|
|
return NULL;
|
|
}
|
|
|
|
sigdata->restart_position = 0;
|
|
|
|
sigdata->song_message = NULL;
|
|
sigdata->instrument = NULL;
|
|
sigdata->pattern = NULL;
|
|
sigdata->midi = NULL;
|
|
sigdata->checkpoint = NULL;
|
|
|
|
sigdata->n_instruments = 0;
|
|
|
|
for ( i = 0; i < sigdata->n_samples; ++i )
|
|
sigdata->sample[i].data = NULL;
|
|
|
|
for ( i = 0; i < sigdata->n_samples; ++i ) {
|
|
int offset;
|
|
if ( it_amf_read_sample_header( &sigdata->sample[i], f, &offset, ver ) ) {
|
|
goto error_ott;
|
|
}
|
|
sampleseekpos[ i ] = offset;
|
|
if ( offset > maxsampleseekpos ) maxsampleseekpos = offset;
|
|
}
|
|
|
|
sigdata->n_patterns = sigdata->n_orders;
|
|
|
|
sigdata->pattern = malloc( sigdata->n_patterns * sizeof( *sigdata->pattern ) );
|
|
if ( !sigdata->pattern ) {
|
|
goto error_ott;
|
|
}
|
|
for (i = 0; i < sigdata->n_patterns; ++i)
|
|
sigdata->pattern[i].entry = NULL;
|
|
|
|
trackmap = malloc( ntracks * sizeof( unsigned short ) );
|
|
if ( !trackmap ) {
|
|
goto error_ott;
|
|
}
|
|
|
|
if ( dumbfile_getnc( ( char * ) trackmap, ntracks * sizeof( unsigned short ), f ) != (long)(ntracks * sizeof( unsigned short )) ) {
|
|
goto error_tm;
|
|
}
|
|
|
|
realntracks = 0;
|
|
|
|
for ( i = 0; i < ntracks; i++ ) {
|
|
if ( trackmap[ i ] > realntracks ) realntracks = trackmap[ i ];
|
|
}
|
|
|
|
track = calloc( realntracks, sizeof( unsigned char * ) );
|
|
if ( !track ) {
|
|
goto error_tm;
|
|
}
|
|
|
|
for ( i = 0; i < realntracks; i++ ) {
|
|
int tracksize = dumbfile_igetw( f );
|
|
tracksize += dumbfile_getc( f ) << 16;
|
|
track[ i ] = malloc( tracksize * 3 + 3 );
|
|
if ( !track[ i ] ) {
|
|
goto error_all;
|
|
}
|
|
track[ i ][ 0 ] = tracksize & 255;
|
|
track[ i ][ 1 ] = ( tracksize >> 8 ) & 255;
|
|
track[ i ][ 2 ] = ( tracksize >> 16 ) & 255;
|
|
if ( dumbfile_getnc( (char *) track[ i ] + 3, tracksize * 3, f ) != tracksize * 3 ) {
|
|
goto error_all;
|
|
}
|
|
}
|
|
|
|
for ( i = 1; i <= maxsampleseekpos; i++ ) {
|
|
for ( j = 0; j < sigdata->n_samples; j++ ) {
|
|
if ( sampleseekpos[ j ] == i ) {
|
|
if ( it_amf_read_sample_data( &sigdata->sample[ j ], f ) ) {
|
|
goto error_all;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Process tracks into patterns */
|
|
for ( i = 0; i < sigdata->n_patterns; i++ ) {
|
|
IT_ENTRY * entry_table = calloc( tracksize[ i ] * nchannels, sizeof( IT_ENTRY ) );
|
|
if ( !entry_table ) {
|
|
goto error_all;
|
|
}
|
|
for ( j = 0; j < nchannels; j++ ) {
|
|
int ntrack = orderstotracks[ i * nchannels + j ];
|
|
if ( ntrack && ntrack <= ntracks ) {
|
|
int realtrack = trackmap[ ntrack - 1 ];
|
|
if ( realtrack ) {
|
|
realtrack--;
|
|
if ( realtrack < realntracks && track[ realtrack ] ) {
|
|
it_amf_process_track( entry_table + j, track[ realtrack ], tracksize[ i ], nchannels );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( it_amf_process_pattern( &sigdata->pattern[ i ], entry_table, tracksize[ i ], nchannels ) ) {
|
|
free( entry_table );
|
|
goto error_all;
|
|
}
|
|
free( entry_table );
|
|
}
|
|
|
|
/* Now let's initialise the remaining variables, and we're done! */
|
|
sigdata->flags = IT_OLD_EFFECTS | IT_COMPATIBLE_GXX | IT_STEREO | IT_WAS_AN_S3M;
|
|
|
|
sigdata->global_volume = 128;
|
|
sigdata->mixing_volume = 48;
|
|
sigdata->pan_separation = 128;
|
|
|
|
_dumb_it_fix_invalid_orders(sigdata);
|
|
|
|
for ( i = 0; i < realntracks; i++ ) {
|
|
if ( track[ i ] ) {
|
|
free( track[ i ] );
|
|
}
|
|
}
|
|
free( track );
|
|
free( trackmap );
|
|
free( orderstotracks );
|
|
|
|
return sigdata;
|
|
|
|
error_all:
|
|
for ( i = 0; i < realntracks; i++ ) {
|
|
if ( track[ i ] ) {
|
|
free( track[ i ] );
|
|
}
|
|
}
|
|
free( track );
|
|
error_tm:
|
|
free( trackmap );
|
|
error_ott:
|
|
free( orderstotracks );
|
|
_dumb_it_unload_sigdata( sigdata );
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
DUH *DUMBEXPORT dumb_read_amf_quick(DUMBFILE *f)
|
|
{
|
|
sigdata_t *sigdata;
|
|
|
|
DUH_SIGTYPE_DESC *descptr = &_dumb_sigtype_it;
|
|
|
|
int version;
|
|
|
|
sigdata = it_amf_load_sigdata(f, &version);
|
|
|
|
if (!sigdata)
|
|
return NULL;
|
|
|
|
{
|
|
const char *tag[2][2];
|
|
char ver_string[14];
|
|
tag[0][0] = "TITLE";
|
|
tag[0][1] = (const char *)(((DUMB_IT_SIGDATA *)sigdata)->name);
|
|
tag[1][0] = "FORMAT";
|
|
memcpy( ver_string, "DSMI AMF v", 10 );
|
|
ver_string[10] = '0' + version / 10;
|
|
ver_string[11] = '.';
|
|
ver_string[12] = '0' + version % 10;
|
|
ver_string[13] = 0;
|
|
tag[1][1] = ver_string;
|
|
return make_duh(-1, 2, (const char *const (*)[2])tag, 1, &descptr, &sigdata);
|
|
}
|
|
}
|