You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
530 lines
12 KiB
530 lines
12 KiB
# /*
|
|
|
|
Copyright (C) 2001 Stefan Westerfeld
|
|
stefan@space.twc.de
|
|
|
|
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; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
#include "artsmodulessynth.h"
|
|
#include <sys/stat.h>
|
|
#include <stdsynthmodule.h>
|
|
#include <unistd.h>
|
|
#include <math.h>
|
|
#include <debug.h>
|
|
#include <cache.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
using namespace std;
|
|
|
|
namespace Arts {
|
|
|
|
namespace PatchLoader {
|
|
typedef unsigned char byte;
|
|
typedef unsigned short int word;
|
|
typedef unsigned int dword;
|
|
typedef char sbyte;
|
|
typedef short int sword;
|
|
typedef int sdword;
|
|
|
|
static int pos = 0;
|
|
static int apos = 0;
|
|
|
|
inline void xRead(FILE *file, int len, void *data)
|
|
{
|
|
// printf("(0x%2x) - 0x%02x ... reading %d bytes\n",apos,pos,len);
|
|
pos += len;
|
|
apos += len;
|
|
if(fread(data, len, 1, file) != 1)
|
|
fprintf(stdout, "short read\n");
|
|
}
|
|
|
|
inline void skip(FILE *file, int len)
|
|
{
|
|
// printf("(0x%2x) - 0x%02x ... skipping %d bytes\n",apos,pos,len);
|
|
pos += len;
|
|
apos += len;
|
|
while(len > 0)
|
|
{
|
|
char junk;
|
|
if(fread(&junk, 1, 1, file) != 1)
|
|
fprintf(stdout, "short read\n");
|
|
len--;
|
|
}
|
|
}
|
|
|
|
|
|
inline void readBytes(FILE *file, unsigned char *bytes, int len)
|
|
{
|
|
xRead(file, len, bytes);
|
|
}
|
|
|
|
inline void readString(FILE *file, char *str, int len)
|
|
{
|
|
xRead(file, len, str);
|
|
}
|
|
|
|
/* readXXX with sizeof(xxx) == 1 */
|
|
inline void readByte(FILE *file, byte& b)
|
|
{
|
|
xRead(file, 1, &b);
|
|
}
|
|
|
|
/* readXXX with sizeof(xxx) == 2 */
|
|
inline void readWord(FILE *file, word& w)
|
|
{
|
|
byte h, l;
|
|
xRead(file, 1, &l);
|
|
xRead(file, 1, &h);
|
|
w = (h << 8) + l;
|
|
}
|
|
|
|
inline void readSWord(FILE *file, sword& sw)
|
|
{
|
|
word w;
|
|
readWord(file, w);
|
|
sw = (sword)w;
|
|
}
|
|
|
|
/* readXXX with sizeof(xxx) == 4 */
|
|
inline void readDWord(FILE *file, dword& dw)
|
|
{
|
|
byte h, l, hh, hl;
|
|
xRead(file, 1, &l);
|
|
xRead(file, 1, &h);
|
|
xRead(file, 1, &hl);
|
|
xRead(file, 1, &hh);
|
|
dw = (hh << 24) + (hl << 16) + (h << 8) + l;
|
|
}
|
|
|
|
struct PatHeader {
|
|
char id[12]; /* ID='GF1PATCH110' */
|
|
char manufacturer_id[10]; /* Manufacturer ID */
|
|
char description[60]; /* Description of the contained Instruments
|
|
or copyright of manufacturer. */
|
|
byte instruments; /* Number of instruments in this patch */
|
|
byte voices; /* Number of voices for sample */
|
|
byte channels; /* Number of output channels
|
|
(1=mono,2=stereo) */
|
|
word waveforms; /* Number of waveforms */
|
|
word mastervolume; /* Master volume for all samples */
|
|
dword size; /* Size of the following data */
|
|
char reserved[36]; /* reserved */
|
|
|
|
PatHeader(FILE *file)
|
|
{
|
|
readString(file, id, 12);
|
|
readString(file, manufacturer_id, 10);
|
|
readString(file, description, 60);
|
|
/* skip(file, 2);*/
|
|
|
|
readByte(file, instruments);
|
|
readByte(file, voices);
|
|
readByte(file, channels);
|
|
|
|
readWord(file, waveforms);
|
|
readWord(file, mastervolume);
|
|
readDWord(file, size);
|
|
|
|
readString(file, reserved, 36);
|
|
}
|
|
};
|
|
|
|
struct PatInstrument {
|
|
word number;
|
|
char name[16];
|
|
dword size; /* Size of the whole instrument in bytes. */
|
|
byte layers;
|
|
char reserved[40];
|
|
|
|
/* layer? */
|
|
word layerUnknown;
|
|
dword layerSize;
|
|
byte sampleCount; /* number of samples in this layer (?) */
|
|
char layerReserved[40];
|
|
|
|
PatInstrument(FILE *file)
|
|
{
|
|
readWord(file, number);
|
|
readString(file, name, 16);
|
|
readDWord(file, size);
|
|
readByte(file, layers);
|
|
readString(file, reserved, 40);
|
|
|
|
/* layer: (?) */
|
|
readWord(file, layerUnknown);
|
|
readDWord(file, layerSize);
|
|
readByte(file, sampleCount);
|
|
readString(file, reserved, 40);
|
|
}
|
|
};
|
|
|
|
struct PatPatch {
|
|
char filename[7]; /* Wave file name */
|
|
byte fractions; /* Fractions */
|
|
dword wavesize; /* Wave size.
|
|
Size of the wave digital data */
|
|
dword loopStart;
|
|
dword loopEnd;
|
|
word sampleRate;
|
|
dword minFreq;
|
|
dword maxFreq;
|
|
dword origFreq;
|
|
sword fineTune;
|
|
byte balance;
|
|
byte filterRate[6];
|
|
byte filterOffset[6];
|
|
byte tremoloSweep;
|
|
byte tremoloRate;
|
|
byte tremoloDepth;
|
|
byte vibratoSweep;
|
|
byte vibratoRate;
|
|
byte vibratoDepth;
|
|
byte waveFormat;
|
|
sword freqScale;
|
|
word freqScaleFactor;
|
|
char reserved[36];
|
|
|
|
PatPatch(FILE *file)
|
|
{
|
|
readString(file, filename, 7);
|
|
readByte(file, fractions);
|
|
readDWord(file, wavesize);
|
|
readDWord(file, loopStart);
|
|
readDWord(file, loopEnd);
|
|
readWord(file, sampleRate);
|
|
readDWord(file, minFreq);
|
|
readDWord(file, maxFreq);
|
|
readDWord(file, origFreq);
|
|
readSWord(file, fineTune);
|
|
readByte(file, balance);
|
|
readBytes(file, filterRate, 6);
|
|
readBytes(file, filterOffset, 6);
|
|
readByte(file, tremoloSweep);
|
|
readByte(file, tremoloRate);
|
|
readByte(file, tremoloDepth);
|
|
readByte(file, vibratoSweep);
|
|
readByte(file, vibratoRate);
|
|
readByte(file, vibratoDepth);
|
|
readByte(file, waveFormat);
|
|
readSWord(file, freqScale);
|
|
readWord(file, freqScaleFactor);
|
|
readString(file, reserved, 36);
|
|
}
|
|
};
|
|
}
|
|
|
|
class CachedPat : public CachedObject
|
|
{
|
|
protected:
|
|
struct stat oldstat;
|
|
string filename;
|
|
bool initOk;
|
|
long dataSize;
|
|
|
|
CachedPat(Cache *cache, const string& filename);
|
|
~CachedPat();
|
|
|
|
public:
|
|
|
|
struct Data {
|
|
PatchLoader::PatPatch patch;
|
|
mcopbyte *rawdata;
|
|
Data(FILE *file)
|
|
: patch(file)
|
|
{
|
|
rawdata = new mcopbyte[patch.wavesize];
|
|
fread(rawdata, 1, patch.wavesize, file);
|
|
|
|
// sign conversion only for 16bit!
|
|
if(patch.waveFormat & (1 << 1))
|
|
{
|
|
for(unsigned int i = 1; i < patch.wavesize; i+=2)
|
|
rawdata[i] ^= 0x80;
|
|
}
|
|
|
|
// unfold ping-pong loops
|
|
if(patch.waveFormat & (1 << 3))
|
|
{
|
|
int looplen = patch.loopEnd - patch.loopStart;
|
|
arts_assert(looplen > 0);
|
|
|
|
mcopbyte *newdata = new mcopbyte[patch.wavesize + looplen];
|
|
|
|
// copy head
|
|
memcpy(&newdata[0], &rawdata[0], patch.loopStart + looplen);
|
|
|
|
// 16 bit unfolding only:
|
|
for(int i=0; i<looplen; i+=2)
|
|
{
|
|
newdata[patch.loopStart+looplen+i] =
|
|
newdata[patch.loopStart+looplen-i-2];
|
|
newdata[patch.loopStart+looplen+i+1] =
|
|
newdata[patch.loopStart+looplen-i-1];
|
|
}
|
|
|
|
// copy tail:
|
|
memcpy(&newdata[patch.loopStart+2*looplen],
|
|
&rawdata[patch.loopStart+looplen],
|
|
patch.wavesize - patch.loopEnd);
|
|
|
|
delete[] rawdata;
|
|
rawdata = newdata;
|
|
|
|
patch.wavesize += looplen;
|
|
patch.loopEnd += looplen;
|
|
patch.waveFormat &= ~(1 << 3);
|
|
}
|
|
}
|
|
~Data()
|
|
{
|
|
delete[] rawdata;
|
|
}
|
|
};
|
|
|
|
list<Data*> dList;
|
|
|
|
static CachedPat *load(Cache *cache, const string& filename);
|
|
/**
|
|
* validity test for the cache - returns false if the object is having
|
|
* reflecting the correct contents anymore (e.g. if the file on the
|
|
* disk has changed), and there is no point in keeping it in the cache any
|
|
* longer
|
|
*/
|
|
bool isValid();
|
|
/**
|
|
* memory usage for the cache
|
|
*/
|
|
int memoryUsage();
|
|
};
|
|
|
|
CachedPat *CachedPat::load(Cache *cache, const string& filename)
|
|
{
|
|
CachedPat *pat;
|
|
|
|
pat = (CachedPat *)cache->get(string("CachedPat:")+filename);
|
|
if(!pat) {
|
|
pat = new CachedPat(cache, filename);
|
|
|
|
if(!pat->initOk) // loading failed
|
|
{
|
|
pat->decRef();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return pat;
|
|
}
|
|
|
|
bool CachedPat::isValid()
|
|
{
|
|
if(!initOk)
|
|
return false;
|
|
|
|
struct stat newstat;
|
|
|
|
lstat(filename.c_str(),&newstat);
|
|
return(newstat.st_mtime == oldstat.st_mtime);
|
|
}
|
|
|
|
int CachedPat::memoryUsage()
|
|
{
|
|
return dataSize;
|
|
}
|
|
|
|
CachedPat::CachedPat(Cache *cache, const string& filename)
|
|
: CachedObject(cache), filename(filename), initOk(false), dataSize(0)
|
|
{
|
|
setKey(string("CachedPat:")+filename);
|
|
|
|
if(lstat(filename.c_str(),&oldstat) == -1)
|
|
{
|
|
arts_info("CachedPat: Can't stat file '%s'", filename.c_str());
|
|
return;
|
|
}
|
|
|
|
FILE *patfile = fopen(filename.c_str(), "r");
|
|
if(patfile)
|
|
{
|
|
//PatchLoader::PatHeader header(patfile);
|
|
PatchLoader::PatInstrument ins(patfile);
|
|
|
|
for(int i=0;i<ins.sampleCount;i++)
|
|
{
|
|
Data *data = new Data(patfile);
|
|
dList.push_back(data);
|
|
dataSize += data->patch.wavesize;
|
|
}
|
|
fclose(patfile);
|
|
|
|
arts_debug("loaded pat %s",filename.c_str());
|
|
arts_debug(" %d patches, datasize total is %d bytes",
|
|
ins.sampleCount, dataSize);
|
|
|
|
initOk = true;
|
|
}
|
|
}
|
|
|
|
CachedPat::~CachedPat()
|
|
{
|
|
while(!dList.empty())
|
|
{
|
|
delete dList.front();
|
|
dList.pop_front();
|
|
}
|
|
}
|
|
|
|
class Synth_PLAY_PAT_impl : virtual public Synth_PLAY_PAT_skel,
|
|
virtual public StdSynthModule
|
|
{
|
|
protected:
|
|
string _filename;
|
|
CachedPat *pat;
|
|
CachedPat::Data *selected;
|
|
float fpos;
|
|
|
|
public:
|
|
Synth_PLAY_PAT_impl()
|
|
{
|
|
pat = 0;
|
|
selected = 0;
|
|
}
|
|
|
|
void unload()
|
|
{
|
|
if(pat)
|
|
{
|
|
pat->decRef();
|
|
pat = 0;
|
|
}
|
|
}
|
|
|
|
~Synth_PLAY_PAT_impl()
|
|
{
|
|
unload();
|
|
}
|
|
|
|
void streamStart()
|
|
{
|
|
fpos = 0.0;
|
|
selected = 0;
|
|
}
|
|
|
|
void calculateBlock(unsigned long samples)
|
|
{
|
|
/* the normal offset is 60
|
|
60 = 12*5
|
|
so we scale with 2^5 = 32
|
|
*/
|
|
float freq = frequency[0]; /* * 32.0 / 2.0; * why /2.0? */
|
|
int ifreq = (int)(freq * 1024.0);
|
|
if(!selected && pat)
|
|
{
|
|
int bestdiff = 20000 * 1024;
|
|
|
|
list<CachedPat::Data*>::iterator i;
|
|
for(i = pat->dList.begin(); i != pat->dList.end(); i++)
|
|
{
|
|
int diff = ::abs(double(ifreq - (*i)->patch.origFreq));
|
|
if(diff < bestdiff)
|
|
{
|
|
selected = *i;
|
|
bestdiff = diff;
|
|
}
|
|
}
|
|
|
|
/* drums */
|
|
if(selected && selected->patch.freqScaleFactor == 0)
|
|
ifreq = selected->patch.origFreq;
|
|
}
|
|
if(selected)
|
|
{
|
|
const PatchLoader::PatPatch& patch = selected->patch;
|
|
|
|
/*
|
|
* thats just a *guess*, I have no idea how tuning actually
|
|
* should work
|
|
*/
|
|
#if 0
|
|
float tonetune = float(pat.fineTune)/float(pat.freqScaleFactor);
|
|
float tuning = pow(2.0, tonetune/12.0);
|
|
freq *= tuning;
|
|
#endif
|
|
//printf("tonetune: %f\n",tonetune);
|
|
//printf("tuning: %f\n",tuning);
|
|
|
|
float step = double(patch.sampleRate)/samplingRateFloat *
|
|
float(ifreq) / float(patch.origFreq);
|
|
for(unsigned int i = 0; i < samples; i++)
|
|
{
|
|
// ASSUME 16bit signed, native byte order
|
|
int ifpos = int(fpos)*2;
|
|
|
|
// looped? bidi? backw?
|
|
if((patch.waveFormat & ((1 << 2) + (1 << 3) + (1 << 4)))
|
|
== (1 << 2))
|
|
{
|
|
while(ifpos >= signed(patch.loopEnd))
|
|
{
|
|
ifpos -= (patch.loopEnd - patch.loopStart);
|
|
fpos -= (patch.loopEnd - patch.loopStart)/2;
|
|
}
|
|
}
|
|
|
|
short int *x = (short int *)&selected->rawdata[ifpos];
|
|
float sample = (ifpos >= 0 && ifpos < signed(patch.wavesize))?
|
|
x[0]/32768.0:0.0;
|
|
float sample1 = ((ifpos+2) >= 0 && (ifpos+2) < signed(patch.wavesize))?
|
|
x[1]/32768.0:0.0;
|
|
float error = fpos - (int)fpos;
|
|
outvalue[i] = sample * (1.0-error) + sample1 * error;
|
|
|
|
fpos += step;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(unsigned int i = 0; i < samples; i++)
|
|
outvalue[i] = 0.0;
|
|
}
|
|
}
|
|
|
|
void clearDList()
|
|
{
|
|
selected = 0;
|
|
|
|
}
|
|
|
|
string filename() { return _filename; }
|
|
void filename(const string& newFile)
|
|
{
|
|
if(newFile == _filename) return;
|
|
|
|
unload();
|
|
pat = CachedPat::load(Cache::the(), newFile);
|
|
|
|
_filename = newFile;
|
|
filename_changed(newFile);
|
|
}
|
|
};
|
|
|
|
REGISTER_IMPLEMENTATION(Synth_PLAY_PAT_impl);
|
|
|
|
}
|