gzdoom/dumb/src/it/reads3m.c
Randy Heit 01f59fa85f - Added an alternate module replay engine that uses foo_dumb's replayer, a
heavily customized version of DUMB (Dynamic Universal Music Bibliotheque).
  It has been slightly modified by me:
  * Added support for Ogg Vorbis-compressed samples in XM files ala FMOD.
  * Removed excessive mallocs from the replay core.
  * Rerolled the loops in resample.c. Unrolling them made the object file
    ~250k large while providing little benefit. Even at ~100k, I think it's
    still larger than it ought to be, but I'll live with it for now.
  Other than that, it's essentially the same thing you'd hear in foobar2000,
  minus some subsong detection features. Release builds of the library look
  like they might even be slightly faster than FMOD, which is a plus.
- Fixed: Timidity::font_add() did not release the file reader it created.
- Fixed: The SF2 loader did not free the sample headers in its destructor.


SVN r995 (trunk)
2008-05-29 23:33:07 +00:00

861 lines
22 KiB
C

/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* reads3m.c - Code to read a ScreamTracker 3 / / \ \
* module from an open file. | < / \_
* | \/ /\ /
* By entheh. \_ / > /
* | \ / /
* | ' /
* \__/
*/
// IT_STEREO... :o
#include <stdlib.h>
#include <string.h>
#include "dumb.h"
#include "internal/it.h"
//#define S3M_BROKEN_OVERLAPPED_SAMPLES
/** WARNING: this is duplicated in itread.c */
static int it_seek(DUMBFILE *f, int32 offset)
{
int32 pos = dumbfile_pos(f);
if (pos > offset) {
return -1;
}
if (pos < offset)
if (dumbfile_skip(f, offset - pos))
return -1;
return 0;
}
static int it_s3m_read_sample_header(IT_SAMPLE *sample, int32 *offset, unsigned char *pack, int cwtv, DUMBFILE *f)
{
unsigned char type;
int flags;
type = dumbfile_getc(f);
dumbfile_getnc(sample->filename, 12, f);
sample->filename[12] = 0;
if (type > 1) {
/** WARNING: no adlib support */
dumbfile_skip(f, 3 + 12 + 1 + 1 + 2 + 2 + 2 + 12);
dumbfile_getnc(sample->name, 28, f);
sample->name[28] = 0;
dumbfile_skip(f, 4);
sample->flags &= ~IT_SAMPLE_EXISTS;
return dumbfile_error(f);
}
*offset = dumbfile_getc(f) << 20;
*offset += dumbfile_igetw(f) << 4;
sample->length = dumbfile_igetl(f);
sample->loop_start = dumbfile_igetl(f);
sample->loop_end = dumbfile_igetl(f);
sample->default_volume = dumbfile_getc(f);
dumbfile_skip(f, 1);
flags = dumbfile_getc(f);
if (flags < 0 || (flags != 0 && flags != 4))
/* Sample is packed apparently (or error reading from file). We don't
* know how to read packed samples.
*/
return -1;
*pack = flags;
flags = dumbfile_getc(f);
sample->C5_speed = dumbfile_igetl(f) << 1;
/* Skip four unused bytes and three internal variables. */
dumbfile_skip(f, 4+2+2+4);
dumbfile_getnc(sample->name, 28, f);
sample->name[28] = 0;
if (type == 0 || sample->length <= 0) {
/* Looks like no-existy. Anyway, there's for sure no 'SCRS' ... */
sample->flags &= ~IT_SAMPLE_EXISTS;
return dumbfile_error(f);
}
if (dumbfile_mgetl(f) != DUMB_ID('S','C','R','S'))
return -1;
sample->global_volume = 64;
sample->flags = IT_SAMPLE_EXISTS;
if (flags & 1) sample->flags |= IT_SAMPLE_LOOP;
/* The ST3 TECH.DOC is unclear on this, but IMAGO Orpheus is not. Piece of crap. */
if (flags & 2) {
sample->flags |= IT_SAMPLE_STEREO;
if ((cwtv & 0xF000) == 0x2000) {
sample->length >>= 1;
sample->loop_start >>= 1;
sample->loop_end >>= 1;
}
}
if (flags & 4) {
sample->flags |= IT_SAMPLE_16BIT;
if ((cwtv & 0xF000) == 0x2000) {
sample->length >>= 1;
sample->loop_start >>= 1;
sample->loop_end >>= 1;
}
}
sample->default_pan = 0; // 0 = don't use, or 160 = centre?
if (sample->flags & IT_SAMPLE_LOOP) {
if ((unsigned int)sample->loop_end > (unsigned int)sample->length)
/*sample->flags &= ~IT_SAMPLE_LOOP;*/
sample->loop_end = sample->length;
else if ((unsigned int)sample->loop_start >= (unsigned int)sample->loop_end)
sample->flags &= ~IT_SAMPLE_LOOP;
else
/* ScreamTracker seems not to save what comes after the loop end
* point, but rather to assume it is a duplicate of what comes at
* the loop start point. I am not completely sure of this though.
* It is easy to evade; simply truncate the sample.
*/
sample->length = sample->loop_end;
}
//Do we need to set all these?
sample->vibrato_speed = 0;
sample->vibrato_depth = 0;
sample->vibrato_rate = 0;
sample->vibrato_waveform = IT_VIBRATO_SINE;
sample->finetune = 0;
sample->max_resampling_quality = -1;
return dumbfile_error(f);
}
static int it_s3m_read_sample_data(IT_SAMPLE *sample, int ffi, unsigned char pack, DUMBFILE *f)
{
int32 n;
int32 datasize = sample->length;
if (sample->flags & IT_SAMPLE_STEREO) datasize <<= 1;
sample->data = malloc(datasize * (sample->flags & IT_SAMPLE_16BIT ? 2 : 1));
if (!sample->data)
return -1;
if (pack == 4) {
if (_dumb_it_read_sample_data_adpcm4(sample, f) < 0)
return -1;
}
else if (sample->flags & IT_SAMPLE_STEREO) {
if (sample->flags & IT_SAMPLE_16BIT) {
for (n = 0; n < datasize; n += 2)
((short *)sample->data)[n] = dumbfile_igetw(f);
for (n = 1; n < datasize; n += 2)
((short *)sample->data)[n] = dumbfile_igetw(f);
} else {
for (n = 0; n < datasize; n += 2)
((signed char *)sample->data)[n] = dumbfile_getc(f);
for (n = 1; n < datasize; n += 2)
((signed char *)sample->data)[n] = dumbfile_getc(f);
}
} else if (sample->flags & IT_SAMPLE_16BIT)
for (n = 0; n < sample->length; n++)
((short *)sample->data)[n] = dumbfile_igetw(f);
else
for (n = 0; n < sample->length; n++)
((signed char *)sample->data)[n] = dumbfile_getc(f);
if (dumbfile_error(f))
return -1;
if (ffi != 1) {
/* Convert to signed. */
if (sample->flags & IT_SAMPLE_16BIT)
for (n = 0; n < datasize; n++)
((short *)sample->data)[n] ^= 0x8000;
else
for (n = 0; n < datasize; n++)
((signed char *)sample->data)[n] ^= 0x80;
}
return 0;
}
static int it_s3m_read_pattern(IT_PATTERN *pattern, DUMBFILE *f, unsigned char *buffer, int maxlen)
{
int length;
int buflen = 0;
int bufpos = 0;
IT_ENTRY *entry;
unsigned char channel;
/* Haha, this is hilarious!
*
* Well, after some experimentation, it seems that different S3M writers
* define the format in different ways. The S3M docs say that the first
* two bytes hold the "length of [the] packed pattern", and the packed
* pattern data follow. Judging by the contents of ARMANI.S3M, packaged
* with ScreamTracker itself, the measure of length _includes_ the two
* bytes used to store the length; in other words, we should read
* (length - 2) more bytes. However, aryx.s3m, packaged with ModPlug
* Tracker, excludes these two bytes, so (length) more bytes must be
* read.
*
* Call me crazy, but I just find it insanely funny that the format was
* misunderstood in this way :D
*
* Now we can't just risk reading two extra bytes, because then we
* overshoot, and DUMBFILEs don't support backward seeking (for a good
* reason). Luckily, there is a way. We can read the data little by
* little, and stop when we have 64 rows in memory. Provided we protect
* against buffer overflow, this method should work with all sensibly
* written S3M files. If you find one for which it does not work, please
* let me know at entheh@users.sf.net so I can look at it.
*/
/* Discard the length. */
/* read at most length bytes, in case of retarded crap */
length = dumbfile_igetw(f);
if (maxlen)
{
maxlen -= 2;
if (length > maxlen) length = maxlen;
}
if (dumbfile_error(f) || !length)
return -1;
pattern->n_rows = 0;
pattern->n_entries = 0;
/* Read in the pattern data, little by little, and work out how many
* entries we need room for. Sorry, but this is just so funny...
*/
for (;;) {
unsigned char b = buffer[buflen++] = dumbfile_getc(f);
#if 1
static const unsigned char used[8] = {0, 2, 1, 3, 2, 4, 3, 5};
channel = b & 31;
b >>= 5;
pattern->n_entries++;
if (b) {
if (buflen + used[b] >= 65536) return -1;
if (buflen + used[b] <= length)
dumbfile_getnc(buffer + buflen, used[b], f);
else
memset(buffer + buflen, 0, used[b]);
buflen += used[b];
} else {
/* End of row */
if (++pattern->n_rows == 64) break;
if (buflen >= 65536) return -1;
}
#else
if (b == 0) {
/* End of row */
pattern->n_entries++;
if (++pattern->n_rows == 64) break;
if (buflen >= 65536) return -1;
} else {
static const unsigned char used[8] = {0, 2, 1, 3, 2, 4, 3, 5};
channel = b & 31;
b >>= 5;
if (b) {
pattern->n_entries++;
if (buflen + used[b] >= 65536) return -1;
dumbfile_getnc(buffer + buflen, used[b], f);
buflen += used[b];
}
}
#endif
/* We have ensured that buflen < 65536 at this point, so it is safe
* to iterate and read at least one more byte without checking.
* However, now would be a good time to check for errors reading from
* the file.
*/
if (dumbfile_error(f))
return -1;
/* Great. We ran out of data, but there should be data for more rows.
* Fill the rest with null data...
*/
if (buflen >= length && pattern->n_rows < 64)
{
while (pattern->n_rows < 64)
{
if (buflen >= 65536) return -1;
buffer[buflen++] = 0;
pattern->n_entries++;
pattern->n_rows++;
}
break;
}
}
pattern->entry = malloc(pattern->n_entries * sizeof(*pattern->entry));
if (!pattern->entry)
return -1;
entry = pattern->entry;
while (bufpos < buflen) {
unsigned char b = buffer[bufpos++];
#if 1
if (!(b & ~31))
#else
if (b == 0)
#endif
{
/* End of row */
IT_SET_END_ROW(entry);
entry++;
continue;
}
channel = b & 31;
if (b & 224) {
entry->mask = 0;
entry->channel = channel;
if (b & 32) {
unsigned char n = buffer[bufpos++];
if (n != 255) {
if (n == 254)
entry->note = IT_NOTE_CUT;
else
entry->note = (n >> 4) * 12 + (n & 15);
entry->mask |= IT_ENTRY_NOTE;
}
entry->instrument = buffer[bufpos++];
if (entry->instrument)
entry->mask |= IT_ENTRY_INSTRUMENT;
}
if (b & 64) {
entry->volpan = buffer[bufpos++];
if (entry->volpan != 255)
entry->mask |= IT_ENTRY_VOLPAN;
}
if (b & 128) {
entry->effect = buffer[bufpos++];
entry->effectvalue = buffer[bufpos++];
// XXX woot
if (entry->effect && entry->effect < IT_MIDI_MACRO /*!= 255*/) {
entry->mask |= IT_ENTRY_EFFECT;
switch (entry->effect) {
case IT_BREAK_TO_ROW:
entry->effectvalue -= (entry->effectvalue >> 4) * 6;
break;
case IT_SET_CHANNEL_VOLUME:
case IT_CHANNEL_VOLUME_SLIDE:
case IT_PANNING_SLIDE:
case IT_GLOBAL_VOLUME_SLIDE:
case IT_PANBRELLO:
case IT_MIDI_MACRO:
entry->mask &= ~IT_ENTRY_EFFECT;
break;
case IT_S:
switch (entry->effectvalue >> 4) {
case IT_S_SET_PANBRELLO_WAVEFORM:
case IT_S_FINE_PATTERN_DELAY:
case IT_S7:
case IT_S_SET_SURROUND_SOUND:
case IT_S_SET_MIDI_MACRO:
entry->mask &= ~IT_ENTRY_EFFECT;
break;
}
break;
}
}
/** WARNING: ARGH! CONVERT TEH EFFECTS!@~ */
}
entry++;
}
}
ASSERT(entry == pattern->entry + pattern->n_entries);
return 0;
}
/** WARNING: this is duplicated in itread.c - also bad practice to use the same struct name unless they are unified in a header */
/* Currently we assume the sample data are stored after the sample headers in
* module files. This assumption may be unjustified; let me know if you have
* trouble.
*/
#define S3M_COMPONENT_INSTRUMENT 1
#define S3M_COMPONENT_PATTERN 2
#define S3M_COMPONENT_SAMPLE 3
typedef struct S3M_COMPONENT
{
unsigned char type;
unsigned char n;
int32 offset;
short sampfirst; /* component[sampfirst] = first sample data after this */
short sampnext; /* sampnext is used to create linked lists of sample data */
}
S3M_COMPONENT;
static int CDECL s3m_component_compare(const void *e1, const void *e2)
{
return ((const S3M_COMPONENT *)e1)->offset -
((const S3M_COMPONENT *)e2)->offset;
}
static DUMB_IT_SIGDATA *it_s3m_load_sigdata(DUMBFILE *f, int * cwtv)
{
DUMB_IT_SIGDATA *sigdata;
int flags, ffi;
int default_pan_present;
int master_volume;
unsigned char sample_pack[256];
S3M_COMPONENT *component;
int n_components = 0;
int n;
unsigned char *buffer;
sigdata = malloc(sizeof(*sigdata));
if (!sigdata) return NULL;
dumbfile_getnc(sigdata->name, 28, f);
sigdata->name[28] = 0;
n = dumbfile_getc(f);
if (n != 0x1A && n != 0) {
free(sigdata);
return NULL;
}
if (dumbfile_getc(f) != 16) {
free(sigdata);
return NULL;
}
dumbfile_skip(f, 2);
sigdata->song_message = NULL;
sigdata->order = NULL;
sigdata->instrument = NULL;
sigdata->sample = NULL;
sigdata->pattern = NULL;
sigdata->midi = NULL;
sigdata->checkpoint = NULL;
sigdata->n_orders = dumbfile_igetw(f);
sigdata->n_instruments = 0;
sigdata->n_samples = dumbfile_igetw(f);
sigdata->n_patterns = dumbfile_igetw(f);
if (dumbfile_error(f) || sigdata->n_orders <= 0 || sigdata->n_samples > 256 || sigdata->n_patterns > 256) {
_dumb_it_unload_sigdata(sigdata);
return NULL;
}
sigdata->order = malloc(sigdata->n_orders);
if (!sigdata->order) {
_dumb_it_unload_sigdata(sigdata);
return NULL;
}
if (sigdata->n_samples) {
sigdata->sample = malloc(sigdata->n_samples * sizeof(*sigdata->sample));
if (!sigdata->sample) {
_dumb_it_unload_sigdata(sigdata);
return NULL;
}
for (n = 0; n < sigdata->n_samples; n++)
sigdata->sample[n].data = NULL;
}
if (sigdata->n_patterns) {
sigdata->pattern = malloc(sigdata->n_patterns * sizeof(*sigdata->pattern));
if (!sigdata->pattern) {
_dumb_it_unload_sigdata(sigdata);
return NULL;
}
for (n = 0; n < sigdata->n_patterns; n++)
sigdata->pattern[n].entry = NULL;
}
flags = dumbfile_igetw(f);
*cwtv = dumbfile_igetw(f);
if (*cwtv == 0x1300) {
/** WARNING: volume slides on every frame */
}
ffi = dumbfile_igetw(f);
/** WARNING: which ones? */
sigdata->flags = IT_OLD_EFFECTS | IT_COMPATIBLE_GXX | IT_WAS_AN_S3M;
if (dumbfile_mgetl(f) != DUMB_ID('S','C','R','M')) {
_dumb_it_unload_sigdata(sigdata);
return NULL;
}
sigdata->global_volume = dumbfile_getc(f) << 1;
if ( !sigdata->global_volume || sigdata->global_volume > 128 ) sigdata->global_volume = 128;
sigdata->speed = dumbfile_getc(f);
if (sigdata->speed == 0) sigdata->speed = 6; // Should we? What about tempo?
sigdata->tempo = dumbfile_getc(f);
master_volume = dumbfile_getc(f); // 7 bits; +128 for stereo
//what do we do with master_volume? it's not the same as mixing volume...
sigdata->mixing_volume = 48;
if (master_volume & 128) sigdata->flags |= IT_STEREO;
/* Skip GUS Ultra Click Removal byte. */
dumbfile_getc(f);
default_pan_present = dumbfile_getc(f);
dumbfile_skip(f, 8);
/* Skip Special Custom Data Pointer. */
/** WARNING: investigate this? */
dumbfile_igetw(f);
sigdata->n_pchannels = 0;
/* Channel settings for 32 channels, 255=unused, +128=disabled */
{
int i;
for (i = 0; i < 32; i++) {
int c = dumbfile_getc(f);
if (!(c & (128 | 16))) { /* +128=disabled, +16=Adlib */
if (sigdata->n_pchannels < i + 1) sigdata->n_pchannels = i + 1;
sigdata->channel_volume[i] = 64;
sigdata->channel_pan[i] = c & 8 ? 12 : 3;
/** WARNING: ah, but it should be 7 for mono... */
} else {
/** WARNING: this could be improved if we support channel muting... */
sigdata->channel_volume[i] = 0;
sigdata->channel_pan[i] = 7;
}
}
}
/* Orders, byte each, length = sigdata->n_orders (should be even) */
dumbfile_getnc(sigdata->order, sigdata->n_orders, f);
sigdata->restart_position = 0;
component = malloc(768*sizeof(*component));
if (!component) {
_dumb_it_unload_sigdata(sigdata);
return NULL;
}
for (n = 0; n < sigdata->n_samples; n++) {
component[n_components].type = S3M_COMPONENT_SAMPLE;
component[n_components].n = n;
component[n_components].offset = dumbfile_igetw(f) << 4;
component[n_components].sampfirst = -1;
n_components++;
}
for (n = 0; n < sigdata->n_patterns; n++) {
int32 offset = dumbfile_igetw(f) << 4;
if (offset) {
component[n_components].type = S3M_COMPONENT_PATTERN;
component[n_components].n = n;
component[n_components].offset = offset;
component[n_components].sampfirst = -1;
n_components++;
} else {
/** WARNING: Empty 64-row pattern ... ? (this does happen!) */
sigdata->pattern[n].n_rows = 64;
sigdata->pattern[n].n_entries = 0;
}
}
qsort(component, n_components, sizeof(S3M_COMPONENT), &s3m_component_compare);
/* I found a really dumb S3M file that claimed to contain default pan
* data but didn't contain any. Programs would load it by reading part of
* the first instrument header, assuming the data to be default pan
* positions, and then rereading the instrument module. We cannot do this
* without obfuscating the file input model, so we insert an extra check
* here that we won't overrun the start of the first component.
*/
if (default_pan_present == 252 && component[0].offset >= dumbfile_pos(f) + 32) {
/* Channel default pan positions */
int i;
for (i = 0; i < 32; i++) {
int c = dumbfile_getc(f);
if (c & 32)
sigdata->channel_pan[i] = c & 15;
}
}
{
int i;
for (i = 0; i < 32; i++) {
sigdata->channel_pan[i] -= (sigdata->channel_pan[i] & 8) >> 3;
sigdata->channel_pan[i] = ((int)sigdata->channel_pan[i] << 5) / 7;
}
}
sigdata->pan_separation = 128;
if (dumbfile_error(f)) {
free(component);
_dumb_it_unload_sigdata(sigdata);
return NULL;
}
buffer = malloc(65536);
if (!buffer) {
free(component);
_dumb_it_unload_sigdata(sigdata);
return NULL;
}
/* Voila, I must deal with a very dumb S3M myself. This file refers to the same file offset twice
* for two different patterns. Solution: Eliminate it.
*/
for (n = 0; n < n_components; n++) {
if (component[n].type == S3M_COMPONENT_PATTERN) {
int m;
for (m = n + 1; m < n_components; m++) {
if (component[m].type == S3M_COMPONENT_PATTERN) {
if (component[n].offset == component[m].offset) {
int o, pattern;
pattern = component[m].n;
n_components--;
for (o = m; o < n_components; o++) {
component[o] = component[o + 1];
}
for (o = 0; o < sigdata->n_orders; o++) {
if (sigdata->order[o] == pattern) {
sigdata->order[o] = component[n].n;
}
}
sigdata->pattern[pattern].n_rows = 64;
sigdata->pattern[pattern].n_entries = 0;
m--;
} else
break;
}
}
}
}
for (n = 0; n < n_components; n++) {
int32 offset = 0;
int m;
#ifdef S3M_BROKEN_OVERLAPPED_SAMPLES
int last;
#endif
if (it_seek(f, component[n].offset)) {
free(buffer);
free(component);
_dumb_it_unload_sigdata(sigdata);
return NULL;
}
switch (component[n].type) {
case S3M_COMPONENT_PATTERN:
if (it_s3m_read_pattern(&sigdata->pattern[component[n].n], f, buffer, (n + 1 < n_components) ? (component[n+1].offset - component[n].offset) : 0)) {
free(buffer);
free(component);
_dumb_it_unload_sigdata(sigdata);
return NULL;
}
break;
case S3M_COMPONENT_SAMPLE:
if (it_s3m_read_sample_header(&sigdata->sample[component[n].n], &offset, &sample_pack[component[n].n], *cwtv, f)) {
free(buffer);
free(component);
_dumb_it_unload_sigdata(sigdata);
return NULL;
}
if (sigdata->sample[component[n].n].flags & IT_SAMPLE_EXISTS) {
short *sample;
for (m = n + 1; m < n_components; m++)
if (component[m].offset > offset)
break;
m--;
sample = &component[m].sampfirst;
while (*sample >= 0 && component[*sample].offset <= offset)
sample = &component[*sample].sampnext;
component[n].sampnext = *sample;
*sample = n;
component[n].offset = offset;
}
}
m = component[n].sampfirst;
#ifdef S3M_BROKEN_OVERLAPPED_SAMPLES
last = -1;
#endif
while (m >= 0) {
// XXX
#ifdef S3M_BROKEN_OVERLAPPED_SAMPLES
if ( last >= 0 ) {
if ( dumbfile_pos( f ) > component[m].offset ) {
IT_SAMPLE * s1 = &sigdata->sample[component[last].n];
IT_SAMPLE * s2 = &sigdata->sample[component[m].n];
if ( ( s1->flags | s2->flags ) & ( IT_SAMPLE_16BIT | IT_SAMPLE_STEREO ) ) {
free(buffer);
free(component);
_dumb_it_unload_sigdata(sigdata);
return NULL;
}
if ( component[m].offset >= component[last].offset &&
component[m].offset + s2->length <= component[last].offset + s1->length ) {
s2->left = malloc( s2->length );
if ( ! s2->left ) {
free(buffer);
free(component);
_dumb_it_unload_sigdata(sigdata);
return NULL;
}
memcpy( s2->left, ( const char * ) s1->left + component[m].offset - component[last].offset, s2->length );
last = -1;
}
}
} else last = 0;
if ( last >= 0 ) {
#endif
if (it_seek(f, component[m].offset)) {
free(buffer);
free(component);
_dumb_it_unload_sigdata(sigdata);
return NULL;
}
if (it_s3m_read_sample_data(&sigdata->sample[component[m].n], ffi, sample_pack[component[m].n], f)) {
free(buffer);
free(component);
_dumb_it_unload_sigdata(sigdata);
return NULL;
}
#ifdef S3M_BROKEN_OVERLAPPED_SAMPLES
last = m;
}
#endif
m = component[m].sampnext;
}
}
free(buffer);
free(component);
_dumb_it_fix_invalid_orders(sigdata);
return sigdata;
}
static char hexdigit(int in)
{
if (in < 10) return in + '0';
else return in + 'A' - 10;
}
DUH *DUMBEXPORT dumb_read_s3m_quick(DUMBFILE *f)
{
sigdata_t *sigdata;
int cwtv;
DUH_SIGTYPE_DESC *descptr = &_dumb_sigtype_it;
sigdata = it_s3m_load_sigdata(f, &cwtv);
if (!sigdata)
return NULL;
{
char version[8];
const char *tag[3][2];
tag[0][0] = "TITLE";
tag[0][1] = ((DUMB_IT_SIGDATA *)sigdata)->name;
tag[1][0] = "FORMAT";
tag[1][1] = "S3M";
tag[2][0] = "TRACKERVERSION";
version[0] = hexdigit((cwtv >> 8) & 15);
version[1] = '.';
version[2] = hexdigit((cwtv >> 4) & 15);
version[3] = hexdigit(cwtv & 15);
version[4] = 0;
tag[2][1] = (const char *) &version;
return make_duh(-1, 3, (const char *const (*)[2])tag, 1, &descptr, &sigdata);
}
}