MuseScore/fluid/sfont.cpp

1704 lines
60 KiB
C++

/* 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 "libmscore/xml.h"
static bool debugMode = false;
namespace FluidS {
//---------------------------------------------------------
// SFVersion
//---------------------------------------------------------
SFVersion::SFVersion()
{
major = 0;
minor = 0;
}
//---------------------------------------------------------
// SFont
//---------------------------------------------------------
SFont::SFont(Fluid* f)
{
synth = f;
samplepos = 0;
samplesize = 0;
_bankOffset = 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)
{
bank -= _bankOffset;
for (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()
{
// sfont->synth->mutex.lock();
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();
}
// sfont->synth->mutex.unlock();
}
//---------------------------------------------------------
// noteon
//---------------------------------------------------------
bool Preset::noteon(Fluid* synth, unsigned id, int chan, int key, int vel, double nt)
{
Mod* mod;
Mod* mod_list[FLUID_NUM_MOD]; /* list for 'sorting' preset modulators */
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 != 0) && (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. */
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_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_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;
int type;
// mod_dest->next = 0; /* pointer to next modulator, this is the end of the list now.*/
/* *** Amount *** */
mod_dest->amount = mod_src->amount;
/* *** Source *** */
mod_dest->src1 = mod_src->src & 127; /* 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;
else
mod_dest->flags1 |= FLUID_MOD_GC;
/* 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;
else
mod_dest->flags1 |= FLUID_MOD_POSITIVE;
/* 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;
else
mod_dest->flags1 |= FLUID_MOD_UNIPOLAR;
/* modulator source types: SF2.01 section 8.2.1 page 52 */
type = (mod_src->src) >> 10;
type &= 63; /* 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.
*/
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;
else
mod_dest->flags2 |= FLUID_MOD_GC;
/* 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;
else
mod_dest->flags2 |= FLUID_MOD_POSITIVE;
/* 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;
else
mod_dest->flags2 |= FLUID_MOD_UNIPOLAR;
/* 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. */
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 (sampletype & FLUID_SAMPLETYPE_OGG_VORBIS) {
if (!fd.seek(sf->samplePos() + start))
return;
}
else {
if (!fd.seek(sf->samplePos() + start * sizeof(short)))
return;
}
unsigned int size = end - start;
if (sampletype & FLUID_SAMPLETYPE_OGG_VORBIS) {
#ifdef SOUNDFONT3
char* p = new char[size];
if (fd.read(p, size) != size) {
printf(" read %d failed\n", size);
delete[] p;
return;
}
decompressOggVorbis(p, size);
delete[] p;
#endif
}
else {
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;
uchar* cbuf = (uchar*) 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) {
qDebug("fluid: error loading sound font: %s", 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 > 3)
throw(QString("Bad Sound font version %1.%2").arg(_version.major).arg(_version.minor));
}
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 (unsigned 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"));
/* SDTA chunk may also contain sm24 chunk for 24 bit samples
* (not yet supported), only an error if SMPL chunk size is
* greater than SDTA. */
if (chunk.size > size)
throw(QString("SDTA chunk size mismatch %1 != %2").arg(size).arg(chunk.size));
/* sample data follows */
setSamplepos(f.pos());
setSamplesize(chunk.size);
FSKIP(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 = QString::fromLatin1(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<SFGen*>& 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<Zone*>* 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<Zone*>& 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);
qDebug("Preset \"%s\": Global zone is not first zone", qPrintable(p->name));
zl.prepend(save);
continue;
}
}
else { // previous global zone exists, discard
qDebug("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)
qDebug("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<Zone*> 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->sampletype & FLUID_SAMPLETYPE_OGG_VORBIS) {
}
else {
// loop is fowled?? (cluck cluck :)
if (p->loopend > p->end || p->loopstart >= p->loopend || p->loopstart <= p->start) {
/* 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))
throw(QString("File seek failed with offset = %1").arg(ofs));
}
}