370 lines
12 KiB
C++
370 lines
12 KiB
C++
//=============================================================================
|
|
// Zerberos
|
|
// Zample player
|
|
//
|
|
// Copyright (C) 2013 Werner Schweer
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License version 2
|
|
// as published by the Free Software Foundation and appearing in
|
|
// the file LICENCE.GPL
|
|
//=============================================================================
|
|
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QStringList>
|
|
#include <sndfile.h>
|
|
|
|
#include "libmscore/xml.h"
|
|
// #include "zip/qzipreader_p.h"
|
|
|
|
#include "instrument.h"
|
|
#include "zone.h"
|
|
#include "sample.h"
|
|
#include "zerberus.h"
|
|
|
|
//---------------------------------------------------------
|
|
// SfzRegion
|
|
//---------------------------------------------------------
|
|
|
|
struct SfzRegion {
|
|
QString path;
|
|
double amp_veltrack;
|
|
double ampeg_release;
|
|
double rt_decay;
|
|
QString sample;
|
|
int lochan, hichan;
|
|
int lokey, hikey, lovel, hivel, pitch_keycenter;
|
|
double hirand, lorand;
|
|
int on_locc[128];
|
|
int on_hicc[128];
|
|
int locc[128];
|
|
int hicc[128];
|
|
int off_by;
|
|
int group;
|
|
int seq_length, seq_position;
|
|
double volume;
|
|
int octave_offset, note_offset;
|
|
int tune, transpose;
|
|
Trigger trigger;
|
|
LoopMode loop_mode;
|
|
OffMode off_mode;
|
|
|
|
void init(const QString&);
|
|
bool isEmpty() const { return sample.isEmpty(); }
|
|
int readKey(const QByteArray&) const;
|
|
void readOp(const QByteArray&);
|
|
void setZone(Zone*) const;
|
|
};
|
|
|
|
//---------------------------------------------------------
|
|
// init
|
|
//---------------------------------------------------------
|
|
|
|
void SfzRegion::init(const QString& _path)
|
|
{
|
|
path = _path;
|
|
lochan = 1;
|
|
hichan = 16;
|
|
amp_veltrack = 100;
|
|
ampeg_release = 0.0; // in sec
|
|
rt_decay = 0.0; // dB /sec
|
|
lokey = 0;
|
|
hikey = 127;
|
|
lovel = 0;
|
|
hivel = 127;
|
|
pitch_keycenter = 60;
|
|
sample = "";
|
|
lorand = 0.0;
|
|
hirand = 1.0;
|
|
group = 0;
|
|
off_by = 0;
|
|
volume = 1.0;
|
|
note_offset = 0;
|
|
octave_offset = 0;
|
|
seq_length = 1;
|
|
seq_position = 1;
|
|
trigger = Trigger::ATTACK;
|
|
loop_mode = LoopMode::NO_LOOP;
|
|
tune = 0;
|
|
transpose = 0;
|
|
for (int i = 0; i < 128; ++i) {
|
|
on_locc[i] = -1;
|
|
on_hicc[i] = -1;
|
|
locc[i] = 0;
|
|
hicc[i] = 127;
|
|
}
|
|
off_mode = OffMode::FAST;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setZone
|
|
// set Zone from sfzRegion
|
|
//---------------------------------------------------------
|
|
|
|
void SfzRegion::setZone(Zone* z) const
|
|
{
|
|
z->keyLo = lokey;
|
|
z->keyHi = hikey;
|
|
z->veloLo = lovel;
|
|
z->veloHi = hivel;
|
|
z->keyBase = pitch_keycenter;
|
|
z->offset = 0;
|
|
z->volume = pow(10.0, volume / 20.0);
|
|
z->ampVeltrack = amp_veltrack;
|
|
z->ampegRelease = ampeg_release * 1000;
|
|
z->seqPos = seq_position - 1;
|
|
z->seqLen = seq_length - 1;
|
|
z->seq = 0;
|
|
z->trigger = trigger;
|
|
z->loopMode = loop_mode;
|
|
z->tune = tune + transpose * 100;
|
|
z->rtDecay = rt_decay;
|
|
for (int i = 0; i < 128; ++i) {
|
|
z->onLocc[i] = on_locc[i];
|
|
z->onHicc[i] = on_hicc[i];
|
|
z->locc[i] = locc[i];
|
|
z->hicc[i] = hicc[i];
|
|
}
|
|
z->offMode = off_mode;
|
|
z->offBy = off_by;
|
|
z->group = group;
|
|
if (note_offset || octave_offset) {
|
|
printf("=========================offsets %d %d\n", note_offset, octave_offset);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readKey
|
|
//---------------------------------------------------------
|
|
|
|
int SfzRegion::readKey(const QByteArray& s) const
|
|
{
|
|
bool ok;
|
|
int i = s.toInt(&ok);
|
|
if (ok)
|
|
return i;
|
|
switch (tolower(s[0])) {
|
|
case 'c': i = 0; break;
|
|
case 'd': i = 2; break;
|
|
case 'e': i = 4; break;
|
|
case 'f': i = 5; break;
|
|
case 'g': i = 7; break;
|
|
case 'a': i = 9; break;
|
|
case 'b': i = 11; break;
|
|
default:
|
|
qDebug("SfzRegion: Not a note: %s", qPrintable(s));
|
|
return 0;
|
|
}
|
|
int idx = 1;
|
|
if (s[idx] == '#') {
|
|
i++;
|
|
++idx;
|
|
}
|
|
else if (tolower(s[idx]) == 'b') {
|
|
i--;
|
|
++idx;
|
|
}
|
|
int octave = s.mid(idx).toInt(&ok);
|
|
if (!ok) {
|
|
qDebug("SfzRegion: Not a note: %s", qPrintable(s));
|
|
return 0;
|
|
}
|
|
i += (octave + 1) * 12;
|
|
return i;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// addRegion
|
|
//---------------------------------------------------------
|
|
|
|
void ZInstrument::addRegion(SfzRegion& r)
|
|
{
|
|
for (int i = 0; i < 128; ++i) {
|
|
if (r.on_locc[i] != -1 || r.on_hicc[i] != -1) {
|
|
r.trigger = Trigger::CC;
|
|
break;
|
|
}
|
|
}
|
|
Zone* z = new Zone;
|
|
r.setZone(z);
|
|
z->sample = readSample(r.sample, 0);
|
|
if (z->sample)
|
|
addZone(z);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readDouble
|
|
//---------------------------------------------------------
|
|
|
|
static void readDouble(const QByteArray& data, double* val)
|
|
{
|
|
bool ok;
|
|
double d = data.toDouble(&ok);
|
|
if (ok)
|
|
*val = d;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readOp
|
|
//---------------------------------------------------------
|
|
|
|
void SfzRegion::readOp(const QByteArray& bb)
|
|
{
|
|
QList<QByteArray> b2 = bb.split('=');
|
|
if (b2.size() != 2)
|
|
return;
|
|
QByteArray b = b2[0];
|
|
QByteArray data = b2[1];
|
|
int i = data.toInt();
|
|
|
|
if (b == "amp_veltrack") readDouble(data, &_veltrack);
|
|
else if (b == "ampeg_release") readDouble(data, &eg_release);
|
|
else if (b == "sample") {
|
|
sample = path + "/" + b2[1];
|
|
sample.replace("\\", "/");
|
|
}
|
|
else if (b == "key") {
|
|
lokey = readKey(data);
|
|
hikey = lokey;
|
|
pitch_keycenter = lokey;
|
|
}
|
|
else if (b == "pitch_keytrack")
|
|
;
|
|
else if (b == "trigger") {
|
|
if (data == "attack")
|
|
trigger = Trigger::ATTACK;
|
|
else if (data == "release")
|
|
trigger = Trigger::RELEASE;
|
|
else if (data == "first")
|
|
trigger = Trigger::FIRST;
|
|
else if (data == "legato")
|
|
trigger = Trigger::LEGATO;
|
|
else
|
|
qDebug("SfzRegion: bad trigger value: %s", qPrintable(data));
|
|
}
|
|
else if (b == "loop_mode") {
|
|
if (data == "no_loop")
|
|
loop_mode = LoopMode::NO_LOOP;
|
|
else if (data == "one_shot")
|
|
loop_mode = LoopMode::ONE_SHOT;
|
|
else if (data == "loop_continuous")
|
|
loop_mode = LoopMode::CONTINUOUS;
|
|
else if (data == "loop_sustain")
|
|
loop_mode = LoopMode::SUSTAIN;
|
|
if (loop_mode != LoopMode::ONE_SHOT)
|
|
qDebug("SfzRegion: loop_mode <%s>", qPrintable(data));
|
|
}
|
|
else if (b.startsWith("on_locc")) {
|
|
int idx = b.mid(7).toInt();
|
|
if (idx >= 0 && idx < 128)
|
|
on_locc[idx] = i;
|
|
}
|
|
else if (b.startsWith("on_hicc")) {
|
|
int idx = b.mid(7).toInt();
|
|
if (idx >= 0 && idx < 128)
|
|
on_hicc[idx] = i;
|
|
}
|
|
else if (b.startsWith("locc")) {
|
|
int idx = b.mid(7).toInt();
|
|
if (idx >= 0 && idx < 128)
|
|
locc[idx] = i;
|
|
}
|
|
else if (b.startsWith("hicc")) {
|
|
int idx = b.mid(7).toInt();
|
|
if (idx >= 0 && idx < 128)
|
|
hicc[idx] = i;
|
|
}
|
|
else if (b == "off_mode") {
|
|
if (data == "fast")
|
|
off_mode = OffMode::FAST;
|
|
else if (data == "normal")
|
|
off_mode = OffMode::NORMAL;
|
|
}
|
|
else if (b == "tune") tune = i;
|
|
else if (b == "rt_decay") readDouble(data, &rt_decay);
|
|
else if (b == "hirand") readDouble(data, &hirand);
|
|
else if (b == "lorand") readDouble(data, &lorand);
|
|
else if (b == "volume") readDouble(data, &volume);
|
|
else if (b == "pitch_keycenter") pitch_keycenter = readKey(data);
|
|
else if (b == "lokey") lokey = readKey(data);
|
|
else if (b == "hikey") hikey = readKey(data);
|
|
else if (b == "lovel") lovel = i;
|
|
else if (b == "hivel") hivel = i;
|
|
else if (b == "off_by") off_by = i;
|
|
else if (b == "group") group = i;
|
|
else if (b == "lochan") lochan = i;
|
|
else if (b == "hichan") hichan = i;
|
|
else if (b == "note_offset") note_offset = i;
|
|
else if (b == "octave_offset") octave_offset = i;
|
|
else if (b == "seq_length") seq_length = i;
|
|
else if (b == "seq_position") seq_position = i;
|
|
else if (b == "transpose") transpose = i;
|
|
else
|
|
qDebug("SfzRegion: unknown opcode <%s>", qPrintable(b));
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// loadSfz
|
|
//---------------------------------------------------------
|
|
|
|
bool ZInstrument::loadSfz(const QString& s)
|
|
{
|
|
_program = 0;
|
|
QFile f(s);
|
|
if (!f.open(QIODevice::ReadOnly)) {
|
|
qDebug("ZInstrument: cannot load %s", qPrintable(s));
|
|
return false;
|
|
}
|
|
QFileInfo fi(f);
|
|
QString path = fi.absolutePath();
|
|
qint64 total = fi.size();
|
|
|
|
QString sample;
|
|
|
|
SfzRegion r;
|
|
SfzRegion g; // group
|
|
r.init(path);
|
|
g.init(path);
|
|
|
|
bool groupMode = false;
|
|
zerberus->setLoadProgress(0);
|
|
|
|
while (!f.atEnd()) {
|
|
QByteArray ba = f.readLine();
|
|
zerberus->setLoadProgress(((qreal)f.pos() * 100) / total);
|
|
ba = ba.simplified();
|
|
if (ba.isEmpty() || ba.startsWith("//"))
|
|
continue;
|
|
QList<QByteArray> bal = ba.split(' ');
|
|
foreach(const QByteArray& bb, bal) {
|
|
if (bb == "<group>") {
|
|
if (!groupMode && !r.isEmpty())
|
|
addRegion(r);
|
|
g.init(path);
|
|
r.init(path);
|
|
groupMode = true;
|
|
}
|
|
else if (bb == "<region>") {
|
|
if (groupMode) {
|
|
g = r;
|
|
groupMode = false;
|
|
}
|
|
else {
|
|
if (!r.isEmpty())
|
|
addRegion(r);
|
|
r = g; // initialize next region with group values
|
|
}
|
|
}
|
|
else
|
|
r.readOp(bb);
|
|
}
|
|
}
|
|
zerberus->setLoadProgress(100);
|
|
if (!groupMode && !r.isEmpty())
|
|
addRegion(r);
|
|
return true;
|
|
}
|
|
|