/* load.c /* loads a soundtracker binary file * into memory, using a more convenient format */ /* $Author: Espie $ * $Date: 91/05/16 15:05:24 $ * $Revision: 1.34 $ * $Log: load.c,v $ * Revision 1.34 91/05/16 15:05:24 Espie * *** empty log message *** * * Revision 1.33 91/05/12 19:56:39 Espie * Shortened event structure. * * Revision 1.32 91/05/12 16:00:39 Espie * switched back to a char *. * * Revision 1.31 91/05/07 12:13:36 Espie * *** empty log message *** * * Revision 1.30 91/05/06 23:39:31 Espie * Now tries to load a WBArg. * Changes for accomodating new finetune and external find_note. * * Revision 1.29 91/05/06 15:15:06 Espie * Some more error checking, still not enough probably. * * Revision 1.28 91/05/05 19:06:41 Espie * *** empty log message *** * * Revision 1.27 91/05/05 03:59:37 Espie * Corrected other problems. Added some measure of error recovery * * * Revision 1.26 91/05/04 23:22:22 Espie * Corrected a very small bug in the handling of dummy samples. * * Revision 1.25 91/05/02 23:31:14 Espie * Now uses standard amigados filehandles... Might come in handy. * * Revision 1.24 91/05/02 11:20:48 Espie * Can now unload() empty songs... * * Revision 1.23 91/05/02 01:30:57 Espie * Correction of some small bugs, added a dummy sample for the player. * * Revision 1.22 91/04/30 16:52:09 Espie * Suppressed every IO. * Added channel tracks, should handle finetuned instruments. * * Revision 1.21 91/04/30 01:48:58 Espie * Corrected a BIG and stupid bug, there was two separate cleanups where * there should have been one, so that cleaning up an old module only happened * on exit of the program... * * Revision 1.20 91/04/30 00:35:15 Espie * Stable version III. * * Revision 1.19 91/04/30 00:23:34 Espie * New error checking, does still exit if not enough memory. * * Revision 1.18 91/04/29 23:53:56 Espie * Added unload_song(). Need a decent error recovery strategy right now. * * Revision 1.17 91/04/29 02:22:06 Espie * checkabort() added for user interruption, * since there are no longer critical sections. * * Revision 1.16 91/04/28 22:53:37 Espie * More leeway in the period computation. * * Revision 1.15 91/04/27 20:49:00 Espie * Small bug in find_note (order was reversed) * * Revision 1.14 91/04/27 16:45:56 Espie * Rounding errors for note period. * * Revision 1.13 91/04/24 15:27:19 Espie * Minor changes ??? * * Revision 1.12 91/04/23 21:30:32 Espie * Revised so that one song can be automatically cleaned out. * * Revision 1.11 91/04/21 20:06:07 Espie * The loader now precomputes notes. * It does not yet precompiles the effects (next step ?). * * Revision 1.10 91/04/21 12:11:43 Espie * Stable version, known as bunch II. * Also features ``right'' log description. * * Revision 1.9 91/04/21 11:14:34 * Bug in st files: repeat start is sometimes double what it should be. * * Revision 1.8 91/04/20 18:14:24 * * Revision 1.7 91/04/19 13:22:20 * * Revision 1.6 91/04/19 02:20:07 * loader without error checking for last sample. * added dummy sample for simplified check. * * Revision 1.5 91/04/18 02:25:41 * bunch I. * * Revision 1.4 91/04/17 18:50:42 * This is now a working module. Only the block translation is not yet debugged. * * Revision 1.3 91/04/14 22:02:32 * Playing version. This one knows how to play sample! * * Revision 1.2 91/04/14 18:06:52 * This version is able to load most soundtrackers files without apparent mistakes. * I still have got to figure out how the audio hardware works. * So this doesn't play any tune. * Apparently no bug as long as I don't try anything with the hardware. * * Revision 1.1 91/04/11 18:39:23 * Initial revision. * Not yet debugged or anything. * No interface for other modules. * */ #include #include #include #include #include #include #include #include #include #include "song.h" #include "proto.h" #include "periods.h" #include #include /* definitions for the binary st file format */ #define MAX_BLOCKS 128 #define SAMPLE_NAME 22 #define SONG_NAME 20 #define OLD_SAMPLES 15 #define NEW_SAMPLES 31 struct binary_sample_info { char sample_name[SAMPLE_NAME]; UWORD length; UBYTE finetune; UBYTE volume; UWORD rp_start; UWORD rp_length; }; struct binary_song_info { UBYTE length; UBYTE thing; UBYTE block_number[MAX_BLOCKS]; }; struct binary_event { UBYTE detail[4]; }; struct interleaved { struct binary_event track[NUMBER_TRACKS]; }; struct binary_block { struct interleaved data[BLOCK_LENGTH]; }; /* a song is laid out like this: char song_name[SONG_NAME]; struct sample_info samples[OLD_SAMPLES or NEW_SAMPLES]; struct song_info song; (char sig[4] == "M.K." for new soundtrackers, with NEW_SAMPLES instruments) struct block blocks[number of blocks]; UWORD sample0[length sample0]; UWORD sample1[length sample1]; ... UWORD samplen[length samplen]; where n=OLD_SAMPLES or NEW_SAMPLES. */ struct noisetracker_header { char song_name[SONG_NAME]; struct binary_sample_info sample_info[NEW_SAMPLES]; struct binary_song_info song_info; ULONG sig; }; struct old_st_header { char song_name[SONG_NAME]; struct binary_sample_info sample_info[OLD_SAMPLES]; struct binary_song_info song_info; /* no sig */ }; struct other_stuff { ULONG sig; }; #define MK_SIG(a, b, c, d) ((a<<24) | (b<< 16) | (c<<8) | d) union header { struct noisetracker_header nt; struct old_st_header st; struct other_stuff other; } header; struct sample_info dummy = { "dummy sample", 0, 0, 0, 0, 0, 0, 0 }; int last_error; /* cstring(buffer, maxlength) * converts a soundtracker string into a decent cstring */ char *cstring(CLEAN clear, char buffer[], int maxlength) { char *st; int i; for (i = 0; i < maxlength; i++) if (buffer[i] == 0) break; st = malloc(i+1); if (st) ToCleanL(clear, free, st); else return NULL; st[i] = 0; return strncpy(st, buffer, i); } struct sample_info *extract_sample_info(CLEAN clear, struct binary_sample_info *i) { struct sample_info *new; check_abort(); if (i->length <= 1) return &dummy; new = malloc(sizeof(struct sample_info)); if (new) ToCleanL(clear, free, new); else return NULL; new->length = i->length; new->finetune = normalize_finetune(i->finetune); new->volume = i->volume; if (i->rp_start + i->rp_length - 1 > i->length) { i->rp_start >>=1; } new->rp_offset = i->rp_start; new->rp_length = i->rp_length; new->name = cstring(clear, i->sample_name, SAMPLE_NAME); if (new->finetune < 0) { last_error = NOT_A_MOD; return NULL; } else return new; } int find_max(UBYTE block_number[], int max_index) { int current, i; for (current = -1, i = 0; i < max_index; i++) if (block_number[i] > current) current = block_number[i]; /* a negative return indicates a problem */ if (current > 127) return (-1); return current; } struct block **map_blocks(CLEAN clear, UBYTE block_number[], int length, struct block array[]) { int i; struct block **pters; pters = malloc(length * sizeof(struct block *)); if (pters) ToCleanL(clear, free, pters); else return NULL; for (i = 0; i < length; i++) pters[i] = array+block_number[i]; return pters; } struct block *allocate_blocks(CLEAN clear, int number) { struct block *new; new = malloc(number * sizeof(struct block)); if (new) ToCleanL(clear, free, new); else return NULL; return new; } struct song_info *extract_song_info(CLEAN clear, struct binary_song_info *b) { struct song_info *new; new = malloc(sizeof(struct song_info)); if (new) ToCleanL(clear, free, new); else return NULL; new->length = b->length; new->thing = b->thing; new->total = find_max(b->block_number, 128)+1; if (new->total < 0) return NULL; new->physical = allocate_blocks(clear, new->total); if (!new->physical) return NULL; new->pblocks = map_blocks(clear, b->block_number, new->length, new->physical); if (!new->pblocks) return NULL; return new; } /* we will leave that alone for the time being */ struct sample_info *instr_channel[NUMBER_TRACKS]; struct sample_info **sample_array; int current_max; void parse_event(struct binary_event *b, struct event *e, int tn) { UWORD period; e->sample_number = (b->detail[0]&~15) | (b->detail[2]>>4); e->sample_number &= 31; if (e->sample_number) { instr_channel[tn] = sample_array[e->sample_number]; if (e->sample_number > current_max) current_max = e->sample_number; } period = ((b->detail[0]&15)<<8) | b->detail[1]; e->note = find_note(period, instr_channel[tn]->finetune); if (e->note > FINE_PERIOD) last_error = NOTE_PROBLEM; e->effect = b->detail[2]&15; e->parameters = b->detail[3]; } void parse_block(struct binary_block *b, struct block *d) { int i, j; for (i = 0; i < BLOCK_LENGTH; i++) for (j = 0; j < NUMBER_TRACKS; j++) parse_event(&b->data[i].track[j], &d->e[j][i], j); } int read_blocks(CLEAN clear, BPTR f, int total, struct block *first) { struct binary_block b; int i; current_max = 0; for (i = 0; i < NUMBER_TRACKS; i++) instr_channel[i] = sample_array[0]; for (i = 0; i < total; i++) { if (Read(f, &b, sizeof(b)) != sizeof(b)) return 0; check_abort(); parse_block(&b, first+i); } return current_max; } BOOL allocate_samples(CLEAN clear, struct song *s, int max_sn) { int i; int length; UWORD *p, *empty; /* so we don't have to multiply everything by 2 */ /* compute the total length */ for (length = 1, i = 1 ; i <= max_sn; i++) { if (s->samples[i] != &dummy) length += s->samples[i]->length; } /* allocate corresponding chip memory */ p = AllocMem(2 * length, MEMF_CHIP|MEMF_CLEAR); if (p) ToClean2L(clear, FreeMem, p, 2*length); else { last_error = OUT_OF_CHIP; return NULL; } empty = p++; dummy.start = empty; dummy.length = 1; dummy.rp_start = empty; dummy.rp_length = 1; /* compute addresses information */ for (i = 1; i <= max_sn; i++) if (s->samples[i] != &dummy) { s->samples[i]->start = p; if (s->samples[i]->rp_length == 1) s->samples[i]->rp_start = empty; else s->samples[i]->rp_start = p + s->samples[i]->rp_offset; p+= s->samples[i]->length; } } BOOL read_sample(CLEAN clear, BPTR f, struct binary_sample_info *b, struct sample_info *s) { check_abort(); if (Read(f, s->start, b->length*2) != b->length*2 && b->length > 1) { last_error = MISSING_SAMPLE; return TRUE; } else return TRUE; } struct song *read_song(BPTR f) { struct song *s; int i, sn, max_sn; CLEAN clear; last_error = OUT_OF_MEMORY; clear = AllocClean(NIL); if (!clear) mayPanic("Couldn't allocate cleanup"); s = malloc(sizeof(struct song)); if (s) { s->clear = clear; ToCleanL(clear, free, s); } else { last_error = OUT_OF_MEMORY; return NULL; } if (Read(f, &header, sizeof(union header)) != sizeof(union header)) { last_error = NOT_A_MOD; return unload_song(s); } /* fast recognize other formats */ if (header.other.sig == MK_SIG('M','M','D','0') || header.other.sig == MK_SIG('S','M','O','D') || header.other.sig == MK_SIG('F','C','1','4')) { last_error = UNSUPPORTED; return unload_song(s); } if (header.nt.sig == MK_SIG('M','.','K','.')) { sn = NEW_SAMPLES; s->info = extract_song_info(clear, &header.nt.song_info); } else { sn = OLD_SAMPLES; s->info = extract_song_info(clear, &header.st.song_info); Seek(f, sizeof(struct old_st_header), OFFSET_BEGINNING); } if (!s->info) return unload_song(s); s->title = cstring(clear, header.nt.song_name, SONG_NAME); if (!s->title) return unload_song(s); /* so that we don't allocate memory for dummy samples */ dummy.length = 0; for (i = 0; i < NUMBER_SAMPLES; i++) s->samples[i] = &dummy; for (i = 0; i < sn; i++) { s->samples[i+1] = extract_sample_info(clear, &header.nt.sample_info[i]); if (!s->samples[i+1]) return unload_song(s); } sample_array = s->samples; max_sn = read_blocks(clear, f, s->info->total, s->info->physical); if (!max_sn) return unload_song(s); if (!allocate_samples(clear, s, max_sn)) return unload_song(s); for (i = 0; i < max_sn; i++) if (!read_sample(clear, f, &header.nt.sample_info[i], s->samples[i+1])) return unload_song(s); return s; } int load_error(void) { return last_error; } struct song *unload_song(struct song *s) { if (s) CleanUp(s->clear); return NULL; } struct song *load_song(char *arg) { BPTR f; CLEAN closefile; struct song *s; static char message[200]; closefile = AllocClean(NIL); if (!arg) return NULL; /* check the precise length !!! */ sprintf(message, "Loading %s...", arg); temporary_title(message); ToClean0L(closefile, restore_title); f = Open(arg, MODE_OLDFILE); if (f) { ToCleanL(closefile, Close, f); s = read_song(f); } else s = NULL; CleanUp(closefile); return s; }