/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * SoundFont file loading code borrowed from Smurf SoundFont Editor * Copyright (C) 1999-2001 Josh Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License * as published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307, USA */ #include "sfont.h" #include "fluid.h" #include "voice.h" // #define DEBUG_SFONT #include "xml.h" extern bool debugMode; namespace FluidS { //--------------------------------------------------------- // SFVersion //--------------------------------------------------------- SFVersion::SFVersion() { major = 0; minor = 0; } //--------------------------------------------------------- // SFont //--------------------------------------------------------- SFont::SFont(Fluid* f) { synth = f; samplepos = 0; samplesize = 0; } SFont::~SFont() { foreach(Sample* s, sample) delete s; foreach(Preset* p, presets) delete p; foreach(unsigned char* p, infos) delete[] p; foreach(Instrument* i, instruments) { // foreach(Zone* z, i->zones) // delete z; delete i; } } //--------------------------------------------------------- // read //--------------------------------------------------------- bool SFont::read(const QString& s) { f.setFileName(s); if (!load()) return false; foreach(Instrument* i, instruments) { if (!i->import_sfont()) return false; } foreach(Preset* p, presets) { if (!p->importSfont()) return false; } return true; } //--------------------------------------------------------- // get_preset //--------------------------------------------------------- Preset* SFont::get_preset(int bank, int num) { foreach(Preset* p, presets) { if ((p->get_banknum() == bank) && (p->get_num() == num)) return p; } return 0; } //--------------------------------------------------------- // Preset //--------------------------------------------------------- Preset::Preset(SFont* s) { sfont = s; bank = 0; num = 0; _global_zone = 0; } //--------------------------------------------------------- // Preset //--------------------------------------------------------- Preset::~Preset() { delete _global_zone; foreach(Zone* z, zones) delete z; } //--------------------------------------------------------- // loadSamples // this is called if the preset is associated with a // channel //--------------------------------------------------------- void Preset::loadSamples() { if (_global_zone && _global_zone->instrument) { Instrument* i = _global_zone->instrument; if (i->global_zone && i->global_zone->sample) i->global_zone->sample->load(); foreach(Zone* iz, i->zones) iz->sample->load(); } foreach(Zone* z, zones) { Instrument* i = z->instrument; if (i->global_zone && i->global_zone->sample) i->global_zone->sample->load(); foreach(Zone* iz, i->zones) iz->sample->load(); } } //--------------------------------------------------------- // noteon //--------------------------------------------------------- bool Preset::noteon(Fluid* synth, unsigned id, int chan, int key, int vel, double nt) { Zone* global_preset_zone = global_zone(); /* run thru all the zones of this preset */ foreach (Zone* preset_zone, zones) { /* check if the note falls into the key and velocity range of this preset */ if (preset_zone->inside_range(key, vel)) { Instrument* inst = preset_zone->get_inst(); Zone* global_inst_zone = inst->get_global_zone(); /* run thru all the zones of this instrument */ foreach(Zone* inst_zone, inst->get_zone()) { /* make sure this instrument zone has a valid sample */ Sample* sample = inst_zone->get_sample(); if (sample == 0 || sample->inRom()) continue; /* check if the note falls into the key and velocity range of this instrument */ if (inst_zone->inside_range(key, vel) && (sample != 0)) { /* this is a good zone. allocate a new synthesis process and initialize it */ Voice* voice = synth->alloc_voice(id, sample, chan, key, vel, nt); if (voice == 0) return false; /* Instrumentrument level, generators */ for (int i = 0; i < GEN_LAST; i++) { /* SF 2.01 section 9.4 'bullet' 4: * * A generator in a local instrument zone supersedes a * global instrument zone generator. Both cases supersede * the default generator -> voice_gen_set */ if (inst_zone->genlist[i].flags) voice->gen_set(i, inst_zone->genlist[i].val); else if ((global_inst_zone) && (global_inst_zone->genlist[i].flags)) voice->gen_set(i, global_inst_zone->genlist[i].val); else { /* The generator has not been defined in this instrument. * Do nothing, leave it at the default. */ } } /* for all generators */ /* global instrument zone, modulators: Put them all into a * list. */ Mod* mod_list[FLUID_NUM_MOD]; /* list for 'sorting' preset modulators */ int mod_list_count = 0; if (global_inst_zone) { foreach(Mod* mod, global_inst_zone->modlist) mod_list[mod_list_count++] = mod; } /* local instrument zone, modulators. * Replace modulators with the same definition in the list: * SF 2.01 page 69, 'bullet' 8 */ foreach(Mod* mod, inst_zone->modlist) { /* 'Identical' modulators will be deleted by setting their * list entry to 0. The list length is known, 0 * entries will be ignored later. SF2.01 section 9.5.1 * page 69, 'bullet' 3 defines 'identical'. */ for (int i = 0; i < mod_list_count; i++){ if (mod_list[i] && test_identity(mod, mod_list[i])){ mod_list[i] = 0; } } /* Finally add the new modulator to to the list. */ mod_list[mod_list_count++] = mod; } /* Add instrument modulators (global / local) to the voice. */ for (int i = 0; i < mod_list_count; i++){ Mod* mod = mod_list[i]; if (mod) { // disabled modulators CANNOT be skipped. /* Instrumentrument modulators -supersede- existing (default) * modulators. SF 2.01 page 69, 'bullet' 6 */ voice->add_mod(mod, FLUID_VOICE_OVERWRITE); } } /* Preset level, generators */ for (int i = 0; i < GEN_LAST; i++) { /* SF 2.01 section 8.5 page 58: If some generators are * encountered at preset level, they should be ignored */ if ((i != GEN_STARTADDROFS) && (i != GEN_ENDADDROFS) && (i != GEN_STARTLOOPADDROFS) && (i != GEN_ENDLOOPADDROFS) && (i != GEN_STARTADDRCOARSEOFS) && (i != GEN_ENDADDRCOARSEOFS) && (i != GEN_STARTLOOPADDRCOARSEOFS) && (i != GEN_KEYNUM) && (i != GEN_VELOCITY) && (i != GEN_ENDLOOPADDRCOARSEOFS) && (i != GEN_SAMPLEMODE) && (i != GEN_EXCLUSIVECLASS) && (i != GEN_OVERRIDEROOTKEY)) { /* SF 2.01 section 9.4 'bullet' 9: A generator in a * local preset zone supersedes a global preset zone * generator. The effect is -added- to the destination * summing node -> voice_gen_incr */ if (preset_zone->genlist[i].flags) { voice->gen_incr(i, preset_zone->genlist[i].val); } else if ((global_preset_zone != 0) && global_preset_zone->genlist[i].flags) { voice->gen_incr(i, global_preset_zone->genlist[i].val); } else { /* The generator has not been defined in this preset * Do nothing, leave it unchanged. */ } } /* if available at preset level */ } /* for all generators */ /* Global preset zone, modulators: put them all into a * list. */ mod_list_count = 0; if (global_preset_zone){ foreach(Mod* mod, global_preset_zone->modlist) mod_list[mod_list_count++] = mod; } /* Process the modulators of the local preset zone. Kick * out all identical modulators from the global preset zone * (SF 2.01 page 69, second-last bullet) */ foreach(Mod* mod, preset_zone->modlist) { for (int i = 0; i < mod_list_count; i++) { if (mod_list[i] && test_identity(mod,mod_list[i])) mod_list[i] = 0; } /* Finally add the new modulator to the list. */ mod_list[mod_list_count++] = mod; } /* Add preset modulators (global / local) to the voice. */ for (int i = 0; i < mod_list_count; i++){ Mod* mod = mod_list[i]; if ((mod != 0) && (mod->amount != 0)) { /* disabled modulators can be skipped. */ /* Preset modulators -add- to existing instrument / * default modulators. SF2.01 page 70 first bullet on * page */ voice->add_mod(mod, FLUID_VOICE_ADD); } } /* add the synthesis process to the synthesis loop. */ synth->start_voice(voice); /* Store the ID of the first voice that was created by this noteon event. * Exclusive class may only terminate older voices. * That avoids killing voices, which have just been created. * (a noteon event can create several voice processes with the same exclusive * class - for example when using stereo samples) */ } } } } return true; } //--------------------------------------------------------- // importSfont //--------------------------------------------------------- bool Preset::importSfont() { if (name.isEmpty()) name = QString("Bank%1,Preset%2").arg(bank).arg(num); int idx = 0; foreach(Zone* zone, zones) { // zone->name = QString("%1/%2").arg(name).arg(idx); if (!zone->importZone()) return false; if ((idx == 0) && (zone->get_inst() == 0)) setGlobalZone(zones.takeAt(0)); ++idx; } return true; } //--------------------------------------------------------- // import_sfont //--------------------------------------------------------- bool Instrument::import_sfont() { int idx = 0; foreach(Zone* zone, zones) { if (!zone->importZone()) return false; if ((idx == 0) && (zone->get_sample() == 0)) global_zone = zones.takeAt(0); idx++; } return true; } //--------------------------------------------------------- // Zone //--------------------------------------------------------- Zone::Zone() { instrument = 0; sample = 0; sampIdx = 0; instIdx = 0; keylo = 0; keyhi = 128; vello = 0; velhi = 128; /* Flag all generators as unused (default, they will be set when they are found * in the sound font). * This also sets the generator values to default, but that is of no concern here.*/ fluid_gen_set_default_values(&genlist[0]); } Zone::~Zone() { foreach(Mod* m, modlist) delete m; foreach(SFGen* p, gen) delete p; foreach(SFMod* p, mod) delete p; } //--------------------------------------------------------- // inside_range //--------------------------------------------------------- bool Zone::inside_range(int key, int vel) const { return ((keylo <= key) && (keyhi >= key) && (vello <= vel) && (velhi >= vel)); } //--------------------------------------------------------- // Instrument //--------------------------------------------------------- Instrument::Instrument() { global_zone = 0; } Instrument::~Instrument() { delete global_zone; foreach(Zone* z, zones) delete z; } //--------------------------------------------------------- // importZone //--------------------------------------------------------- bool Zone::importZone() { foreach (SFGen* sfgen, gen) { switch (sfgen->id) { case GEN_KEYRANGE: keylo = sfgen->amount.range.lo; keyhi = sfgen->amount.range.hi; break; case GEN_VELRANGE: vello = sfgen->amount.range.lo; velhi = sfgen->amount.range.hi; break; default: /* FIXME: some generators have an unsigned word amount value but i don't know which ones */ genlist[sfgen->id].val = (float) sfgen->amount.sword; genlist[sfgen->id].flags = GEN_SET; break; } } // Import the modulators (only SF2.1 and higher) foreach(SFMod* mod_src, mod) { Mod* mod_dest = new Mod; /* *** Amount *** */ mod_dest->amount = mod_src->amount; /* *** Source *** */ mod_dest->src1 = mod_src->src & 0x7f; /* index of source 1, seven-bit value, SF2.01 section 8.2, page 50 */ mod_dest->flags1 = 0; /* Bit 7: CC flag SF 2.01 section 8.2.1 page 50*/ if (mod_src->src & (1<<7)) mod_dest->flags1 |= FLUID_MOD_CC; /* Bit 8: D flag SF 2.01 section 8.2.2 page 51*/ if (mod_src->src & (1<<8)) mod_dest->flags1 |= FLUID_MOD_NEGATIVE; /* Bit 9: P flag SF 2.01 section 8.2.3 page 51*/ if (mod_src->src & (1<<9)) mod_dest->flags1 |= FLUID_MOD_BIPOLAR; /* modulator source types: SF2.01 section 8.2.1 page 52 */ int type = (mod_src->src) >> 10; type &= 0x3f; /* type is a 6-bit value */ if (type == 0) mod_dest->flags1 |= FLUID_MOD_LINEAR; else if (type == 1) mod_dest->flags1 |= FLUID_MOD_CONCAVE; else if (type == 2) mod_dest->flags1 |= FLUID_MOD_CONVEX; else if (type == 3) mod_dest->flags1 |= FLUID_MOD_SWITCH; else { /* This shouldn't happen - unknown type! * Deactivate the modulator by setting the amount to 0. */ // printf("unknown mod type\n"); mod_dest->amount=0; } /* *** Dest *** */ mod_dest->dest = mod_src->dest; /* index of controlled generator */ /* *** Amount source *** */ mod_dest->src2 = mod_src->amtsrc & 127; /* index of source 2, seven-bit value, SF2.01 section 8.2, p.50 */ mod_dest->flags2 = 0; /* Bit 7: CC flag SF 2.01 section 8.2.1 page 50*/ if (mod_src->amtsrc & (1<<7)) mod_dest->flags2 |= FLUID_MOD_CC; /* Bit 8: D flag SF 2.01 section 8.2.2 page 51*/ if (mod_src->amtsrc & (1<<8)) mod_dest->flags2 |= FLUID_MOD_NEGATIVE; /* Bit 9: P flag SF 2.01 section 8.2.3 page 51*/ if (mod_src->amtsrc & (1<<9)) mod_dest->flags2 |= FLUID_MOD_BIPOLAR; /* modulator source types: SF2.01 section 8.2.1 page 52 */ type = (mod_src->amtsrc) >> 10; type &= 63; /* type is a 6-bit value */ if (type == 0) mod_dest->flags2 |= FLUID_MOD_LINEAR; else if (type == 1) mod_dest->flags2 |= FLUID_MOD_CONCAVE; else if (type == 2) mod_dest->flags2 |= FLUID_MOD_CONVEX; else if (type == 3) mod_dest->flags2 |= FLUID_MOD_SWITCH; else { /* This shouldn't happen - unknown type! * Deactivate the modulator by setting the amount to 0. */ // printf("unknown mod type\n"); mod_dest->amount=0; } /* *** Transform *** */ /* SF2.01 only uses the 'linear' transform (0). * Deactivate the modulator by setting the amount to 0 in any other case. */ if (mod_src->trans != 0) mod_dest->amount = 0; /* Store the new modulator in the zone The order of modulators * will make a difference, at least in an instrument context: The * second modulator overwrites the first one, if they only differ * in amount. */ modlist.append(mod_dest); } return true; } //--------------------------------------------------------- // Sample //--------------------------------------------------------- Sample::Sample(SFont* s) { sf = s; _valid = false; start = 0; end = 0; loopstart = 0; loopend = 0; samplerate = 0; origpitch = 0; pitchadj = 0; sampletype = 0; data = 0; amplitude_that_reaches_noise_floor_is_valid = false; amplitude_that_reaches_noise_floor = 0.0; } //--------------------------------------------------------- // Sample //--------------------------------------------------------- Sample::~Sample() { delete[] data; } //--------------------------------------------------------- // load //--------------------------------------------------------- void Sample::load() { if (!_valid || data) return; QFile fd(sf->get_name()); if (!fd.open(QIODevice::ReadOnly)) return; if (!fd.seek(sf->samplePos() + start * sizeof(short))) return; unsigned int size = end - start; data = new short[size]; size *= sizeof(short); if (fd.read((char*)data, size) != size) return; if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { unsigned char hi, lo; unsigned int i, j; short s; unsigned char* cbuf = (unsigned char*) data; for (i = 0, j = 0; j < size; i++) { lo = cbuf[j++]; hi = cbuf[j++]; s = (hi << 8) | lo; data[i] = s; } } end -= start - 1; // marks last sample, contrary to SF spec. loopstart -= start; loopend -= start; start = 0; optimize(); } //--------------------------------------------------------- // inRom //--------------------------------------------------------- bool Sample::inRom() const { return sampletype & FLUID_SAMPLETYPE_ROM; } static const char idlist[] = { "RIFFLISTsfbkINFOsdtapdtaifilisngINAMiromiverICRDIENGIPRD" "ICOPICMTISFTsnamsmplphdrpbagpmodpgeninstibagimodigenshdr" }; //--------------------------------------------------------- // chunkid //--------------------------------------------------------- static int chunkid (unsigned int id) { unsigned int* p = (unsigned int *) & idlist; for (unsigned i = 0; i < sizeof (idlist) / sizeof (int); i++, p++) { if (*p == id) return (i + 1); } return UNKN_ID; } //--------------------------------------------------------- // preset_compare //--------------------------------------------------------- static bool preset_compare (Preset* a, Preset* b) { int aval = (a->bank) << 16 | a->num; int bval = (b->bank) << 16 | b->num; return aval < bval; } //--------------------------------------------------------- // readchunk //--------------------------------------------------------- void SFont::readchunk(SFChunk* var) { safe_fread(var, 8); if (QSysInfo::ByteOrder == QSysInfo::BigEndian) var->size = GUINT32_FROM_BE(var->size); else var->size = GUINT32_FROM_LE(var->size); } //--------------------------------------------------------- // load // return true on success //--------------------------------------------------------- bool SFont::load() { if (!f.open(QIODevice::ReadOnly)) { qCritical("Unable to open file \"%s\"", qPrintable(f.fileName())); return false; } SFChunk chunk; try { readchunk(&chunk); if (chunkid(chunk.id) != RIFF_ID) throw(QString("Not a RIFF file")); safe_fread(&chunk.id, 4); if (chunkid (chunk.id) != SFBK_ID) /* error if not SFBK_ID */ throw(QString("Not a sound font file")); if (chunk.size != f.size() - 8) throw(QString("Sound font file size mismatch %1 %2").arg(chunk.size).arg(f.size() - 8)); /* Process INFO block */ read_listchunk(&chunk); if (chunkid(chunk.id) != INFO_ID) throw(QString("Invalid ID found when expecting INFO chunk")); process_info(chunk.size); /* Process sample chunk */ read_listchunk(&chunk); if (chunkid (chunk.id) != SDTA_ID) throw(QString("Invalid ID found when expecting SAMPLE chunk")); process_sdta(chunk.size); /* process HYDRA chunk */ read_listchunk(&chunk); if (chunkid (chunk.id) != PDTA_ID) throw(QString("Invalid ID found when expecting HYDRA chunk")); process_pdta (chunk.size); fixup_pgen(); fixup_igen(); } catch (QString s) { printf("fluid: error loading sound font: %s\n", qPrintable(s)); f.close(); return false; } f.close(); /* sort preset list by bank, preset # */ qSort(presets.begin(), presets.end(), preset_compare); return true; } //--------------------------------------------------------- // READW //--------------------------------------------------------- unsigned short SFont::READW() { unsigned short _temp; safe_fread(&_temp, 2); if (QSysInfo::ByteOrder == QSysInfo::BigEndian) return GINT16_FROM_BE(_temp); else return GINT16_FROM_LE(_temp); } //--------------------------------------------------------- // READD //--------------------------------------------------------- void SFont::READD(unsigned int& var) { unsigned int _temp; safe_fread(&_temp, 4); if (QSysInfo::ByteOrder == QSysInfo::BigEndian) var = GINT32_FROM_BE(_temp); else var = GINT32_FROM_LE(_temp); } //--------------------------------------------------------- // READB //--------------------------------------------------------- unsigned char SFont::READB() { unsigned char var; safe_fread(&var, 1); return var; } //--------------------------------------------------------- // READC //--------------------------------------------------------- char SFont::READC() { char var; safe_fread(&var, 1); return var; } //--------------------------------------------------------- // FSKIPW //--------------------------------------------------------- void SFont::FSKIPW() { safe_fseek(2); } void SFont::READSTR(char* name) { safe_fread(name, 20); name[20] = '\0'; } //--------------------------------------------------------- // read_listchunk //--------------------------------------------------------- void SFont::read_listchunk (SFChunk* chunk) { readchunk (chunk); if (chunkid (chunk->id) != LIST_ID) throw(QString("Invalid chunk id in level 0 parse")); safe_fread(&chunk->id, 4); chunk->size -= 4; } //--------------------------------------------------------- // process_info //--------------------------------------------------------- void SFont::process_info(int size) { while (size > 0) { SFChunk chunk; readchunk (&chunk); size -= 8; unsigned char id = chunkid (chunk.id); if (id == IFIL_ID) { // sound font version chunk? if (chunk.size != 4) throw(QString("Sound font version info chunk has invalid size")); version.major = READW(); version.minor = READW(); if (version.major < 2 || version.major > 2) throw(QString("Bad Sound font version")); } else if (id == IVER_ID) { /* ROM version chunk? */ if (chunk.size != 4) throw(QString("ROM version info chunk has invalid size")); romver.major = READW(); romver.minor = READW(); } else if (id != UNKN_ID) { if ((id != ICMT_ID && chunk.size > 256) || (chunk.size > 65536) || (chunk.size % 2)) throw(QString("INFO sub chunk has invalid chunk size")); /* alloc for chunk id and da chunk */ unsigned char* item = new unsigned char[chunk.size + 1]; *(unsigned char *) item = id; safe_fread (&item[1], chunk.size); /* force terminate info item (don't forget uint8 info ID) */ *(item + chunk.size) = '\0'; infos.append(item); } else throw(QString("Invalid chunk id in INFO chunk")); size -= chunk.size; } if (size < 0) throw(QString("INFO chunk size mismatch")); } //--------------------------------------------------------- // process_sdta // return true on success //--------------------------------------------------------- void SFont::process_sdta (int size) { if (size == 0) return; // no sample data? /* read sub chunk */ SFChunk chunk; readchunk (&chunk); size -= 8; if (chunkid (chunk.id) != SMPL_ID) throw(QString("Expected SMPL chunk found invalid id instead")); if ((size - chunk.size) != 0) throw(QString("SDTA chunk size mismatch %1 != %2").arg(size).arg(chunk.size)); /* sample data follows */ setSamplepos(f.pos()); setSamplesize(chunk.size); FSKIP(chunk.size); } //--------------------------------------------------------- // pdtahelper //--------------------------------------------------------- void SFont::pdtahelper (unsigned expid, unsigned reclen, SFChunk* chunk, int* size) { const char* expstr = CHNKIDSTR (expid); readchunk (chunk); *size -= 8; unsigned id = chunkid(chunk->id); if (id != expid) throw(QString("Expected PDTA sub-chunk %1 found invalid id instead").arg(expstr)); if (chunk->size % reclen) /* valid chunk size? */ throw(QString("chunk size is not a multiple of %1 bytes").arg(reclen)); if ((*size -= chunk->size) < 0) throw(QString("%1 chunk size exceeds remaining PDTA chunk size").arg(expstr)); } //--------------------------------------------------------- // process_pdta //--------------------------------------------------------- void SFont::process_pdta (int size) { static const unsigned id[] = { PHDR_ID, PBAG_ID, PMOD_ID, PGEN_ID, IHDR_ID, IBAG_ID, IMOD_ID, IGEN_ID, SHDR_ID }; static const unsigned len[] = { SFPHDRSIZE, SFBAGSIZE, SFMODSIZE, SFGENSIZE, SFIHDRSIZE, SFBAGSIZE, SFMODSIZE, SFGENSIZE, SFSHDRSIZE }; typedef void (SFont::*LoadFunc)(int); static const LoadFunc funcArray[] = { &SFont::load_phdr, &SFont::load_pbag, &SFont::load_pmod, &SFont::load_pgen, &SFont::load_ihdr, &SFont::load_ibag, &SFont::load_imod, &SFont::load_igen, &SFont::load_shdr }; SFChunk chunk; for (int i = 0; i < 9; ++i) { pdtahelper(id[i], len[i], &chunk, &size); (this->*funcArray[i])(chunk.size); } } /* preset header loader */ void SFont::load_phdr (int size) { Preset* pr = 0; /* ptr to current & previous preset */ unsigned short zndx, pzndx = 0; if (size % SFPHDRSIZE || size == 0) throw(QString("Preset header chunk size is invalid")); int i = size / SFPHDRSIZE - 1; if (i == 0) { /* at least one preset + term record */ qWarning("File contains no presets"); FSKIP (SFPHDRSIZE); return; } for (; i > 0; i--) { /* load all preset headers */ Preset* p = new Preset(this); presets.append(p); char str[21]; READSTR (str); p->name = str; p->num = READW(); p->bank = READW(); zndx = READW(); unsigned int tmp; READD (tmp); READD (tmp); READD (tmp); if (pr) { /* not first preset? */ if (zndx < pzndx) throw(QString("Preset header indices not monotonic")); int i2 = zndx - pzndx; while (i2--) pr->zones.prepend(0); } else if (zndx > 0) /* 1st preset, warn if ofs >0 */ qWarning("%d preset zones not referenced, discarding", zndx); pr = p; /* update preset ptr */ pzndx = zndx; } FSKIP (24); zndx = READW(); /* Read terminal generator index */ FSKIP (12); if (zndx < pzndx) throw(QString("Preset header indices not monotonic")); int i2 = zndx - pzndx; while (i2--) pr->zones.prepend(0); } /* preset bag loader */ void SFont::load_pbag (int size) { Zone *z, *pz = 0; unsigned short genndx, modndx; unsigned short pgenndx = 0, pmodndx = 0; if (size % SFBAGSIZE || size == 0) /* size is multiple of SFBAGSIZE? */ throw(QString("Preset bag chunk size is invalid")); foreach(Preset* p, presets) { for (int i = 0; i < p->zones.size(); ++i) { if ((size -= SFBAGSIZE) < 0) throw(QString("1:Preset bag chunk size mismatch")); z = new Zone; genndx = READW(); /* possible read failure ^ */ modndx = READW(); z->sample = 0; if (pz) { /* if not first zone */ if (genndx < pgenndx) throw(QString("Preset bag generator indices not monotonic")); if (modndx < pmodndx) throw(QString("Preset bag modulator indices not monotonic")); int ii = genndx - pgenndx; while (ii--) pz->gen.prepend(0); ii = modndx - pmodndx; while (ii--) pz->mod.prepend(0); } pz = z; /* update previous zone ptr */ pgenndx = genndx; /* update previous zone gen index */ pmodndx = modndx; /* update previous zone mod index */ p->zones[i] = z; } } size -= SFBAGSIZE; if (size != 0) throw(QString("2:Preset bag chunk size mismatch")); genndx = READW(); modndx = READW(); if (!pz) { if (genndx > 0) qWarning("No preset generators and terminal index not 0"); if (modndx > 0) qWarning("No preset modulators and terminal index not 0"); return; } if (genndx < pgenndx) throw(QString("Preset bag generator indices not monotonic")); if (modndx < pmodndx) throw(QString("Preset bag modulator indices not monotonic")); int i = genndx - pgenndx; while (i--) pz->gen.prepend(0); i = modndx - pmodndx; while (i--) pz->mod.prepend(0); } //--------------------------------------------------------- // load_pmod // preset modulator loader //--------------------------------------------------------- void SFont::load_pmod (int size) { foreach (Preset* p, presets) { foreach(Zone* p2, p->zones) { for (int i = 0; i < p2->mod.size(); ++i) { if ((size -= SFMODSIZE) < 0) throw(QString("Preset modulator chunk size mismatch")); SFMod* m = new SFMod; m->src = READW(); m->dest = READW(); m->amount = READW(); m->amtsrc = READW(); m->trans = READW(); p2->mod[i] = m; } } } /* If there isn't even a terminal record Hmmm, the specs say there should be one, but.. */ if (size == 0) { return; } size -= SFMODSIZE; if (size != 0) throw(QString("Preset modulator chunk size mismatch")); FSKIP (SFMODSIZE); /* terminal mod */ } static const unsigned short badgen[] = { Gen_Unused1, Gen_Unused2, Gen_Unused3, Gen_Unused4, Gen_Reserved1, Gen_Reserved2, Gen_Reserved3, 0 }; static const unsigned short badpgen[] = { Gen_StartAddrOfs, Gen_EndAddrOfs, Gen_StartLoopAddrOfs, Gen_EndLoopAddrOfs, Gen_StartAddrCoarseOfs, Gen_EndAddrCoarseOfs, Gen_StartLoopAddrCoarseOfs, Gen_Keynum, Gen_Velocity, Gen_EndLoopAddrCoarseOfs, Gen_SampleModes, Gen_ExclusiveClass, Gen_OverrideRootKey, 0 }; /* check validity of instrument generator */ static int gen_valid (int gen) { /* is generator id valid? */ int i = 0; if (gen > Gen_MaxValid) return false; while (badgen[i] && badgen[i] != gen) i++; return (badgen[i] == 0); } /* check validity of preset generator */ static int gen_validp (int gen) { /* is preset generator valid? */ int i = 0; if (!gen_valid (gen)) return false; while (badpgen[i] && badpgen[i] != (unsigned short) gen) i++; return (badpgen[i] == 0); } //--------------------------------------------------------- // gen_inlist // Find generator in gen list //--------------------------------------------------------- static SFGen* gen_inlist (int gen, const QList& genlist) { foreach(SFGen* p, genlist) { if (p == 0) break; if (gen == p->id) return p; } return 0; } /* delete zone from zone list */ static void sfont_zone_delete (QList* l, Zone * zone) { l->removeOne(zone); delete zone; } /* ------------------------------------------------------------------- * preset generator loader * generator (per preset) loading rules: * Zones with no generators or modulators shall be annihilated * Global zone must be 1st zone, discard additional ones (instrumentless zones) * * generator (per zone) loading rules (in order of decreasing precedence): * KeyRange is 1st in list (if exists), else discard * if a VelRange exists only preceded by a KeyRange, else discard * if a generator follows an instrument discard it * if a duplicate generator exists replace previous one * ------------------------------------------------------------------- */ void SFont::load_pgen (int size) { foreach(Preset* p, presets) { bool gzone = false; bool discarded = false; QList& zl = p->zones; for (int k = 0; k < zl.size(); ++k) { int level = 0; Zone* z = zl[k]; int i = 0; for (; i < z->gen.size();) { SFGen* dup = 0; bool skip = false; bool drop = false; if ((size -= SFGENSIZE) < 0) throw(QString("1:Preset generator chunk size mismatch")); unsigned short genid = READW(); SFGenAmount genval; if (genid == Gen_KeyRange) { /* nothing precedes */ if (level == 0) { level = 1; genval.range.lo = READB(); genval.range.hi = READB(); } else skip = true; } else if (genid == Gen_VelRange) { // only KeyRange precedes if (level <= 1) { level = 2; genval.range.lo = READB(); genval.range.hi = READB(); } else skip = true; } else if (genid == Gen_Instrument) { /* inst is last gen */ level = 3; genval.uword = READW(); z->instIdx = genval.uword + 1; break; /* break out of generator loop */ } else { level = 2; if (gen_validp (genid)) { /* generator valid? */ genval.sword = READW(); dup = gen_inlist (genid, z->gen); } else skip = true; } if (!skip) { SFGen *g; if (!dup) { /* if gen ! dup alloc new */ g = new SFGen; g->id = genid; z->gen[i] = g; } else { g = dup; // ptr to orig gen drop = true; } g->amount = genval; } else { /* Skip this generator */ discarded = true; drop = true; FSKIPW (); } if (!drop) ++i; else { z->gen.removeAt(i); // drop place holder } } if (level == 3) z->gen.removeAt(i); // drop place holder else { // congratulations its a global zone if (!gzone) { // Prior global zones? gzone = true; // if global zone is not 1st zone, relocate if (k != 0) { Zone* save = zl.takeAt(k); synth->log("Preset \"%s\": Global zone is not first zone", qPrintable(p->name)); zl.prepend(save); continue; } } else { // previous global zone exists, discard synth->log("Preset \"%s\": Discarding invalid global zone", qPrintable(p->name)); sfont_zone_delete(&zl, z); } } for (; i < z->gen.size();) { discarded = true; if ((size -= SFGENSIZE) < 0) throw(QString("2:Preset generator chunk size mismatch")); FSKIP (SFGENSIZE); z->gen.removeAt(i); } } if (discarded) synth->log("Preset \"%s\": Some invalid generators were discarded", qPrintable(p->name)); } /* in case there isn't a terminal record */ if (size == 0) return; size -= SFGENSIZE; if (size != 0) throw(QString("3:Preset generator chunk size mismatch")); FSKIP (SFGENSIZE); /* terminal gen */ } /* instrument header loader */ void SFont::load_ihdr(int size) { Instrument *p, *pr = 0; /* ptr to current & previous instrument */ unsigned short zndx, pzndx = 0; if (size % SFIHDRSIZE || size == 0) /* chunk size is valid? */ throw(QString("Instrumentrument header has invalid size")); size = size / SFIHDRSIZE - 1; if (size == 0) { /* at least one preset + term record */ qWarning("File contains no instruments"); FSKIP (SFIHDRSIZE); return; } for (int i = 0; i < size; i++) { /* load all instrument headers */ p = new Instrument; instruments.append(p); char buffer[21]; READSTR (buffer); /* Possible read failure ^ */ zndx = READW(); if (pr) { /* not first instrument? */ if (zndx < pzndx) throw(QString("Instrument header indices not monotonic")); int i2 = zndx - pzndx; while (i2--) pr->zones.prepend(0); } else if (zndx > 0) { /* 1st inst, warn if ofs >0 */ qWarning("%d instrument zones not referenced, discarding", zndx); } pzndx = zndx; pr = p; /* update instrument ptr */ } FSKIP (20); zndx = READW(); if (zndx < pzndx) throw(QString("Instrumentrument header indices not monotonic")); int i2 = zndx - pzndx; while (i2--) pr->zones.prepend(0); } /* instrument bag loader */ void SFont::load_ibag(int size) { Zone *z, *pz = 0; unsigned short genndx, modndx, pgenndx = 0, pmodndx = 0; if (size % SFBAGSIZE || size == 0) /* size is multiple of SFBAGSIZE? */ throw(QString("Instrumentrument bag chunk size is invalid")); foreach(Instrument* in, instruments) { int n = in->zones.size(); for (int i = 0; i < n; ++i) { if ((size -= SFBAGSIZE) < 0) { throw(QString("Instrument bag chunk size mismatch")); } z = new Zone; in->zones[i] = z; genndx = READW(); modndx = READW(); z->sample = 0; if (pz) { /* if not first zone */ if (genndx < pgenndx) throw(QString("Instrumentrument generator indices not monotonic")); if (modndx < pmodndx) throw(QString("Instrumentrument modulator indices not monotonic")); int i2 = genndx - pgenndx; while (i2--) pz->gen.prepend(0); i2 = modndx - pmodndx; while (i2--) pz->mod.prepend(0); } pz = z; /* update previous zone ptr */ pgenndx = genndx; pmodndx = modndx; } } size -= SFBAGSIZE; if (size != 0) throw(QString("Instrumentrument chunk size mismatch")); genndx = READW(); modndx = READW(); if (!pz) { /* in case that all are no zoners */ if (genndx > 0) qWarning("No instrument generators and terminal index not 0"); if (modndx > 0) qWarning("No instrument modulators and terminal index not 0"); return; } if (genndx < pgenndx) throw(QString("Instrumentrument generator indices not monotonic")); if (modndx < pmodndx) throw(QString("Instrumentrument modulator indices not monotonic")); int i = genndx - pgenndx; while (i--) pz->gen.prepend(0); i = modndx - pmodndx; while (i--) pz->mod.prepend(0); } /* instrument modulator loader */ void SFont::load_imod(int size) { foreach(Instrument* i, instruments) { foreach(Zone* p2, i->zones) { for (int k = 0; k < p2->mod.size(); ++k) { if ((size -= SFMODSIZE) < 0) throw(QString("Instrumentrument modulator chunk size mismatch")); SFMod* m = new SFMod; m->src = READW(); m->dest = READW(); m->amount = READW(); m->amtsrc = READW(); m->trans = READW(); p2->mod[k] = m; } } } /* If there isn't even a terminal record Hmmm, the specs say there should be one, but.. */ if (size == 0) { return; } size -= SFMODSIZE; if (size != 0) throw(QString("Instrumentrument modulator chunk size mismatch")); FSKIP (SFMODSIZE); /* terminal mod */ } //--------------------------------------------------------- // load_igen // load instrument generators // (see load_pgen for loading rules) //--------------------------------------------------------- void SFont::load_igen (int size) { foreach(Instrument* instr, instruments) { bool gzone = false; bool discarded = false; QList zl = instr->zones; for (int k = 0; k < zl.size(); ++k) { int level = 0; Zone* z = zl[k]; int i = 0; for (; i < z->gen.size();) { SFGenAmount genval; SFGen* dup = 0; bool skip = false; bool drop = false; if ((size -= SFGENSIZE) < 0) throw(QString("IGEN chunk size mismatch")); unsigned short genid = READW(); if (genid == Gen_KeyRange) { /* nothing precedes */ if (level == 0) { level = 1; genval.range.lo = READB(); genval.range.hi = READB(); } else skip = true; } else if (genid == Gen_VelRange) { // only KeyRange precedes if (level <= 1) { level = 2; genval.range.lo = READB(); genval.range.hi = READB(); } else skip = true; } else if (genid == Gen_SampleId) { // sample is last gen level = 3; genval.uword = READW(); z->sampIdx = genval.uword + 1; break; /* break out of generator loop */ } else { level = 2; if (gen_valid (genid)) { // gen valid? genval.sword = READW(); dup = gen_inlist (genid, z->gen); } else skip = true; } if (!skip) { SFGen* g; if (!dup) { /* if gen ! dup alloc new */ g = new SFGen; g->id = genid; z->gen[i] = g; } else { g = dup; drop = true; } g->amount = genval; } else { /* skip this generator */ discarded = true; drop = true; FSKIPW (); } if (!drop) ++i; else z->gen.removeAt(i); } if (level == 3) z->gen.removeAt(i); else { /* its a global zone */ if (!gzone) { gzone = true; /* if global zone is not 1st zone, relocate */ if (k != 0) { Zone* save = zl.takeAt(k); zl.prepend(save); continue; } } else { /* previous global zone exists, discard */ sfont_zone_delete (&zl, z); } } for (; i < z->gen.size();) { discarded = true; if ((size -= SFGENSIZE) < 0) throw(QString("Instrumentrument generator chunk size mismatch")); FSKIP (SFGENSIZE); z->gen.removeAt(i); } } if (discarded && debugMode) qWarning("Instrument: Some invalid generators were discarded"); } /* for those non-terminal record cases, grr! */ if (size == 0) return; size -= SFGENSIZE; if (size != 0) throw(QString("IGEN chunk size mismatch")); FSKIP (SFGENSIZE); /* terminal gen */ } //--------------------------------------------------------- // load_shdr // sample header loader //--------------------------------------------------------- void SFont::load_shdr (int size) { if (size % SFSHDRSIZE || size == 0) /* size is multiple of SHDR size? */ throw(QString("Sample header has invalid size")); size = size / SFSHDRSIZE - 1; if (size == 0) { // at least one sample + term record? qWarning("File contains no samples"); FSKIP (SFSHDRSIZE); return; } /* load all sample headers */ for (int i = 0; i < size; i++) { Sample* p = new Sample(this); sample.append(p); char buffer[21]; READSTR (buffer); // READSTR (p->name); READD (p->start); READD (p->end); /* - end, loopstart and loopend */ READD (p->loopstart); /* - will be checked and turned into */ READD (p->loopend); READD (p->samplerate); p->origpitch = READB(); p->pitchadj = READC(); FSKIPW (); // skip sample link p->sampletype = READW(); if (p->sampletype & FLUID_SAMPLETYPE_ROM) { p->setValid(false); continue; } if ((p->end > getSamplesize()) || (p->start > (p->end - 4))) { qWarning("Sample start/end file positions are invalid, disabling"); p->setValid(false); continue; } p->setValid(true); if (p->loopend > p->end || p->loopstart >= p->loopend || p->loopstart <= p->start) { /* loop is fowled?? (cluck cluck :) */ /* can pad loop by 8 samples and ensure at least 4 for loop (2*8+4) */ if ((p->end - p->start) >= 20) { p->loopstart = p->start + 8; p->loopend = p->end - 8; } else { // loop is fowled, sample is tiny (can't pad 8 samples) p->loopstart = p->start + 1; p->loopend = p->end - 1; } } if (p->end - p->start < 8) p->setValid(false); } FSKIP (SFSHDRSIZE); /* skip terminal shdr */ } //--------------------------------------------------------- // fixup_pgen // "fixup" (inst # -> inst ptr) instrument references // in preset list //--------------------------------------------------------- void SFont::fixup_pgen() { foreach(Preset* p, presets) { foreach(Zone* z, p->zones) { if (z->instIdx) { // load instrument # z->instrument = instruments[z->instIdx-1]; if (!z->instrument) throw(QString("Preset %1 %2: Invalid instrument reference").arg(p->bank).arg(p->num)); } } } } /* "fixup" (sample # -> sample ptr) sample references in instrument list */ void SFont::fixup_igen() { foreach(Instrument* p, instruments) { foreach(Zone* z, p->zones) { if (z->sampIdx) { z->sample = sample[z->sampIdx - 1]; if (!z->sample) throw(QString("Instrument: Invalid sample reference")); // throw(QString("Instrument <%1>: Invalid sample reference").arg(p->name)); } } } } //--------------------------------------------------------- // safe_fread //--------------------------------------------------------- void SFont::safe_fread(void* buf, int count) { if (f.read((char*)buf, count) != count) { if (f.atEnd()) throw(QString("EOF while attemping to read %1 bytes").arg(count)); else throw(QString("File read failed")); } } //--------------------------------------------------------- // safe_fseek //--------------------------------------------------------- void SFont::safe_fseek(long ofs) { qint64 newpos = ofs + f.pos(); if (f.seek(newpos) == -1) throw(QString("File seek failed with offset = %1").arg(ofs)); } }