/* ______ ___ ___ * /\ _ \ /\_ \ /\_ \ * \ \ \L\ \\//\ \ \//\ \ __ __ _ __ ___ * \ \ __ \ \ \ \ \ \ \ /'__`\ /'_ `\/\`'__\/ __`\ * \ \ \/\ \ \_\ \_ \_\ \_/\ __//\ \L\ \ \ \//\ \L\ \ * \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/ * \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/ * /\____/ * \_/__/ * * MIDI patch (for DIGMID) grabbing utility for the Allegro library. * * By Shawn Hargreaves. * * SoundFont loader based on code provided by George Foot. * * See readme.txt for copyright information. */ #define ALLEGRO_USE_CONSOLE #include #include #include #include #include "allegro.h" #include "allegro/internal/aintern.h" #include "datedit.h" static DATAFILE *datafile = NULL; static int err = 0; static int opt_compression = 0; static int opt_overwrite = FALSE; static int opt_8bit = FALSE; static int opt_verbose = FALSE; static int opt_veryverbose = FALSE; static int opt_bank = 0; static int opt_drum_bank = 0; static char *opt_datafile = NULL; static char *opt_configfile = NULL; static char *opt_soundfont = NULL; #define MAX_FILES 256 static char *opt_namelist[MAX_FILES]; static int opt_numnames = 0; static int need_patches[128], need_drums[128]; static char *patch_names[] = { "Acoustic Grand", "Bright Acoustic", "Electric Grand", "Honky-Tonk", "Electric Piano 1", "Electric Piano 2", "Harpsichord", "Clav", "Celesta", "Glockenspiel", "Music Box", "Vibraphone", "Marimba", "Xylophone", "Tubular Bells", "Dulcimer", "Drawbar Organ", "Percussive Organ", "Rock Organ", "Church Organ", "Reed Organ", "Accoridan", "Harmonica", "Tango Accordian", "Acoustic Guitar (nylon)", "Acoustic Guitar (steel)", "Electric Guitar (jazz)", "Electric Guitar (clean)", "Electric Guitar (muted)", "Overdriven Guitar", "Distortion Guitar", "Guitar Harmonics", "Acoustic Bass", "Electric Bass (finger)", "Electric Bass (pick)", "Fretless Bass", "Slap Bass 1", "Slap Bass 2", "Synth Bass 1", "Synth Bass 2", "Violin", "Viola", "Cello", "Contrabass", "Tremolo Strings", "Pizzicato Strings", "Orchestral Strings", "Timpani", "String Ensemble 1", "String Ensemble 2", "SynthStrings 1", "SynthStrings 2", "Choir Aahs", "Voice Oohs", "Synth Voice", "Orchestra Hit", "Trumpet", "Trombone", "Tuba", "Muted Trumpet", "French Horn", "Brass Section", "SynthBrass 1", "SynthBrass 2", "Soprano Sax", "Alto Sax", "Tenor Sax", "Baritone Sax", "Oboe", "English Horn", "Bassoon", "Clarinet", "Piccolo", "Flute", "Recorder", "Pan Flute", "Blown Bottle", "Skakuhachi", "Whistle", "Ocarina", "Lead 1 (square)", "Lead 2 (sawtooth)", "Lead 3 (calliope)", "Lead 4 (chiff)", "Lead 5 (charang)", "Lead 6 (voice)", "Lead 7 (fifths)", "Lead 8 (bass+lead)", "Pad 1 (new age)", "Pad 2 (warm)", "Pad 3 (polysynth)", "Pad 4 (choir)", "Pad 5 (bowed)", "Pad 6 (metallic)", "Pad 7 (halo)", "Pad 8 (sweep)", "FX 1 (rain)", "FX 2 (soundtrack)", "FX 3 (crystal)", "FX 4 (atmosphere)", "FX 5 (brightness)", "FX 6 (goblins)", "FX 7 (echoes)", "FX 8 (sci-fi)", "Sitar", "Banjo", "Shamisen", "Koto", "Kalimba", "Bagpipe", "Fiddle", "Shanai", "Tinkle Bell", "Agogo", "Steel Drums", "Woodblock", "Taiko Drum", "Melodic Tom", "Synth Drum", "Reverse Cymbal", "Guitar Fret Noise", "Breath Noise", "Seashore", "Bird Tweet", "Telephone ring", "Helicopter", "Applause", "Gunshot" }; static char *drum_names[] = { "Acoustic Bass Drum", "Bass Drum 1", "Side Stick", "Acoustic Snare", "Hand Clap", "Electric Snare", "Low Floor Tom", "Closed Hi-Hat", "High Floor Tom", "Pedal Hi-Hat", "Low Tom", "Open Hi-Hat", "Low-Mid Tom", "Hi-Mid Tom", "Crash Cymbal 1", "High Tom", "Ride Cymbal 1", "Chinese Cymbal", "Ride Bell", "Tambourine", "Splash Cymbal", "Cowbell", "Crash Cymbal 2", "Vibraslap", "Ride Cymbal 2", "Hi Bongo", "Low Bongo", "Mute Hi Conga", "Open Hi Conga", "Low Conga", "High Timbale", "Low Timbale", "High Agogo", "Low Agogo", "Cabasa", "Maracas", "Short Whistle", "Long Whistle", "Short Guiro", "Long Guiro", "Claves", "Hi Wood Block", "Low Wood Block", "Mute Cuica", "Open Cuica", "Mute Triangle", "Open Triangle" }; static char *short_patch_names[] = { "acpiano", "brpiano", "synpiano", "honktonk", "epiano1", "epiano2", "hrpsi", "clavi", "celeste", "glock", "musicbox", "vibes", "marimba", "xylo", "tubebell", "dulcimer", "drawbar", "percorg", "rockorg", "church", "reedorg", "tango", "harmonca", "concert", "nylongtr", "steelgtr", "jazzgtr", "cleangtr", "mutegtr", "drivegtr", "distgtr", "harmgtr", "acbass", "fngrbass", "pickbass", "fretless", "slapbas1", "slapbas2", "synbass1", "synbass2", "violin", "viola", "cello", "contra", "tremstr", "pizz", "harp", "timp", "strens1", "strens2", "synstr1", "synstr2", "choirah", "choiroh", "synchoir", "orchhit", "trumpet", "trombone", "tuba", "mutetrpt", "horn", "brassens", "synbras1", "synbras2", "sopsax", "altosax", "tenorsax", "barisax", "oboe", "englhorn", "bassoon", "clarinet", "piccolo", "flute", "recorder", "panflute", "bottle", "shaku", "whistle", "ocarina", "square", "saw", "calliope", "chiff", "charang", "voice", "fifths", "basslead", "newage", "warm", "polysyn", "choir", "bowed", "metal", "halo", "sweep", "rain", "sndtrack", "crystal", "atmos", "bright", "goblins", "echoes", "scifi", "sitar", "banjo", "shamisen", "koto", "kalimba", "bagpipes", "fiddle", "shannai", "tinkle", "agogo", "steel", "woodblck", "taiko", "toms", "syndrum", "reverse", "fret", "blow", "seashore", "birds", "phone", "copter", "applause", "gunshot" }; static char *short_drum_names[] = { "kick1", "kick2", "rimshot", "snare1", "handclap", "snare2", "tomflrlo", "hihatcl", "tomflrhi", "hihatpd", "tomlow", "hihatop", "tommidlo", "tommidhi", "crash1", "tomhi", "ride1", "chinese", "ridebell", "tambrine", "splash", "cowbell", "crash2", "vibrslap", "ride2", "bongohi", "bongolo", "congamt", "congahi", "congalo", "timbalhi", "timballo", "agogohi", "agogolo", "cabasa", "maracas", "whistle1", "whistle2", "guiro1", "guiro2", "claves", "woodhi", "woodlo", "cuica1", "cuica2", "triangl1", "triangl2" }; /* display help on the command syntax */ static void usage(void) { printf("\nPatch grabber for the Allegro DIGMID driver " ALLEGRO_VERSION_STR ", " ALLEGRO_PLATFORM_STR "\n"); printf("By Shawn Hargreaves, " ALLEGRO_DATE_STR "\n\n"); printf("Usage: pat2dat [options] filename.dat [samplelocation] [tunes.mid]\n\n"); printf("Supported input formats:\n"); printf("\tGUS patch: should point to the default.cfg index file\n"); printf("\tSoundFont: should point to a SoundFont .sf2 file\n\n"); printf("Options:\n"); printf("\t'-b x' grab specified instrument bank\n"); printf("\t'-c' compress objects\n"); printf("\t'-d x' grab specified percussion kit\n"); printf("\t'-o' overwrite existing datafile\n"); printf("\t'-v' select verbose mode\n"); printf("\t'-vv' select very verbose mode\n"); printf("\t'-8' reduce sample data to 8 bits\n"); } /* unused callback for datedit.c */ int datedit_ask(AL_CONST char *fmt, ...) { return 0; } /* unused callback for datedit.c */ int datedit_select(AL_CONST char *(*list_getter)(int index, int *list_size), AL_CONST char *fmt, ...) { return 0; } /* callback for outputting messages */ void datedit_msg(AL_CONST char *fmt, ...) { va_list args; char buf[1024]; va_start(args, fmt); vsprintf(buf, fmt, args); va_end(args); printf("%s\n", buf); } /* callback for starting a 2-part message output */ void datedit_startmsg(AL_CONST char *fmt, ...) { va_list args; char buf[1024]; va_start(args, fmt); vsprintf(buf, fmt, args); va_end(args); printf("%s", buf); fflush(stdout); } /* callback for ending a 2-part message output */ void datedit_endmsg(AL_CONST char *fmt, ...) { va_list args; char buf[1024]; va_start(args, fmt); vsprintf(buf, fmt, args); va_end(args); printf("%s\n", buf); } /* callback for printing errors */ void datedit_error(AL_CONST char *fmt, ...) { va_list args; char buf[1024]; va_start(args, fmt); vsprintf(buf, fmt, args); va_end(args); fprintf(stderr, "%s\n", buf); err = 1; } /* deal with the strange variable length MIDI number format */ static unsigned long parse_var_len(unsigned char **data) { unsigned long val = **data & 0x7F; while (**data & 0x80) { (*data)++; val <<= 7; val += (**data & 0x7F); } (*data)++; return val; } /* scans through a MIDI file to find which patches it needs */ static void scan_patches(MIDI *midi, char *patches, char *drums) { unsigned char *p, *end; unsigned char running_status, event; long l; int c; for (c=0; c<128; c++) /* initialise to unused */ patches[c] = drums[c] = FALSE; for (c=0; ctrack[c].data; end = p + midi->track[c].len; running_status = 0; while (p < end) { /* work through data stream */ event = *p; if (event & 0x80) { /* regular message */ p++; if ((event != 0xF0) && (event != 0xF7) && (event != 0xFF)) running_status = event; } else /* use running status */ event = running_status; switch (event>>4) { case 0x0C: /* program change! */ patches[*p] = TRUE; p++; break; case 0x09: /* note on, is it a drum? */ if ((event & 0x0F) == 9) drums[*p] = TRUE; p += 2; break; case 0x08: /* note off */ case 0x0A: /* note aftertouch */ case 0x0B: /* control change */ case 0x0E: /* pitch bend */ p += 2; break; case 0x0D: /* channel aftertouch */ p += 1; break; case 0x0F: /* special event */ switch (event) { case 0xF0: /* sysex */ case 0xF7: l = parse_var_len(&p); p += l; break; case 0xF2: /* song position */ p += 2; break; case 0xF3: /* song select */ p++; break; case 0xFF: /* meta-event */ p++; l = parse_var_len(&p); p += l; break; default: /* the other special events don't have any data bytes, so we don't need to bother skipping past them */ break; } break; default: /* something has gone badly wrong if we ever get to here */ break; } if (p < end) /* skip time offset */ parse_var_len(&p); } } } /* reads a MIDI file to see what patches it requires */ static int read_midi(AL_CONST char *filename, int attrib, void *param) { char patches[128], drums[128]; char fname[256]; MIDI *midi; int c; strcpy(fname, filename); fix_filename_case(fname); printf("Scanning %s...\n", fname); midi = load_midi(fname); if (!midi) { fprintf(stderr, "Error reading %s\n", fname); err = 1; return -1; } scan_patches(midi, patches, drums); if (opt_veryverbose) { printf("\n\tUses patches:\n"); for (c=0; c<128; c++) if (patches[c]) printf("\t\t%c Instrument #%-6d(%s)\n", (need_patches[c] ? ' ' : '*'), c+1, patch_names[c]); for (c=0; c<128; c++) if (drums[c]) printf("\t\t%c Percussion #%-6d(%s)\n", (need_drums[c] ? ' ' : '*'), c+1, (((c >= 35) && (c <= 81)) ? drum_names[c-35] : "unknown")); printf("\n"); } for (c=0; c<128; c++) { if (patches[c]) need_patches[c] = TRUE; if (drums[c]) need_drums[c] = TRUE; } destroy_midi(midi); return 0; } /* scratch buffer for generating new patch files */ static unsigned char *mem = NULL; static int mem_size = 0; static int mem_alloced = 0; /* writes a byte to the memory buffer */ static void mem_write8(int val) { if (mem_size >= mem_alloced) { mem_alloced += 4096; mem = _al_realloc(mem, mem_alloced); } mem[mem_size] = val; mem_size++; } /* writes a word to the memory buffer (little endian) */ static void mem_write16(int val) { mem_write8(val & 0xFF); mem_write8((val >> 8) & 0xFF); } /* writes a long to the memory buffer (little endian) */ static void mem_write32(int val) { mem_write8(val & 0xFF); mem_write8((val >> 8) & 0xFF); mem_write8((val >> 16) & 0xFF); mem_write8((val >> 24) & 0xFF); } /* alters data already written to the memory buffer (little endian) */ static void mem_modify32(int pos, int val) { mem[pos] = val & 0xFF; mem[pos+1] = (val >> 8) & 0xFF; mem[pos+2] = (val >> 16) & 0xFF; mem[pos+3] = (val >> 24) & 0xFF; } /* writes a block of data the memory buffer */ static void mem_write_block(void *data, int size) { if (mem_size+size > mem_alloced) { mem_alloced = (mem_alloced + size + 4095) & ~4095; mem = _al_realloc(mem, mem_alloced); } memcpy(mem+mem_size, data, size); mem_size += size; } /* writes from a file to the memory buffer */ static void mem_write_file(PACKFILE *f, int size) { if (mem_size+size > mem_alloced) { mem_alloced = (mem_alloced + size + 4095) & ~4095; mem = _al_realloc(mem, mem_alloced); } pack_fread(mem+mem_size, size, f); mem_size += size; } /* imports data from a GUS patch file */ static DATAFILE *grab_patch(int type, AL_CONST char *filename, DATAFILE_PROPERTY **prop, int depth) { PACKFILE *f; long sz = file_size(filename); char buf[256]; int inst, layer, sample, i; int data_size, data_size_pos; int num_instruments, instrument_size, instrument_size_pos; int num_layers, layer_size, layer_size_pos; int num_samples, sample_size; int loop_start, loop_end; int flags; int odd_len; if (sz <= 0) return NULL; f = pack_fopen(filename, F_READ); if (!f) return NULL; if (!opt_8bit) { /* raw copy of the disk file */ mem = _al_malloc(sz); pack_fread(mem, sz, f); pack_fclose(f); if (errno) { _al_free(mem); return NULL; } if (opt_verbose) { memcpy(buf, mem+22, 60); for (i=0; i<60; i++) if (buf[i] < ' ') break; if (i) { buf[i] = 0; printf("%s\n\n", buf); } } return datedit_construct(type, mem, sz, prop); } /* have to parse the file to adjust the sample format */ mem = NULL; mem_size = 0; mem_alloced = 0; /* check patch header */ pack_fread(buf, 22, f); if (memcmp(buf, "GF1PATCH110\0ID#000002\0", 22)) { pack_fclose(f); return NULL; } mem_write_block(buf, 22); /* description */ pack_fread(buf, 60, f); mem_write_block(buf, 60); if (opt_verbose) { for (i=0; i<60; i++) if (buf[i] < ' ') break; if (i) { buf[i] = 0; printf("%s\n", buf); } } /* how many instruments? */ num_instruments = pack_getc(f); mem_write8(num_instruments); mem_write_file(f, 6); data_size = pack_igetl(f); data_size_pos = mem_size; mem_write32(data_size); mem_write_file(f, 36); for (inst=0; inst> 8); } else { for (i=0; i> 8) ^ 0x80); } if (odd_len) pack_getc(f); } else { /* copy 8 bit waveform */ if (flags & 2) { for (i=0; i 0) { /* is first word all digits? */ flag_num = TRUE; for (i=0; i<(int)strlen(argv[0]); i++) { if ((!uisdigit(argv[0][i])) && (argv[0][i] != '-')) { flag_num = FALSE; break; } } if ((flag_num) && (argc >= 2)) { if (stricmp(argv[1], "begin_multipatch") == 0) { /* start the block of percussion instruments */ drum_start = atoi(argv[0])-1; drum_mode = TRUE; } else if (stricmp(argv[1], "override_patch") == 0) { /* ignore patch overrides */ override_mode = TRUE; } else if (!override_mode) { /* must be a patch number */ patchnum = atoi(argv[0]); if (!drum_mode) patchnum--; if ((patchnum >= 0) && (patchnum < 128) && (((drum_mode) && (need_drums[patchnum])) || ((!drum_mode) && (need_patches[patchnum])))) { if (drum_mode) patchnum += drum_start; /* grab the sample */ strcpy(filename, dir); strcat(filename, argv[1]); if ((*get_extension(filename) == 0) && (!strchr(filename, '#'))) strcat(filename, ".pat"); fix_filename_case(argv[1]); fix_filename_case(filename); for (j=0; filename[j]; j++) if (filename[j] == '\\') filename[j] = '/'; for (j=0; datafile[j].type != DAT_END; j++) if (stricmp(get_datafile_property(datafile+j, DAT_NAME), argv[1]) == 0) break; if (datafile[j].type == DAT_END) { params.filename = filename; params.name = argv[1]; params.type = DAT_PATCH; printf("Grabbing %s\n", filename); d = datedit_grabnew(datafile, ¶ms); } else d = datafile; if (!d) { errno = err = 1; pack_fclose(f); return; } else { datafile = d; if (drum_mode) need_drums[patchnum-drum_start] = FALSE; else need_patches[patchnum] = FALSE; } } } } else { /* handle other keywords */ if (stricmp(argv[0], "end_multipatch") == 0) { drum_mode = FALSE; override_mode = FALSE; } } } } pack_fclose(f); } /* SoundFont chunk format and ID values */ typedef struct RIFF_CHUNK { int size; int id; int type; int end; } RIFF_CHUNK; #define CID(a,b,c,d) (((d)<<24)+((c)<<16)+((b)<<8)+((a))) #define CID_RIFF CID('R','I','F','F') #define CID_LIST CID('L','I','S','T') #define CID_INFO CID('I','N','F','O') #define CID_sdta CID('s','d','t','a') #define CID_snam CID('s','n','a','m') #define CID_smpl CID('s','m','p','l') #define CID_pdta CID('p','d','t','a') #define CID_phdr CID('p','h','d','r') #define CID_pbag CID('p','b','a','g') #define CID_pmod CID('p','m','o','d') #define CID_pgen CID('p','g','e','n') #define CID_inst CID('i','n','s','t') #define CID_ibag CID('i','b','a','g') #define CID_imod CID('i','m','o','d') #define CID_igen CID('i','g','e','n') #define CID_shdr CID('s','h','d','r') #define CID_ifil CID('i','f','i','l') #define CID_isng CID('i','s','n','g') #define CID_irom CID('i','r','o','m') #define CID_iver CID('i','v','e','r') #define CID_INAM CID('I','N','A','M') #define CID_IPRD CID('I','P','R','D') #define CID_ICOP CID('I','C','O','P') #define CID_sfbk CID('s','f','b','k') #define CID_ICRD CID('I','C','R','D') #define CID_IENG CID('I','E','N','G') #define CID_ICMT CID('I','C','M','T') #define CID_ISFT CID('I','S','F','T') /* SoundFont generator types */ #define SFGEN_startAddrsOffset 0 #define SFGEN_endAddrsOffset 1 #define SFGEN_startloopAddrsOffset 2 #define SFGEN_endloopAddrsOffset 3 #define SFGEN_startAddrsCoarseOffset 4 #define SFGEN_modLfoToPitch 5 #define SFGEN_vibLfoToPitch 6 #define SFGEN_modEnvToPitch 7 #define SFGEN_initialFilterFc 8 #define SFGEN_initialFilterQ 9 #define SFGEN_modLfoToFilterFc 10 #define SFGEN_modEnvToFilterFc 11 #define SFGEN_endAddrsCoarseOffset 12 #define SFGEN_modLfoToVolume 13 #define SFGEN_unused1 14 #define SFGEN_chorusEffectsSend 15 #define SFGEN_reverbEffectsSend 16 #define SFGEN_pan 17 #define SFGEN_unused2 18 #define SFGEN_unused3 19 #define SFGEN_unused4 20 #define SFGEN_delayModLFO 21 #define SFGEN_freqModLFO 22 #define SFGEN_delayVibLFO 23 #define SFGEN_freqVibLFO 24 #define SFGEN_delayModEnv 25 #define SFGEN_attackModEnv 26 #define SFGEN_holdModEnv 27 #define SFGEN_decayModEnv 28 #define SFGEN_sustainModEnv 29 #define SFGEN_releaseModEnv 30 #define SFGEN_keynumToModEnvHold 31 #define SFGEN_keynumToModEnvDecay 32 #define SFGEN_delayVolEnv 33 #define SFGEN_attackVolEnv 34 #define SFGEN_holdVolEnv 35 #define SFGEN_decayVolEnv 36 #define SFGEN_sustainVolEnv 37 #define SFGEN_releaseVolEnv 38 #define SFGEN_keynumToVolEnvHold 39 #define SFGEN_keynumToVolEnvDecay 40 #define SFGEN_instrument 41 #define SFGEN_reserved1 42 #define SFGEN_keyRange 43 #define SFGEN_velRange 44 #define SFGEN_startloopAddrsCoarse 45 #define SFGEN_keynum 46 #define SFGEN_velocity 47 #define SFGEN_initialAttenuation 48 #define SFGEN_reserved2 49 #define SFGEN_endloopAddrsCoarse 50 #define SFGEN_coarseTune 51 #define SFGEN_fineTune 52 #define SFGEN_sampleID 53 #define SFGEN_sampleModes 54 #define SFGEN_reserved3 55 #define SFGEN_scaleTuning 56 #define SFGEN_exclusiveClass 57 #define SFGEN_overridingRootKey 58 #define SFGEN_unused5 59 #define SFGEN_endOper 60 /* SoundFont sample data */ static short *sf_sample_data = NULL; static int sf_sample_data_size = 0; /* SoundFont preset headers */ typedef struct sfPresetHeader { char achPresetName[20]; unsigned short wPreset; unsigned short wBank; unsigned short wPresetBagNdx; unsigned long dwLibrary; unsigned long dwGenre; unsigned long dwMorphology; } sfPresetHeader; static sfPresetHeader *sf_presets = NULL; static int sf_num_presets = 0; /* SoundFont preset indexes */ typedef struct sfPresetBag { unsigned short wGenNdx; unsigned short wModNdx; } sfPresetBag; static sfPresetBag *sf_preset_indexes = NULL; static int sf_num_preset_indexes = 0; /* SoundFont preset generators */ typedef struct rangesType { unsigned char byLo; unsigned char byHi; } rangesType; typedef union genAmountType { rangesType ranges; short shAmount; unsigned short wAmount; } genAmountType; typedef struct sfGenList { unsigned short sfGenOper; genAmountType genAmount; } sfGenList; static sfGenList *sf_preset_generators = NULL; static int sf_num_preset_generators = 0; /* SoundFont instrument headers */ typedef struct sfInst { char achInstName[20]; unsigned short wInstBagNdx; } sfInst; static sfInst *sf_instruments = NULL; static int sf_num_instruments = 0; /* SoundFont instrument indexes */ typedef struct sfInstBag { unsigned short wInstGenNdx; unsigned short wInstModNdx; } sfInstBag; static sfInstBag *sf_instrument_indexes = NULL; static int sf_num_instrument_indexes = 0; /* SoundFont instrument generators */ static sfGenList *sf_instrument_generators = NULL; static int sf_num_instrument_generators = 0; /* SoundFont sample headers */ typedef struct sfSample { char achSampleName[20]; unsigned long dwStart; unsigned long dwEnd; unsigned long dwStartloop; unsigned long dwEndloop; unsigned long dwSampleRate; unsigned char byOriginalKey; signed char chCorrection; unsigned short wSampleLink; unsigned short sfSampleType; } sfSample; static sfSample *sf_samples = NULL; static int sf_num_samples = 0; /* list of the layers waiting to be dealt with */ typedef struct EMPTY_WHITE_ROOM { sfSample *sample; sfGenList *igen; sfGenList *pgen; int igen_count; int pgen_count; float volume; } EMPTY_WHITE_ROOM; #define MAX_WAITING 64 static EMPTY_WHITE_ROOM waiting_list[MAX_WAITING]; static int waiting_list_count; /* SoundFont parameters for the current sample */ static int sf_start, sf_end; static int sf_loop_start, sf_loop_end; static int sf_key, sf_tune; static int sf_pan; static int sf_keyscale; static int sf_keymin, sf_keymax; static int sf_sustain_mod_env; static int sf_mod_env_to_pitch; static int sf_delay_vol_env; static int sf_attack_vol_env; static int sf_hold_vol_env; static int sf_decay_vol_env; static int sf_release_vol_env; static int sf_sustain_level; static int sf_mode; /* interprets a SoundFont generator object */ static void apply_generator(sfGenList *g, int preset) { switch (g->sfGenOper) { case SFGEN_startAddrsOffset: sf_start += g->genAmount.shAmount; break; case SFGEN_endAddrsOffset: sf_end += g->genAmount.shAmount; break; case SFGEN_startloopAddrsOffset: sf_loop_start += g->genAmount.shAmount; break; case SFGEN_endloopAddrsOffset: sf_loop_end += g->genAmount.shAmount; break; case SFGEN_startAddrsCoarseOffset: sf_start += (int)g->genAmount.shAmount * 32768; break; case SFGEN_endAddrsCoarseOffset: sf_end += (int)g->genAmount.shAmount * 32768; break; case SFGEN_startloopAddrsCoarse: sf_loop_start += (int)g->genAmount.shAmount * 32768; break; case SFGEN_endloopAddrsCoarse: sf_loop_end += (int)g->genAmount.shAmount * 32768; break; case SFGEN_modEnvToPitch: if (preset) sf_mod_env_to_pitch += g->genAmount.shAmount; else sf_mod_env_to_pitch = g->genAmount.shAmount; break; case SFGEN_sustainModEnv: if (preset) sf_sustain_mod_env += g->genAmount.shAmount; else sf_sustain_mod_env = g->genAmount.shAmount; break; case SFGEN_delayVolEnv: if (preset) sf_delay_vol_env += g->genAmount.shAmount; else sf_delay_vol_env = g->genAmount.shAmount; break; case SFGEN_attackVolEnv: if (preset) sf_attack_vol_env += g->genAmount.shAmount; else sf_attack_vol_env = g->genAmount.shAmount; break; case SFGEN_holdVolEnv: if (preset) sf_hold_vol_env += g->genAmount.shAmount; else sf_hold_vol_env = g->genAmount.shAmount; break; case SFGEN_decayVolEnv: if (preset) sf_decay_vol_env += g->genAmount.shAmount; else sf_decay_vol_env = g->genAmount.shAmount; break; case SFGEN_sustainVolEnv: if (preset) sf_sustain_level += g->genAmount.shAmount; else sf_sustain_level = g->genAmount.shAmount; break; case SFGEN_releaseVolEnv: if (preset) sf_release_vol_env += g->genAmount.shAmount; else sf_release_vol_env = g->genAmount.shAmount; break; case SFGEN_pan: if (preset) sf_pan += g->genAmount.shAmount; else sf_pan = g->genAmount.shAmount; break; case SFGEN_keyRange: sf_keymin = g->genAmount.ranges.byLo; sf_keymax = g->genAmount.ranges.byHi; break; case SFGEN_coarseTune: sf_tune += (int)g->genAmount.shAmount * 100; break; case SFGEN_fineTune: sf_tune += g->genAmount.shAmount; break; case SFGEN_sampleModes: sf_mode = g->genAmount.wAmount; break; case SFGEN_scaleTuning: if (preset) sf_keyscale += g->genAmount.shAmount; else sf_keyscale = g->genAmount.shAmount; break; case SFGEN_overridingRootKey: sf_key = g->genAmount.shAmount; break; } } /* converts AWE32 (MIDI) pitches to GUS (frequency) format */ static int key2freq(int note, int cents) { return pow(2.0, (float)(note*100+cents)/1200.0) * 8175.800781; } /* converts the strange AWE32 timecent values to milliseconds */ static int timecent2msec(int t) { return pow(2.0, (float)t/1200.0) * 1000.0; } /* converts milliseconds to the even stranger floating point GUS format */ static int msec2gus(int t, int r) { static int vexp[4] = { 1, 8, 64, 512 }; int e, m; t = t * 32 / r; if (t <= 0) return 0x3F; for (e=3; e>=0; e--) { m = (vexp[e] * 16 + t/2) / t; if ((m > 0) && (m < 64)) return ((e << 6) | m); } return 0xC1; } /* copies data from the waiting list into a GUS .pat struct */ static void grab_soundfont_sample(char *name) { static char msg[] = "Converted by Allegro pat2dat v" ALLEGRO_VERSION_STR ", " ALLEGRO_PLATFORM_STR; sfSample *sample; sfGenList *igen; sfGenList *pgen; float vol, total_vol; int igen_count; int pgen_count; int length; int min_freq, max_freq; int root_freq; int decay; int sustain; int release; int flags; int i, n; mem = NULL; mem_size = 0; mem_alloced = 0; mem_write_block("GF1PATCH110\0ID#000002\0", 22); mem_write_block(msg, sizeof(msg)); /* copyright message */ for (i=0; i<60-(int)sizeof(msg); i++) mem_write8(0); mem_write8(1); /* number of instruments */ mem_write8(14); /* number of voices */ mem_write8(0); /* number of channels */ mem_write16(waiting_list_count); /* number of waveforms */ mem_write16(127); /* master volume */ mem_write32(0); /* data size (wrong!) */ for (i=0; i<36; i++) /* reserved */ mem_write8(0); mem_write16(0); /* instrument number */ for (i=0; name[i]; i++) /* instrument name */ mem_write8(name[i]); while (i < 16) { /* pad instrument name */ mem_write8(0); i++; } mem_write32(0); /* instrument size (wrong!) */ mem_write8(1); /* number of layers */ for (i=0; i<40; i++) /* reserved */ mem_write8(0); mem_write8(0); /* layer duplicate */ mem_write8(0); /* layer number */ mem_write32(0); /* layer size (wrong!) */ mem_write8(waiting_list_count); /* number of samples */ for (i=0; i<40; i++) /* reserved */ mem_write8(0); /* bodge alert!!! I don't know how to make the volume parameters come * out right. If I ignore them, some things are very wrong (eg. with * the Emu 2MB bank the pads have overloud 'tinkle' layers, and the * square lead is way too loud). But if I process the attenuation * parameter the way I think it should be done, other things go wrong * (the church organ and honkytonk piano come out very quiet). So I * look at the volume setting and then normalise the results, to get * the differential between each layer but still keep a uniform overall * volume for the patch. Totally incorrect, but it usually seems to do * the right thing... */ total_vol = 0; for (n=0; n 0) vol /= pow(10, 0.005); waiting_list[n].volume = vol; if ((keymin <= 60) && (keymax >= 60)) total_vol += vol; } /* normalise the layer volumes so they sum to unity */ if (total_vol > 0) { for (n=0; ndwStart; sf_end = sample->dwEnd; sf_loop_start = sample->dwStartloop; sf_loop_end = sample->dwEndloop; sf_key = sample->byOriginalKey; sf_tune = sample->chCorrection; sf_sustain_mod_env = 0; sf_mod_env_to_pitch = 0; sf_delay_vol_env = -12000; sf_attack_vol_env = -12000; sf_hold_vol_env = -12000; sf_decay_vol_env = -12000; sf_release_vol_env = -12000; sf_sustain_level = 0; sf_pan = 0; sf_keyscale = 100; sf_keymin = 0; sf_keymax = 127; sf_mode = 0; /* process the lists of generator data */ for (i=0; idwSampleRate); /* sample freq */ mem_write32(min_freq); /* low freq */ mem_write32(max_freq); /* high freq */ mem_write32(root_freq); /* root frequency */ mem_write16(512); /* finetune */ mem_write8(sf_pan); /* balance */ mem_write8(0x3F); /* envelope rates */ mem_write8(0x3F); mem_write8(msec2gus(decay, 256-sustain)); mem_write8(0x3F); mem_write8(msec2gus(release, 256)); mem_write8(0x3F); mem_write8(255); /* envelope offsets */ mem_write8(255); mem_write8(sustain); mem_write8(255); mem_write8(0); mem_write8(0); mem_write8(0x3C); /* tremolo sweep */ mem_write8(0x80); /* tremolo rate */ mem_write8(0); /* tremolo depth */ mem_write8(0x3C); /* vibrato sweep */ mem_write8(0x80); /* vibrato rate */ mem_write8(0); /* vibrato depth */ flags = (32 | 64); /* enable sustain and envelope */ if (opt_8bit) flags |= 2; /* signed waveform */ else flags |= 1; /* 16-bit waveform */ if (sf_mode & 1) flags |= 4; /* looped sample */ mem_write8(flags); /* write sample mode */ mem_write16(60); /* scale frequency */ mem_write16(sf_keyscale); /* scale factor */ for (i=0; i<36; i++) /* reserved */ mem_write8(0); if (opt_8bit) { /* sample waveform */ for (i=0; idwStart+i] >> 8) * vol) ^ 0x80); } else { for (i=0; idwStart+i] * vol); } } datafile = datedit_insert(datafile, NULL, name, DAT_PATCH, mem, mem_size); } /* converts loaded SoundFont data */ static int grab_soundfont(int num, int drum, char *name) { sfPresetHeader *pheader; sfPresetBag *pindex; sfGenList *pgen; sfInst *iheader; sfInstBag *iindex; sfGenList *igen; sfSample *sample; int pindex_count; int pgen_count; int iindex_count; int igen_count; int pnum, inum, lnum; int wanted_patch, wanted_bank; int keymin, keymax; int waiting_room_full; int i; char *s; if (drum) { wanted_patch = opt_drum_bank; wanted_bank = 128; keymin = num; keymax = num; } else { wanted_patch = num; wanted_bank = opt_bank; keymin = 0; keymax = 127; } /* search for the desired preset */ for (pnum=0; pnumwPreset == wanted_patch) && (pheader->wBank == wanted_bank)) { /* find what substructures it uses */ pindex = &sf_preset_indexes[pheader->wPresetBagNdx]; pindex_count = pheader[1].wPresetBagNdx - pheader[0].wPresetBagNdx; if (pindex_count < 1) return FALSE; /* prettify the preset name */ s = pheader->achPresetName; i = strlen(s)-1; while ((i >= 0) && (uisspace(s[i]))) { s[i] = 0; i--; } printf("Grabbing %s -> %s\n", s, name); waiting_list_count = 0; waiting_room_full = FALSE; /* for each layer in this preset */ for (inum=0; inum 0) && (pgen[pgen_count-1].sfGenOper == SFGEN_instrument)) { if (pgen[0].sfGenOper == SFGEN_keyRange) if ((pgen[0].genAmount.ranges.byHi < keymin) || (pgen[0].genAmount.ranges.byLo > keymax)) continue; iheader = &sf_instruments[pgen[pgen_count-1].genAmount.wAmount]; iindex = &sf_instrument_indexes[iheader->wInstBagNdx]; iindex_count = iheader[1].wInstBagNdx - iheader[0].wInstBagNdx; /* for each layer in this instrument */ for (lnum=0; lnum 0) && (igen[igen_count-1].sfGenOper == SFGEN_sampleID)) { if (igen[0].sfGenOper == SFGEN_keyRange) if ((igen[0].genAmount.ranges.byHi < keymin) || (igen[0].genAmount.ranges.byLo > keymax)) continue; sample = &sf_samples[igen[igen_count-1].genAmount.wAmount]; /* don't bother with left channel or linked samples */ if (sample->sfSampleType & 12) continue; /* prettify the sample name */ s = sample->achSampleName; i = strlen(s)-1; while ((i >= 0) && (uisspace(s[i]))) { s[i] = 0; i--; } if (sample->sfSampleType & 0x8000) { printf("Error: this SoundFont uses the AWE32 ROM data in sample %s\n", s); if (opt_veryverbose) printf("\n"); return FALSE; } /* add this sample to the waiting list */ if (waiting_list_count < MAX_WAITING) { if (opt_veryverbose) printf(" - sample %s\n", s); waiting_list[waiting_list_count].sample = sample; waiting_list[waiting_list_count].igen = igen; waiting_list[waiting_list_count].pgen = pgen; waiting_list[waiting_list_count].igen_count = igen_count; waiting_list[waiting_list_count].pgen_count = pgen_count; waiting_list[waiting_list_count].volume = 1.0; waiting_list_count++; } else waiting_room_full = TRUE; } } } } if (waiting_room_full) printf("Warning: too many layers in this instrument!\n"); if (waiting_list_count > 0) grab_soundfont_sample(name); else printf("Strange... no valid layers found!\n"); if (opt_veryverbose) printf("\n"); return (waiting_list_count > 0); } } return FALSE; } /* reads a byte from the input file */ static int get8(FILE *f) { return getc(f); } /* reads a word from the input file (little endian) */ static int get16(FILE *f) { int b1, b2; b1 = get8(f); b2 = get8(f); return ((b2 << 8) | b1); } /* reads a long from the input file (little endian) */ static int get32(FILE *f) { int b1, b2, b3, b4; b1 = get8(f); b2 = get8(f); b3 = get8(f); b4 = get8(f); return ((b4 << 24) | (b3 << 16) | (b2 << 8) | b1); } /* calculates the file offset for the end of a chunk */ static void calc_end(RIFF_CHUNK *chunk, FILE *f) { chunk->end = ftell(f) + chunk->size + (chunk->size & 1); } /* reads and displays a SoundFont text/copyright message */ static void print_sf_string(FILE *f, char *title) { char buf[256]; char ch; int i = 0; do { ch = get8(f); buf[i++] = ch; } while ((ch) && (i < 256)); if (i & 1) get8(f); if (opt_verbose) printf("%-12s%s\n", title, buf); } /* error handling macro */ #define BAD_SF() \ { \ fprintf(stderr, "Error: bad SoundFont structure\n"); \ err = 1; \ goto getout; \ } /* inserts all the required patches into the datafile */ static void add_soundfont_patches(void) { RIFF_CHUNK file, chunk, subchunk; DATEDIT_GRAB_PARAMETERS params; DATAFILE *d; FILE *f; time_t now; char buf[256] = "XXXXXX"; char tm[80]; int i; #ifdef HAVE_MKSTEMP int tmp_fd; tmp_fd = mkstemp(buf); close(tmp_fd); #else tmpnam(buf); #endif printf("Generating index file\n"); f = fopen(buf, F_WRITE); if (!f) { fprintf(stderr, "Error writing temporary file\n"); err = 1; return; } time(&now); strcpy(tm, asctime(localtime(&now))); for (i=0; tm[i]; i++) if ((tm[i] == '\r') || (tm[i] == '\n')) tm[i] = 0; fprintf(f, "# Patch index for the Allegro DIGMID driver\n"); fprintf(f, "# Produced by pat2dat v" ALLEGRO_VERSION_STR ", " ALLEGRO_PLATFORM_STR "\n"); fprintf(f, "# Date: %s\n\n", tm); fprintf(f, "0 %s\n", short_patch_names[0]); for (i=0; i<128; i++) fprintf(f, "%d %s\n", i+1, short_patch_names[i]); fprintf(f, "\n129-256 begin_multipatch default blank\n"); for (i=35; i<82; i++) fprintf(f, "\t%d %s\n", i, short_drum_names[i-35]); fprintf(f, "end_multipatch\n"); fclose(f); params.datafile = NULL; /* only with absolute filenames */ params.filename = buf; params.name = "default_cfg"; params.type = DAT_DATA; /* params.x = */ /* params.y = */ /* params.w = */ /* params.h = */ /* params.colordepth = */ params.relative = FALSE; /* required (see above) */ d = datedit_grabnew(datafile, ¶ms); delete_file(buf); if (!d) { errno = err = 1; return; } else datafile = d; if (opt_verbose) printf("\nReading %s\n\n", opt_soundfont); else printf("Reading %s\n", opt_soundfont); f = fopen(opt_soundfont, "rb"); if (!f) { fprintf(stderr, "Error opening file\n"); err = 1; return; } file.id = get32(f); if (file.id != CID_RIFF) { fprintf(stderr, "Error: bad SoundFont header\n"); err = 1; goto getout; } file.size = get32(f); calc_end(&file, f); file.type = get32(f); if (file.type != CID_sfbk) { fprintf(stderr, "Error: bad SoundFont header\n"); err = 1; goto getout; } while (ftell(f) < file.end) { chunk.id = get32(f); chunk.size = get32(f); calc_end(&chunk, f); switch (chunk.id) { case CID_LIST: /* a list of other chunks */ chunk.type = get32(f); while (ftell(f) < chunk.end) { subchunk.id = get32(f); subchunk.size = get32(f); calc_end(&subchunk, f); switch (chunk.type) { case CID_INFO: /* information block */ switch (subchunk.id) { case CID_ifil: if (get16(f) < 2) { fprintf(stderr, "Error: this is a SoundFont 1.x file, and I only understand version 2 (.sf2)\n"); err = 1; goto getout; } get16(f); break; case CID_INAM: print_sf_string(f, "Bank name:"); break; case CID_irom: print_sf_string(f, "ROM name:"); break; case CID_ICRD: print_sf_string(f, "Date:"); break; case CID_IENG: print_sf_string(f, "Made by:"); break; case CID_IPRD: print_sf_string(f, "Target:"); break; case CID_ICOP: print_sf_string(f, "Copyright:"); break; case CID_ISFT: print_sf_string(f, "Tools:"); break; } /* skip unknown chunks and extra data */ fseek(f, subchunk.end, SEEK_SET); break; case CID_pdta: /* preset, instrument and sample header data */ switch (subchunk.id) { case CID_phdr: /* preset headers */ sf_num_presets = subchunk.size/38; if ((sf_num_presets*38 != subchunk.size) || (sf_num_presets < 2) || (sf_presets)) BAD_SF(); sf_presets = malloc(sizeof(sfPresetHeader) * sf_num_presets); for (i=0; itype != DAT_END; c++) { if (datedit_grabber_info[c]->type == DAT_PATCH) { datedit_grabber_info[c]->grab = grab_patch; break; } } if (datedit_grabber_info[c]->type == DAT_END) { fprintf(stderr, "Argh, patch object not registered!\n"); return 1; } for (c=1; c= argc-1) { usage(); return 1; } opt_bank = atoi(argv[++c]); break; case 'c': opt_compression = 1; break; case 'd': if (c >= argc-1) { usage(); return 1; } opt_drum_bank = atoi(argv[++c]); break; case 'o': opt_overwrite = TRUE; break; case 'v': opt_verbose = TRUE; if (utolower(argv[c][2]) == 'v') opt_veryverbose = TRUE; break; case '8': opt_8bit = TRUE; break; default: printf("Unknown option '%s'\n", argv[c]); return 1; } } else { if (stricmp(get_extension(argv[c]), "dat") == 0) { if (opt_datafile) { usage(); return 1; } opt_datafile = argv[c]; } else if (stricmp(get_extension(argv[c]), "mid") == 0) { if (opt_numnames < MAX_FILES) opt_namelist[opt_numnames++] = argv[c]; } else if (stricmp(get_extension(argv[c]), "cfg") == 0) { if ((opt_configfile) || (opt_soundfont)) { usage(); return 1; } opt_configfile = argv[c]; } else if (stricmp(get_extension(argv[c]), "sf2") == 0) { if ((opt_configfile) || (opt_soundfont)) { usage(); return 1; } opt_soundfont = argv[c]; } else { printf("Unknown file type '.%s'\n", get_extension(argv[c])); return 1; } } } if (!opt_datafile) { usage(); return 1; } if (!opt_overwrite) { if (file_exists(opt_datafile, FA_RDONLY | FA_ARCH, NULL)) { printf("%s already exists: use -o to overwrite\n", opt_datafile); return 1; } } if (opt_numnames == 0) { printf("No MIDI files specified: including entire patch set\n"); for (c=0; c<128; c++) need_patches[c] = TRUE; for (c=35; c<82; c++) need_drums[c] = TRUE; } else { need_patches[0] = TRUE; /* always load the piano */ for (c=0; c= 35) && (c <= 81)) ? drum_names[c-35] : "unknown")); printf("\n"); } datafile = _al_malloc(sizeof(DATAFILE)); datafile->dat = NULL; datafile->type = DAT_END; datafile->size = 0; datafile->prop = NULL; if (opt_soundfont) add_soundfont_patches(); else add_gus_patches(); if (!err) { DATEDIT_SAVE_DATAFILE_OPTIONS options; options.pack = opt_compression; options.strip = 1; options.sort = 1; options.verbose = (opt_veryverbose || (opt_verbose && opt_compression)); options.write_msg = TRUE; options.backup = FALSE; if (!datedit_save_datafile(datafile, opt_datafile, NULL, &options, NULL)) err = 1; } unload_datafile(datafile); if (!err) { int ok = TRUE; for (c=0; c<128; c++) { if (need_patches[c]) { if (ok) { printf("\n"); ok = FALSE; } printf("Warning: can't find instrument #%d (%s)\n", c+1, patch_names[c]); } } for (c=0; c<128; c++) { if (need_drums[c]) { if (ok) { printf("\n"); ok = FALSE; } printf("Warning: can't find percussion #%d (%s)\n", c+1, (((c >= 35) && (c <= 81)) ? drum_names[c-35] : "unknown")); } } } if ((!err) && (opt_verbose)) { printf("\nEither specify this file with the \"patches=\" line in your allegro.cfg, or\n"); printf("rename it to patches.dat and put it in the same directory as your program.\n"); } } return err; } END_OF_MAIN()