summaryrefslogtreecommitdiffstats
path: root/arts/modules/synth/synth_play_pat_impl.cc
diff options
context:
space:
mode:
Diffstat (limited to 'arts/modules/synth/synth_play_pat_impl.cc')
-rw-r--r--arts/modules/synth/synth_play_pat_impl.cc529
1 files changed, 529 insertions, 0 deletions
diff --git a/arts/modules/synth/synth_play_pat_impl.cc b/arts/modules/synth/synth_play_pat_impl.cc
new file mode 100644
index 00000000..4913e991
--- /dev/null
+++ b/arts/modules/synth/synth_play_pat_impl.cc
@@ -0,0 +1,529 @@
+# /*
+
+ 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., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, 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);
+
+}