MuseScore/fluid/sfont.cpp
Eric Fontaine 4253d779cf fix #291756 explicitly-signed char fluid pitchadj
C standards say that "char" may either be a "signed char" or "unsigned char" but that it is up to the compilers implementation or the platform which is followed.  Some non x86 platforms, including PowerPC and ARM, treat unspecified chars as unsigned chars, so it is necessary to explicitly declare them as "signed char" (or to compile with "--signed_chars").

This fix ensures that fluid synth's sample's pitchadj value are correctly read as signed.
2019-07-04 19:36:33 -04:00

1730 lines
61 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()
{
for(Sample* s : sample)
delete s;
for(Preset* p : presets)
delete p;
for(unsigned char* p : infos)
delete[] p;
for(Instrument* i : instruments) {
// for(Zone* z : i->zones)
// delete z;
delete i;
}
}
//---------------------------------------------------------
// read
//---------------------------------------------------------
bool SFont::read(const QString& s)
{
f.setFileName(s);
if (!load())
return false;
synth->setLoadProgress(0);
for (auto instrument : instruments) {
if (synth->loadWasCanceled())
return false;
if (!instrument->import_sfont())
return false;
}
for (auto preset : presets) {
if (synth->loadWasCanceled())
return false;
if (!preset->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;
for(Zone* z : zones)
delete z;
}
//---------------------------------------------------------
// loadSamples
// this is called if the preset is associated with a
// channel
//---------------------------------------------------------
void Preset::loadSamples()
{
bool locked = sfont->synth->mutex.tryLock();
if (_global_zone && _global_zone->instrument) {
Instrument* i = _global_zone->instrument;
if (i->global_zone && i->global_zone->sample)
i->global_zone->sample->load();
for (Zone* iz : i->zones)
iz->sample->load();
}
int currentInstrZone = 0;
float instrSize = (float)zones.size(); //float is used to properly calculate progress
for (Zone* z : zones) {
sfont->synth->setLoadProgress(currentInstrZone++ / instrSize * 100);
Instrument* i = z->instrument;
if (i->global_zone && i->global_zone->sample)
i->global_zone->sample->load();
for (Zone* iz : i->zones) {
if (sfont->synth->globalTerminate()) {
if (locked)
sfont->synth->mutex.unlock();
return;
}
iz->sample->load();
}
}
if (locked)
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 */
for (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 */
for(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){
for(Mod* mod1 : global_inst_zone->modlist)
mod_list[mod_list_count++] = mod1;
}
/* local instrument zone, modulators.
* Replace modulators with the same definition in the list:
* SF 2.01 page 69, 'bullet' 8
*/
for(Mod* mod1 : 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(mod1, mod_list[i])){
mod_list[i] = 0;
}
}
/* Finally add the new modulator to to the list. */
mod_list[mod_list_count++] = mod1;
}
/* 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){
for(Mod* mod1 : global_preset_zone->modlist)
mod_list[mod_list_count++] = mod1;
}
/* 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) */
for(Mod* mod1 : preset_zone->modlist) {
for (int i = 0; i < mod_list_count; i++) {
if (mod_list[i] && test_identity(mod1,mod_list[i]))
mod_list[i] = 0;
}
/* Finally add the new modulator to the list. */
mod_list[mod_list_count++] = mod1;
}
/* 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;
for(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;
for(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()
{
for(Mod* m : modlist)
delete m;
for(SFGen* p : gen)
delete p;
for(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;
for(Zone* z : zones)
delete z;
}
//---------------------------------------------------------
// importZone
//---------------------------------------------------------
bool Zone::importZone()
{
for (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)
for(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
std::vector<char> p;
p.resize(size);
if (fd.read(p.data(), size) != size) {
qDebug("read %d failed", size);
return;
}
decompressOggVorbis(p.data(), size);
#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
//---------------------------------------------------------
signed char SFont::READC()
{
signed 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);
if (id == INAM_ID)
_fontName = QString(reinterpret_cast<char*>(item + 1));
}
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"));
for(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)
{
for (Preset* p : presets) {
for(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)
{
for(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)
{
for(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;
genval.uword = 0; // Initialize to prevent "potentially uninitialized local variable" warning in VS.
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"));
for(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)
{
for(Instrument* i : instruments) {
for(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)
{
for(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;
genval.uword = 0; // Initialize to prevent "potentially uninitialized local variable" warning in VS.
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()
{
for(Preset* p : presets) {
for(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()
{
for(Instrument* p : instruments) {
for(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));
}
}