int fmt_aiff_export_body(disko_t *fp, const uint8_t *data, size_t length) { struct aiff_writedata *awd = fp->userdata; if (length % awd->bps) { log_appendf(4, "AIFF export: received uneven length"); return DW_ERROR; } awd->numbytes += length; if (awd->swap) { const int16_t *ptr = (const int16_t *) data; uint16_t v; length /= 2; while (length--) { v = *ptr; v = bswapBE16(v); disko_write(fp, &v, 2); ptr++; } } else { disko_write(fp, data, length); } return DW_OK; }
static int _read_iff(dmoz_file_t *file, song_sample_t *smp, const uint8_t *data, size_t length) { chunk_t chunk; size_t pos = 0; chunk_t vhdr, body, name, comm, auth, anno, ssnd; // butt if (!iff_chunk_read(&chunk, data, length, &pos)) return 0; if (chunk.id != ID_FORM) return 0; // jump "into" the FORM chunk // if (pos < length), there's more data after the FORM chunk -- but I don't care about this scenario pos = 0; length = MIN(length, chunk.size); data = chunk.data->FORM.data; /* the header is already byteswapped, but anything in 'chunk' will need to be swapped as needed because the structure is a const pointing into the data itself */ switch (bswapBE32(chunk.data->FORM.filetype)) { case ID_8SVX: // shut up, gcc ZEROIZE(vhdr); ZEROIZE(body); ZEROIZE(name); ZEROIZE(auth); ZEROIZE(anno); while (iff_chunk_read(&chunk, data, length, &pos)) { switch (chunk.id) { case ID_VHDR: vhdr = chunk; break; case ID_BODY: body = chunk; break; case ID_NAME: name = chunk; break; case ID_AUTH: auth = chunk; break; case ID_ANNO: anno = chunk; break; default: break; } } if (!(vhdr.id && body.id)) return 0; if (vhdr.data->VHDR.compression) { log_appendf(4, "error: compressed 8SVX files are unsupported"); return 0; } if (vhdr.data->VHDR.num_octaves != 1) { log_appendf(4, "warning: 8SVX file contains %d octaves", vhdr.data->VHDR.num_octaves); } if (file) { file->description = "8SVX sample"; file->type = TYPE_SAMPLE_PLAIN; } if (!name.id) name = auth; if (!name.id) name = anno; if (name.id) { if (file) { file->title = calloc(1, name.size + 1); memcpy(file->title, name.data->bytes, name.size); file->title[name.size] = '\0'; } if (smp) { int len = MIN(25, name.size); memcpy(smp->name, name.data->bytes, len); smp->name[len] = 0; } } if (smp) { smp->c5speed = bswapBE16(vhdr.data->VHDR.smp_per_sec); smp->length = body.size; csf_read_sample(smp, SF_BE | SF_PCMS | SF_8 | SF_M, body.data->bytes, body.size); smp->volume = 64*4; smp->global_volume = 64; // this is done kinda weird smp->loop_end = bswapBE32(vhdr.data->VHDR.smp_highoct_repeat); if (smp->loop_end) { smp->loop_start = bswapBE32(vhdr.data->VHDR.smp_highoct_1shot); smp->loop_end += smp->loop_start; if (smp->loop_start > smp->length) smp->loop_start = 0; if (smp->loop_end > smp->length) smp->loop_end = smp->length; if (smp->loop_start + 2 < smp->loop_end) smp->flags |= CHN_LOOP; } // TODO vhdr.data->VHDR.volume ? } return 1; case ID_AIFF: ZEROIZE(comm); ZEROIZE(ssnd); ZEROIZE(name); ZEROIZE(auth); ZEROIZE(anno); while (iff_chunk_read(&chunk, data, length, &pos)) { switch (chunk.id) { case ID_COMM: comm = chunk; break; case ID_SSND: ssnd = chunk; break; case ID_NAME: name = chunk; break; default: break; } } if (!(comm.id && ssnd.id)) return 0; if (file) { file->description = "Audio IFF sample"; file->type = TYPE_SAMPLE_PLAIN; } if (!name.id) name = auth; if (!name.id) name = anno; if (name.id) { if (file) { file->title = calloc(1, name.size + 1); memcpy(file->title, name.data->bytes, name.size); file->title[name.size] = '\0'; } if (smp) { int len = MIN(25, name.size); memcpy(smp->name, name.data->bytes, len); smp->name[len] = 0; } } /* TODO loop points */ if (smp) { uint32_t flags = SF_BE | SF_PCMS; switch (bswapBE16(comm.data->COMM.num_channels)) { default: log_appendf(4, "warning: multichannel AIFF is unsupported"); case 1: flags |= SF_M; break; case 2: flags |= SF_SI; break; } switch ((bswapBE16(comm.data->COMM.sample_size) + 7) & ~7) { default: log_appendf(4, "warning: AIFF has unsupported bit-width"); case 8: flags |= SF_8; break; case 16: flags |= SF_16; break; } // TODO: data checking; make sure sample count and byte size agree // (and if not, cut to shorter of the two) smp->c5speed = ConvertFromIeeeExtended(comm.data->COMM.sample_rate); smp->length = bswapBE32(comm.data->COMM.num_frames); smp->volume = 64*4; smp->global_volume = 64; // the audio data starts 8 bytes into the chunk // (don't care about the block alignment stuff) csf_read_sample(smp, flags, ssnd.data->bytes + 8, ssnd.size - 8); } return 1; } return 0; }
static int aiff_header(disko_t *fp, int bits, int channels, int rate, const char *name, size_t length, struct aiff_writedata *awd /* out */) { int16_t s; uint32_t ul; int tlen, bps = 1; uint8_t b[10]; bps *= ((bits + 7) / 8); /* note: channel multiply is done below -- need single-channel value for the COMM chunk */ /* write a very large size for now */ disko_write(fp, "FORM\377\377\377\377AIFF", 12); if (name && *name) { disko_write(fp, "NAME", 4); tlen = strlen(name); ul = (tlen + 1) & ~1; /* must be even */ ul = bswapBE32(ul); disko_write(fp, &ul, 4); disko_write(fp, name, tlen); if (tlen & 1) disko_putc(fp, '\0'); } /* Common Chunk The Common Chunk describes fundamental parameters of the sampled sound. typedef struct { ID ckID; // 'COMM' long ckSize; // 18 short numChannels; unsigned long numSampleFrames; short sampleSize; extended sampleRate; } CommonChunk; */ disko_write(fp, "COMM", 4); ul = bswapBE32(18); /* chunk size -- won't change */ disko_write(fp, &ul, 4); s = bswapBE16(channels); disko_write(fp, &s, 2); if (awd) awd->comm_frames = disko_tell(fp); ul = bswapBE32(length); /* num sample frames */ disko_write(fp, &ul, 4); s = bswapBE16(bits); disko_write(fp, &s, 2); ConvertToIeeeExtended(rate, b); disko_write(fp, b, 10); /* NOW do this (sample size in AIFF is indicated per channel, not per frame) */ bps *= channels; /* == number of bytes per (stereo) sample */ /* Sound Data Chunk The Sound Data Chunk contains the actual sample frames. typedef struct { ID ckID; // 'SSND' long ckSize; // data size in bytes, *PLUS EIGHT* (for offset and blockSize) unsigned long offset; // just set this to 0... unsigned long blockSize; // likewise unsigned char soundData[]; } SoundDataChunk; */ disko_write(fp, "SSND", 4); if (awd) awd->ssnd_size = disko_tell(fp); ul = bswapBE32(length * bps + 8); disko_write(fp, &ul, 4); ul = bswapBE32(0); disko_write(fp, &ul, 4); disko_write(fp, &ul, 4); return bps; }
int fmt_mod_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { uint8_t tag[4]; int n, npat, pat, chan, nchan, nord; song_note_t *note; uint16_t tmp; int startrekker = 0; int test_wow = 0; int mk = 0; int maybe_st3 = 0; int maybe_ft2 = 0; uint8_t restart; long samplesize = 0; const char *tid = NULL; /* check the tag (and set the number of channels) -- this is ugly, so don't look */ slurp_seek(fp, 1080, SEEK_SET); slurp_read(fp, tag, 4); if (!memcmp(tag, "M.K.", 4)) { /* M.K. = Protracker etc., or Mod's Grave (*.wow) */ nchan = 4; test_wow = 1; mk = 1; maybe_ft2 = 1; tid = "Amiga-NewTracker"; } else if (!memcmp(tag, "M!K!", 4)) { nchan = 4; tid = "Amiga-ProTracker"; } else if (!memcmp(tag, "M&K!", 4) || !memcmp(tag, "N.T.", 4)) { nchan = 4; tid = "Amiga-NoiseTracker"; // or so the word on the street is; I don't have any of these } else if ((!memcmp(tag, "FLT", 3) || !memcmp(tag, "EXO", 3)) && (tag[3] == '4' || tag[3] == '8')) { // Hopefully EXO8 is stored the same way as FLT8 nchan = tag[3] - '0'; startrekker = (nchan == 8); tid = "%d Channel Startrekker"; //log_appendf(4, " Warning: Startrekker AM synth is not supported"); } else if (!memcmp(tag, "FEST", 4)) { // the mysterious mod.jobbig nchan = 4; tid = "4 Channel Startrekker (?)"; } else if (!memcmp(tag, "OCTA", 4)) { nchan = 8; tid = "Amiga Oktalyzer"; // IT just identifies this as "8 Channel MOD" } else if (!memcmp(tag, "CD81", 4)) { nchan = 8; tid = "8 Channel Falcon"; // Atari Oktalyser } else if (tag[0] > '0' && tag[0] <= '9' && !memcmp(tag + 1, "CHN", 3)) { /* nCHN = Fast Tracker (if n is even) or TakeTracker (if n = 5, 7, or 9) */ nchan = tag[0] - '0'; if (nchan == 5 || nchan == 7 || nchan == 9) { tid = "%d Channel TakeTracker"; } else { if (!(nchan & 1)) maybe_ft2 = 1; tid = "%d Channel MOD"; // generic } maybe_st3 = 1; } else if (tag[0] > '0' && tag[0] <= '9' && tag[1] >= '0' && tag[1] <= '9' && tag[2] == 'C' && (tag[3] == 'H' || tag[3] == 'N')) { /* nnCH = Fast Tracker (if n is even and <= 32) or TakeTracker (if n = 11, 13, 15) * Not sure what the nnCN variant is. */ nchan = 10 * (tag[0] - '0') + (tag[1] - '0'); if (nchan == 11 || nchan == 13 || nchan == 15) { tid = "%d Channel TakeTracker"; } else { if ((nchan & 1) == 0 && nchan <= 32 && tag[3] == 'H') maybe_ft2 = 1; tid = "%d Channel MOD"; // generic } if (tag[3] == 'H') maybe_st3 = 1; } else if (!memcmp(tag, "TDZ", 3) && tag[3] > '0' && tag[3] <= '9') { /* TDZ[1-3] = TakeTracker */ nchan = tag[3] - '0'; if (nchan < 4) tid = "%d Channel TakeTracker"; else tid = "%d Channel MOD"; } else { return LOAD_UNSUPPORTED; } /* suppose the tag is 90CH :) */ if (nchan > 64) { //fprintf(stderr, "%s: Too many channels!\n", filename); return LOAD_FORMAT_ERROR; } /* read the title */ slurp_rewind(fp); slurp_read(fp, song->title, 20); song->title[20] = 0; /* sample headers */ for (n = 1; n < 32; n++) { slurp_read(fp, song->samples[n].name, 22); song->samples[n].name[22] = 0; slurp_read(fp, &tmp, 2); song->samples[n].length = bswapBE16(tmp) * 2; /* this is only necessary for the wow test... */ samplesize += song->samples[n].length; song->samples[n].c5speed = MOD_FINETUNE(slurp_getc(fp)); song->samples[n].volume = slurp_getc(fp); if (song->samples[n].volume > 64) song->samples[n].volume = 64; if (!song->samples[n].length && song->samples[n].volume) maybe_ft2 = 0; song->samples[n].volume *= 4; //mphack song->samples[n].global_volume = 64; slurp_read(fp, &tmp, 2); song->samples[n].loop_start = bswapBE16(tmp) * 2; slurp_read(fp, &tmp, 2); tmp = bswapBE16(tmp) * 2; if (tmp > 2) song->samples[n].flags |= CHN_LOOP; else if (tmp == 0) maybe_st3 = 0; else if (!song->samples[n].length) maybe_ft2 = 0; song->samples[n].loop_end = song->samples[n].loop_start + tmp; song->samples[n].vib_type = 0; song->samples[n].vib_rate = 0; song->samples[n].vib_depth = 0; song->samples[n].vib_speed = 0; } /* pattern/order stuff */ nord = slurp_getc(fp); restart = slurp_getc(fp); slurp_read(fp, song->orderlist, 128); npat = 0; if (startrekker) { /* from mikmod: if the file says FLT8, but the orderlist has odd numbers, it's probably really an FLT4 */ for (n = 0; n < 128; n++) { if (song->orderlist[n] & 1) { startrekker = 0; nchan = 4; break; } } } if (startrekker) { for (n = 0; n < 128; n++) song->orderlist[n] >>= 1; } for (n = 0; n < 128; n++) { if (song->orderlist[n] >= MAX_PATTERNS) song->orderlist[n] = ORDER_SKIP; else if (song->orderlist[n] > npat) npat = song->orderlist[n]; } /* set all the extra orders to the end-of-song marker */ memset(song->orderlist + nord, ORDER_LAST, MAX_ORDERS - nord); if (restart == 0x7f && maybe_st3) tid = "Scream Tracker 3?"; else if (restart == 0x7f && mk) tid = "%d Channel ProTracker"; else if (restart <= npat && maybe_ft2) tid = "%d Channel FastTracker"; else if (restart == npat && mk) tid = "%d Channel Soundtracker"; /* hey, is this a wow file? */ if (test_wow) { slurp_seek(fp, 0, SEEK_END); if (slurp_tell(fp) == 2048 * npat + samplesize + 3132) { nchan = 8; tid = "Mod's Grave WOW"; } } // http://llvm.org/viewvc/llvm-project?view=rev&revision=91888 sprintf(song->tracker_id, tid ? tid : "%d Channel MOD", nchan); slurp_seek(fp, 1084, SEEK_SET); /* pattern data */ if (startrekker) { for (pat = 0; pat <= npat; pat++) { note = song->patterns[pat] = csf_allocate_pattern(64); song->pattern_size[pat] = song->pattern_alloc_size[pat] = 64; for (n = 0; n < 64; n++, note += 60) { for (chan = 0; chan < 4; chan++, note++) { uint8_t p[4]; slurp_read(fp, p, 4); mod_import_note(p, note); csf_import_mod_effect(note, 0); } } note = song->patterns[pat] + 4; for (n = 0; n < 64; n++, note += 60) { for (chan = 0; chan < 4; chan++, note++) { uint8_t p[4]; slurp_read(fp, p, 4); mod_import_note(p, note); csf_import_mod_effect(note, 0); } } } } else { for (pat = 0; pat <= npat; pat++) { note = song->patterns[pat] = csf_allocate_pattern(64); song->pattern_size[pat] = song->pattern_alloc_size[pat] = 64; for (n = 0; n < 64; n++, note += 64 - nchan) { for (chan = 0; chan < nchan; chan++, note++) { uint8_t p[4]; slurp_read(fp, p, 4); mod_import_note(p, note); csf_import_mod_effect(note, 0); } } } } if (restart < npat) csf_insert_restart_pos(song, restart); /* sample data */ if (!(lflags & LOAD_NOSAMPLES)) { for (n = 1; n < 32; n++) { uint32_t ssize; if (song->samples[n].length == 0) continue; ssize = csf_read_sample(song->samples + n, SF_8 | SF_M | SF_LE | SF_PCMS, fp->data + fp->pos, fp->length - fp->pos); slurp_seek(fp, ssize, SEEK_CUR); } } /* set some other header info that's always the same for .mod files */ song->flags = (SONG_ITOLDEFFECTS | SONG_COMPATGXX); for (n = 0; n < nchan; n++) song->channels[n].panning = PROTRACKER_PANNING(n); for (; n < MAX_CHANNELS; n++) song->channels[n].flags = CHN_MUTE; song->pan_separation = 64; // if (slurp_error(fp)) { // return LOAD_FILE_ERROR; // } /* done! */ return LOAD_SUCCESS; }