Merge pull request #154 from softrabbit/stable-0.4

OpulenZ improvements: pitch bend, velocity, aftertouch
This commit is contained in:
Tobias Doerffel
2014-01-25 14:32:26 -08:00
8 changed files with 175 additions and 755 deletions

View File

@@ -1,3 +1,3 @@
INCLUDE(BuildPlugin)
BUILD_PLUGIN(OPL2 opl2instrument.cpp opl2instrument.h opl.h kemuopl.h adlibemu.c adlibemu.h fmopl.c fmopl.h temuopl.cpp temuopl.h MOCFILES opl2instrument.h EMBEDDED_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.png)
BUILD_PLUGIN(OPL2 opl2instrument.cpp opl2instrument.h opl.h fmopl.c fmopl.h temuopl.cpp temuopl.h MOCFILES opl2instrument.h EMBEDDED_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.png)

View File

@@ -1,608 +0,0 @@
/*
* ADLIBEMU.C
* Copyright (C) 1998-2001 Ken Silverman
* Ken Silverman's official web site: "http://www.advsys.net/ken"
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
This file is a digital Adlib emulator for OPL2 and possibly OPL3
Features that could be added in a future version:
- Amplitude and Frequency Vibrato Bits (not hard, but a big speed hit)
- Global Keyboard Split Number Bit (need to research this one some more)
- 2nd Adlib chip for OPL3 (simply need to make my cell array bigger)
- Advanced connection modes of OPL3 (Just need to add more "docell" cases)
- L/R Stereo bits of OPL3 (Need adlibgetsample to return stereo)
Features that aren't worth supporting:
- Anything related to adlib timers&interrupts (Sorry - I always used IRQ0)
- Composite sine wave mode (CSM) (Supported only on ancient cards)
I'm not sure about a few things in my code:
- Attack curve. What function is this anyway? I chose to use an order-3
polynomial to approximate but this doesn't seem right.
- Attack/Decay/Release constants - my constants may not be exact
- What should ADJUSTSPEED be?
- Haven't verified that Global Keyboard Split Number Bit works yet
- Some of the drums don't always sound right. It's pretty hard to guess
the exact waveform of drums when you look at random data which is
slightly randomized due to digital ADC recording.
- Adlib seems to have a lot more treble than my emulator does. I'm not
sure if this is simply unfixable due to the sound blaster's different
filtering on FM and digital playback or if it's a serious bug in my
code.
*/
#include <math.h>
#include <string.h>
#if !defined(max) && !defined(__cplusplus)
#define max(a,b) (((a) > (b)) ? (a) : (b))
#endif
#if !defined(min) && !defined(__cplusplus)
#define min(a,b) (((a) < (b)) ? (a) : (b))
#endif
#define PI 3.141592653589793
#define MAXCELLS 18
#define WAVPREC 2048
static float AMPSCALE=(8192.0);
#define FRQSCALE (49716/512.0)
//Constants for Ken's Awe32, on a PII-266 (Ken says: Use these for KSM's!)
#define MODFACTOR 4.0 //How much of modulator cell goes into carrier
#define MFBFACTOR 1.0 //How much feedback goes back into modulator
#define ADJUSTSPEED 0.75 //0<=x<=1 Simulate finite rate of change of state
//Constants for Ken's Awe64G, on a P-133
//#define MODFACTOR 4.25 //How much of modulator cell goes into carrier
//#define MFBFACTOR 0.5 //How much feedback goes back into modulator
//#define ADJUSTSPEED 0.85 //0<=x<=1 Simulate finite rate of change of state
typedef struct
{
float val, t, tinc, vol, sustain, amp, mfb;
float a0, a1, a2, a3, decaymul, releasemul;
short *waveform;
long wavemask;
void (*cellfunc)(void *, float);
unsigned char flags, dum0, dum1, dum2;
} celltype;
static long numspeakers, bytespersample;
static float recipsamp;
static celltype cell[MAXCELLS];
static signed short wavtable[WAVPREC*3];
static float kslmul[4] = {0.0,0.5,0.25,1.0};
static float frqmul[16] = {.5,1,2,3,4,5,6,7,8,9,10,10,12,12,15,15}, nfrqmul[16];
static unsigned char adlibreg[256], ksl[8][16];
static unsigned char modulatorbase[9] = {0,1,2,8,9,10,16,17,18};
static unsigned char odrumstat = 0;
static unsigned char base2cell[22] = {0,1,2,0,1,2,0,0,3,4,5,3,4,5,0,0,6,7,8,6,7,8};
float lvol[9] = {1,1,1,1,1,1,1,1,1}; //Volume multiplier on left speaker
float rvol[9] = {1,1,1,1,1,1,1,1,1}; //Volume multiplier on right speaker
long lplc[9] = {0,0,0,0,0,0,0,0,0}; //Samples to delay on left speaker
long rplc[9] = {0,0,0,0,0,0,0,0,0}; //Samples to delay on right speaker
long nlvol[9], nrvol[9];
long nlplc[9], nrplc[9];
long rend = 0;
#define FIFOSIZ 256
static float *rptr[9], *nrptr[9];
static float rbuf[9][FIFOSIZ*2];
static float snd[FIFOSIZ*2];
#ifndef USING_ASM
#define _inline
#endif
#ifdef USING_ASM
static _inline void ftol (float f, long *a)
{
_asm
{
mov eax, a
fld f
fistp dword ptr [eax]
}
}
#else
static void ftol(float f, long *a) {
*a=f;
}
#endif
#define ctc ((celltype *)c) //A rare attempt to make code easier to read!
void docell4 (void *c, float modulator) { }
void docell3 (void *c, float modulator)
{
long i;
ftol(ctc->t+modulator,&i);
ctc->t += ctc->tinc;
ctc->val += (ctc->amp*ctc->vol*((float)ctc->waveform[i&ctc->wavemask])-ctc->val)*ADJUSTSPEED;
}
void docell2 (void *c, float modulator)
{
long i;
ftol(ctc->t+modulator,&i);
void *amp_void = &ctc->amp;
long *amp_long = (long *)amp_void;
if (*amp_long <= 0x37800000)
{
ctc->amp = 0;
ctc->cellfunc = docell4;
}
ctc->amp *= ctc->releasemul;
ctc->t += ctc->tinc;
ctc->val += (ctc->amp*ctc->vol*((float)ctc->waveform[i&ctc->wavemask])-ctc->val)*ADJUSTSPEED;
}
void docell1 (void *c, float modulator)
{
long i;
ftol(ctc->t+modulator,&i);
void *amp_void = &ctc->amp;
long *amp_long = (long *)amp_void;
void *sustain_void = &ctc->sustain;
long *sustain_long = (long *)sustain_void;
if (*amp_long <= *sustain_long)
{
if (ctc->flags&32)
{
ctc->amp = ctc->sustain;
ctc->cellfunc = docell3;
}
else
ctc->cellfunc = docell2;
}
else
ctc->amp *= ctc->decaymul;
ctc->t += ctc->tinc;
ctc->val += (ctc->amp*ctc->vol*((float)ctc->waveform[i&ctc->wavemask])-ctc->val)*ADJUSTSPEED;
}
void docell0 (void *c, float modulator)
{
long i;
ftol(ctc->t+modulator,&i);
ctc->amp = ((ctc->a3*ctc->amp + ctc->a2)*ctc->amp + ctc->a1)*ctc->amp + ctc->a0;
void *amp_void = &ctc->amp;
long *amp_long = (long *)amp_void;
if (*amp_long > 0x3f800000)
{
ctc->amp = 1;
ctc->cellfunc = docell1;
}
ctc->t += ctc->tinc;
ctc->val += (ctc->amp*ctc->vol*((float)ctc->waveform[i&ctc->wavemask])-ctc->val)*ADJUSTSPEED;
}
static long waveform[8] = {WAVPREC,WAVPREC>>1,WAVPREC,(WAVPREC*3)>>2,0,0,(WAVPREC*5)>>2,WAVPREC<<1};
static long wavemask[8] = {WAVPREC-1,WAVPREC-1,(WAVPREC>>1)-1,(WAVPREC>>1)-1,WAVPREC-1,((WAVPREC*3)>>2)-1,WAVPREC>>1,WAVPREC-1};
static long wavestart[8] = {0,WAVPREC>>1,0,WAVPREC>>2,0,0,0,WAVPREC>>3};
static float attackconst[4] = {1/2.82624,1/2.25280,1/1.88416,1/1.59744};
static float decrelconst[4] = {1/39.28064,1/31.41608,1/26.17344,1/22.44608};
void cellon (long i, long j, celltype *c, unsigned char iscarrier)
{
long frn, oct, toff;
float f;
frn = ((((long)adlibreg[i+0xb0])&3)<<8) + (long)adlibreg[i+0xa0];
oct = ((((long)adlibreg[i+0xb0])>>2)&7);
toff = (oct<<1) + ((frn>>9)&((frn>>8)|(((adlibreg[8]>>6)&1)^1)));
if (!(adlibreg[j+0x20]&16)) toff >>= 2;
f = pow(2.0,(adlibreg[j+0x60]>>4)+(toff>>2)-1)*attackconst[toff&3]*recipsamp;
c->a0 = .0377*f; c->a1 = 10.73*f+1; c->a2 = -17.57*f; c->a3 = 7.42*f;
f = -7.4493*decrelconst[toff&3]*recipsamp;
c->decaymul = pow(2.0,f*pow(2.0,(adlibreg[j+0x60]&15)+(toff>>2)));
c->releasemul = pow(2.0,f*pow(2.0,(adlibreg[j+0x80]&15)+(toff>>2)));
c->wavemask = wavemask[adlibreg[j+0xe0]&7];
c->waveform = &wavtable[waveform[adlibreg[j+0xe0]&7]];
if (!(adlibreg[1]&0x20)) c->waveform = &wavtable[WAVPREC];
c->t = wavestart[adlibreg[j+0xe0]&7];
c->flags = adlibreg[j+0x20];
c->cellfunc = docell0;
c->tinc = (float)(frn<<oct)*nfrqmul[adlibreg[j+0x20]&15];
c->vol = pow(2.0,((float)(adlibreg[j+0x40]&63) +
(float)kslmul[adlibreg[j+0x40]>>6]*ksl[oct][frn>>6]) * -.125 - 14);
c->sustain = pow(2.0,(float)(adlibreg[j+0x80]>>4) * -.5);
if (!iscarrier) c->amp = 0;
c->mfb = pow(2.0,((adlibreg[i+0xc0]>>1)&7)+5)*(WAVPREC/2048.0)*MFBFACTOR;
if (!(adlibreg[i+0xc0]&14)) c->mfb = 0;
c->val = 0;
}
//This function (and bug fix) written by Chris Moeller
void cellfreq (signed long i, signed long j, celltype *c)
{
long frn, oct;
frn = ((((long)adlibreg[i+0xb0])&3)<<8) + (long)adlibreg[i+0xa0];
oct = ((((long)adlibreg[i+0xb0])>>2)&7);
c->tinc = (float)(frn<<oct)*nfrqmul[adlibreg[j+0x20]&15];
c->vol = pow(2.0,((float)(adlibreg[j+0x40]&63) +
(float)kslmul[adlibreg[j+0x40]>>6]*ksl[oct][frn>>6]) * -.125 - 14);
}
static long initfirstime = 0;
void adlibinit (long dasamplerate, long danumspeakers, long dabytespersample)
{
long i, j, frn, oct;
memset((void *)adlibreg,0,sizeof(adlibreg));
memset((void *)cell,0,sizeof(celltype)*MAXCELLS);
memset((void *)rbuf,0,sizeof(rbuf));
rend = 0; odrumstat = 0;
for(i=0;i<MAXCELLS;i++)
{
cell[i].cellfunc = docell4;
cell[i].amp = 0;
cell[i].vol = 0;
cell[i].t = 0;
cell[i].tinc = 0;
cell[i].wavemask = 0;
cell[i].waveform = &wavtable[WAVPREC];
}
numspeakers = danumspeakers;
bytespersample = dabytespersample;
recipsamp = 1.0 / (float)dasamplerate;
for(i=15;i>=0;i--) nfrqmul[i] = frqmul[i]*recipsamp*FRQSCALE*(WAVPREC/2048.0);
if (!initfirstime)
{
initfirstime = 1;
for(i=0;i<(WAVPREC>>1);i++)
{
wavtable[i] =
wavtable[(i<<1) +WAVPREC] = (signed short)(16384*sin((float)((i<<1) )*PI*2/WAVPREC));
wavtable[(i<<1)+1+WAVPREC] = (signed short)(16384*sin((float)((i<<1)+1)*PI*2/WAVPREC));
}
for(i=0;i<(WAVPREC>>3);i++)
{
wavtable[i+(WAVPREC<<1)] = wavtable[i+(WAVPREC>>3)]-16384;
wavtable[i+((WAVPREC*17)>>3)] = wavtable[i+(WAVPREC>>2)]+16384;
}
//[table in book]*8/3
ksl[7][0] = 0; ksl[7][1] = 24; ksl[7][2] = 32; ksl[7][3] = 37;
ksl[7][4] = 40; ksl[7][5] = 43; ksl[7][6] = 45; ksl[7][7] = 47;
ksl[7][8] = 48; for(i=9;i<16;i++) ksl[7][i] = i+41;
for(j=6;j>=0;j--)
for(i=0;i<16;i++)
{
oct = (long)ksl[j+1][i]-8; if (oct < 0) oct = 0;
ksl[j][i] = (unsigned char)oct;
}
}
else
{
for(i=0;i<9;i++)
{
frn = ((((long)adlibreg[i+0xb0])&3)<<8) + (long)adlibreg[i+0xa0];
oct = ((((long)adlibreg[i+0xb0])>>2)&7);
cell[i].tinc = (float)(frn<<oct)*nfrqmul[adlibreg[modulatorbase[i]+0x20]&15];
}
}
}
void adlib0 (long i, long v)
{
unsigned char tmp = adlibreg[i];
adlibreg[i] = v;
if (i == 0xbd)
{
if ((v&16) > (odrumstat&16)) //BassDrum
{
cellon(6,16,&cell[6],0);
cellon(6,19,&cell[15],1);
cell[15].vol *= 2;
}
if ((v&8) > (odrumstat&8)) //Snare
{
cellon(16,20,&cell[16],0);
cell[16].tinc *= 2*(nfrqmul[adlibreg[17+0x20]&15] / nfrqmul[adlibreg[20+0x20]&15]);
if (((adlibreg[20+0xe0]&7) >= 3) && ((adlibreg[20+0xe0]&7) <= 5)) cell[16].vol = 0;
cell[16].vol *= 2;
}
if ((v&4) > (odrumstat&4)) //TomTom
{
cellon(8,18,&cell[8],0);
cell[8].vol *= 2;
}
if ((v&2) > (odrumstat&2)) //Cymbal
{
cellon(17,21,&cell[17],0);
cell[17].wavemask = wavemask[5];
cell[17].waveform = &wavtable[waveform[5]];
cell[17].tinc *= 16; cell[17].vol *= 2;
//cell[17].waveform = &wavtable[WAVPREC]; cell[17].wavemask = 0;
//if (((adlibreg[21+0xe0]&7) == 0) || ((adlibreg[21+0xe0]&7) == 6))
// cell[17].waveform = &wavtable[(WAVPREC*7)>>2];
//if (((adlibreg[21+0xe0]&7) == 2) || ((adlibreg[21+0xe0]&7) == 3))
// cell[17].waveform = &wavtable[(WAVPREC*5)>>2];
}
if ((v&1) > (odrumstat&1)) //Hihat
{
cellon(7,17,&cell[7],0);
if (((adlibreg[17+0xe0]&7) == 1) || ((adlibreg[17+0xe0]&7) == 4) ||
((adlibreg[17+0xe0]&7) == 5) || ((adlibreg[17+0xe0]&7) == 7)) cell[7].vol = 0;
if ((adlibreg[17+0xe0]&7) == 6) { cell[7].wavemask = 0; cell[7].waveform = &wavtable[(WAVPREC*7)>>2]; }
}
odrumstat = v;
}
else if (((unsigned)(i-0x40) < (unsigned)22) && ((i&7) < 6))
{
if ((i&7) < 3) // Modulator
cellfreq(base2cell[i-0x40],i-0x40,&cell[base2cell[i-0x40]]);
else // Carrier
cellfreq(base2cell[i-0x40],i-0x40,&cell[base2cell[i-0x40]+9]);
}
else if ((unsigned)(i-0xa0) < (unsigned)9)
{
cellfreq(i-0xa0,modulatorbase[i-0xa0],&cell[i-0xa0]);
cellfreq(i-0xa0,modulatorbase[i-0xa0]+3,&cell[i-0xa0+9]);
}
else if ((unsigned)(i-0xb0) < (unsigned)9)
{
if ((v&32) > (tmp&32))
{
cellon(i-0xb0,modulatorbase[i-0xb0],&cell[i-0xb0],0);
cellon(i-0xb0,modulatorbase[i-0xb0]+3,&cell[i-0xb0+9],1);
}
else if ((v&32) < (tmp&32))
cell[i-0xb0].cellfunc = cell[i-0xb0+9].cellfunc = docell2;
cellfreq(i-0xb0,modulatorbase[i-0xb0],&cell[i-0xb0]);
cellfreq(i-0xb0,modulatorbase[i-0xb0]+3,&cell[i-0xb0+9]);
}
//outdata(i,v);
}
#ifdef USING_ASM
static long fpuasm;
static float fakeadd = 8388608.0+128.0;
static _inline void clipit8 (float f, long a)
{
_asm
{
mov edi, a
fld dword ptr f
fadd dword ptr fakeadd
fstp dword ptr fpuasm
mov eax, fpuasm
test eax, 0x007fff00
jz short skipit
shr eax, 16
xor eax, -1
skipit: mov byte ptr [edi], al
}
}
static _inline void clipit16 (float f, long a)
{
_asm
{
mov eax, a
fld dword ptr f
fist word ptr [eax]
cmp word ptr [eax], 0x8000
jne short skipit2
fst dword ptr [fpuasm]
cmp fpuasm, 0x80000000
sbb word ptr [eax], 0
skipit2: fstp st
}
}
#else
static void clipit8(float f,unsigned char *a) {
f/=256.0;
f+=128.0;
if (f>254.5) *a=255;
else if (f<0.5) *a=0;
else *a=f;
}
static void clipit16(float f,short *a) {
if (f>32766.5) *a=32767;
else if (f<-32767.5) *a=-32768;
else *a=f;
}
#endif
void adlibsetvolume(int i) {
AMPSCALE=i;
}
void adlibgetsample (unsigned char *sndptr, long numbytes)
{
long i, j, k=0, ns, endsamples, rptrs, numsamples;
celltype *cptr;
float f;
short *sndptr2=(short *)sndptr;
numsamples = (numbytes>>(numspeakers+bytespersample-2));
if (bytespersample == 1) f = AMPSCALE/256.0; else f = AMPSCALE;
if (numspeakers == 1)
{
nlvol[0] = lvol[0]*f;
for(i=0;i<9;i++) rptr[i] = &rbuf[0][0];
rptrs = 1;
}
else
{
rptrs = 0;
for(i=0;i<9;i++)
{
if ((!i) || (lvol[i] != lvol[i-1]) || (rvol[i] != rvol[i-1]) ||
(lplc[i] != lplc[i-1]) || (rplc[i] != rplc[i-1]))
{
nlvol[rptrs] = lvol[i]*f;
nrvol[rptrs] = rvol[i]*f;
nlplc[rptrs] = rend-min(max(lplc[i],0),FIFOSIZ);
nrplc[rptrs] = rend-min(max(rplc[i],0),FIFOSIZ);
rptrs++;
}
rptr[i] = &rbuf[rptrs-1][0];
}
}
//CPU time used to be somewhat less when emulator was only mono!
// Because of no delay fifos!
for(ns=0;ns<numsamples;ns+=endsamples)
{
endsamples = min(FIFOSIZ*2-rend,FIFOSIZ);
endsamples = min(endsamples,numsamples-ns);
for(i=0;i<9;i++)
nrptr[i] = &rptr[i][rend];
for(i=0;i<rptrs;i++)
memset((void *)&rbuf[i][rend],0,endsamples*sizeof(float));
if (adlibreg[0xbd]&0x20)
{
//BassDrum (j=6)
if (cell[15].cellfunc != docell4)
{
if (adlibreg[0xc6]&1)
{
for(i=0;i<endsamples;i++)
{
(cell[15].cellfunc)((void *)&cell[15],0.0);
nrptr[6][i] += cell[15].val;
}
}
else
{
for(i=0;i<endsamples;i++)
{
(cell[6].cellfunc)((void *)&cell[6],cell[6].val*cell[6].mfb);
(cell[15].cellfunc)((void *)&cell[15],cell[6].val*WAVPREC*MODFACTOR);
nrptr[6][i] += cell[15].val;
}
}
}
//Snare/Hihat (j=7), Cymbal/TomTom (j=8)
if ((cell[7].cellfunc != docell4) || (cell[8].cellfunc != docell4) || (cell[16].cellfunc != docell4) || (cell[17].cellfunc != docell4))
{
for(i=0;i<endsamples;i++)
{
k = k*1664525+1013904223;
(cell[16].cellfunc)((void *)&cell[16],k&((WAVPREC>>1)-1)); //Snare
(cell[7].cellfunc)((void *)&cell[7],k&(WAVPREC-1)); //Hihat
(cell[17].cellfunc)((void *)&cell[17],k&((WAVPREC>>3)-1)); //Cymbal
(cell[8].cellfunc)((void *)&cell[8],0.0); //TomTom
nrptr[7][i] += cell[7].val + cell[16].val;
nrptr[8][i] += cell[8].val + cell[17].val;
}
}
}
for(j=9-1;j>=0;j--)
{
if ((adlibreg[0xbd]&0x20) && (j >= 6) && (j < 9)) continue;
cptr = &cell[j]; k = j;
if (adlibreg[0xc0+k]&1)
{
if ((cptr[9].cellfunc == docell4) && (cptr->cellfunc == docell4)) continue;
for(i=0;i<endsamples;i++)
{
(cptr->cellfunc)((void *)cptr,cptr->val*cptr->mfb);
(cptr->cellfunc)((void *)&cptr[9],0);
nrptr[j][i] += cptr[9].val + cptr->val;
}
}
else
{
if (cptr[9].cellfunc == docell4) continue;
for(i=0;i<endsamples;i++)
{
(cptr->cellfunc)((void *)cptr,cptr->val*cptr->mfb);
(cptr[9].cellfunc)((void *)&cptr[9],cptr->val*WAVPREC*MODFACTOR);
nrptr[j][i] += cptr[9].val;
}
}
}
if (numspeakers == 1)
{
if (bytespersample == 1)
{
for(i=endsamples-1;i>=0;i--)
clipit8(nrptr[0][i]*nlvol[0],sndptr+1);
}
else
{
for(i=endsamples-1;i>=0;i--)
clipit16(nrptr[0][i]*nlvol[0],sndptr2+i);
}
}
else
{
memset((void *)snd,0,endsamples*sizeof(float)*2);
for(j=0;j<rptrs;j++)
{
for(i=0;i<endsamples;i++)
{
snd[(i<<1) ] += rbuf[j][(nlplc[j]+i)&(FIFOSIZ*2-1)]*nlvol[j];
snd[(i<<1)+1] += rbuf[j][(nrplc[j]+i)&(FIFOSIZ*2-1)]*nrvol[j];
}
nlplc[j] += endsamples;
nrplc[j] += endsamples;
}
if (bytespersample == 1)
{
for(i=(endsamples<<1)-1;i>=0;i--)
clipit8(snd[i],sndptr+i);
}
else
{
for(i=(endsamples<<1)-1;i>=0;i--)
clipit16(snd[i],sndptr2+i);
}
}
sndptr = sndptr+(numspeakers*endsamples);
sndptr2 = sndptr2+(numspeakers*endsamples);
rend = ((rend+endsamples)&(FIFOSIZ*2-1));
}
}

View File

@@ -1,26 +0,0 @@
/*
* ADLIBEMU.H
* Copyright (C) 1998-2001 Ken Silverman
* Ken Silverman's official web site: "http://www.advsys.net/ken"
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
void adlibinit(long dasamplerate,long danumspeakers,long dabytespersample);
void adlib0(long i,long v);
void adlibgetsample(void *sndptr,long numbytes);
void adlibsetvolume(int i);
void randoinsts();
extern float lvol[9],rvol[9],lplc[9],rplc[9];

View File

@@ -596,7 +596,7 @@ static void init_timetables( FM_OPL *OPL , int ARRATE , int DRRATE )
OPL->AR_TABLE[i] = rate / ARRATE;
OPL->DR_TABLE[i] = rate / DRRATE;
}
for (i = 60;i < 76;i++)
for (i = 60;i < 75;i++)
{
OPL->AR_TABLE[i] = EG_AED-1;
OPL->DR_TABLE[i] = OPL->DR_TABLE[60];

View File

@@ -1,61 +0,0 @@
/*
* Adplug - Replayer for many OPL2/OPL3 audio file formats.
* Copyright (C) 1999 - 2005 Simon Peter, <dn.tlp@gmx.net>, et al.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* kemuopl.h - Emulated OPL using Ken Silverman's emulator, by Simon Peter
* <dn.tlp@gmx.net>
*/
#ifndef H_ADPLUG_KEMUOPL
#define H_ADPLUG_KEMUOPL
#include "opl.h"
extern "C" {
#include "adlibemu.h"
}
class CKemuopl: public Copl
{
public:
CKemuopl(int rate, bool bit16, bool usestereo)
: use16bit(bit16), stereo(usestereo)
{
adlibinit(rate, usestereo ? 2 : 1, bit16 ? 2 : 1);
currType = TYPE_OPL2;
};
void update(short *buf, int samples)
{
if(use16bit) samples *= 2;
if(stereo) samples *= 2;
adlibgetsample(buf, samples);
}
// template methods
void write(int reg, int val)
{
if(currChip == 0)
adlib0(reg, val);
};
void init() {};
private:
bool use16bit,stereo;
};
#endif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,7 +1,7 @@
/*
* OPL2 FM synth
*
* Copyright (c) 2013 Raine M. Ekman <raine/at/iki/fi>
* Copyright (c) 2014 Raine M. Ekman <raine/at/iki/fi>
*
* This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net
*
@@ -23,10 +23,10 @@
*/
// TODO:
// - Pitch bend
// - Velocity (and aftertouch) sensitivity
// - in FM mode: OP2 level, add mode: OP1 and OP2 levels
// - Better voice allocation: long releases get cut short :(
// - .sbi (or similar) file loading into models
// - RT safety = get rid of mutex = make emulator code thread-safe
// - Extras:
// - double release: first release is in effect until noteoff (heard if percussive sound),
// second is switched in just before key bit cleared (is this useful???)
@@ -53,7 +53,6 @@
#include "opl.h"
#include "temuopl.h"
#include "kemuopl.h"
#include "embed.cpp"
#include "math.h"
@@ -92,6 +91,9 @@ Plugin * PLUGIN_EXPORT lmms_plugin_main( Model *, void * _data )
// the emulator code isn't really ready for threads
QMutex opl2instrument::emulatorMutex;
// Weird ordering of voice parameters
const unsigned int adlib_opadd[9] = {0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12};
opl2instrument::opl2instrument( InstrumentTrack * _instrument_track ) :
Instrument( _instrument_track, &OPL2_plugin_descriptor ),
m_patchModel( 0, 0, 127, this, tr( "Patch" ) ),
@@ -139,10 +141,11 @@ opl2instrument::opl2instrument( InstrumentTrack * _instrument_track ) :
InstrumentPlayHandle * iph = new InstrumentPlayHandle( this );
engine::mixer()->addPlayHandle( iph );
// Voices are laid out in a funny way...
// adlib_opadd = {0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12};
// Create an emulator - samplerate, 16 bit, mono
// CTemuopl is the better one, CKemuopl kinda sucks (some sounds silent, pitch goes flat after a while)
emulatorMutex.lock();
// theEmulator = new CKemuopl(engine::mixer()->processingSampleRate(), true, false);
theEmulator = new CTemuopl(engine::mixer()->processingSampleRate(), true, false);
theEmulator->init();
// Enable waveform selection
@@ -155,12 +158,15 @@ opl2instrument::opl2instrument( InstrumentTrack * _instrument_track ) :
frameCount = engine::mixer()->framesPerPeriod();
renderbuffer = new short[frameCount];
// Some kind of sane default
// Some kind of sane defaults
pitchbend = 0;
tuneEqual(69, 440);
for(int i=1; i<9; ++i) {
voiceNote[i] = OPL2_VOICE_FREE;
voiceLRU[i] = i;
}
connect( engine::mixer(), SIGNAL( sampleRateChanged() ),
this, SLOT( reloadEmulator() ) );
// Connect knobs
@@ -207,8 +213,15 @@ opl2instrument::opl2instrument( InstrumentTrack * _instrument_track ) :
MOD_CON( trem_depth_mdl );
}
opl2instrument::~opl2instrument() {
delete theEmulator;
engine::mixer()->removePlayHandles( instrumentTrack() );
delete [] renderbuffer;
}
// Samplerate changes when choosing oversampling, so this is more or less mandatory
void opl2instrument::reloadEmulator() {
delete theEmulator;
emulatorMutex.lock();
theEmulator = new CTemuopl(engine::mixer()->processingSampleRate(), true, false);
theEmulator->init();
@@ -216,50 +229,135 @@ void opl2instrument::reloadEmulator() {
emulatorMutex.unlock();
for(int i=1; i<9; ++i) {
voiceNote[i] = OPL2_VOICE_FREE;
voiceLRU[i] = i;
}
updatePatch();
}
// This shall only be called from code protected by the holy Mutex!
void opl2instrument::setVoiceVelocity(int voice, int vel) {
int vel_adjusted;
// Velocity calculation, some kind of approximation
// Only calculate for operator 1 if in adding mode, don't want to change timbre
if( fm_mdl.value() == false ) {
vel_adjusted = 63 - ( op1_lvl_mdl.value() * vel/127.0) ;
} else {
vel_adjusted = 63 - op1_lvl_mdl.value();
}
theEmulator->write(0x40+adlib_opadd[voice],
( (int)op1_scale_mdl.value() & 0x03 << 6) +
( vel_adjusted & 0x3f ) );
vel_adjusted = 63 - ( op2_lvl_mdl.value() * vel/127.0 );
// vel_adjusted = 63 - op2_lvl_mdl.value();
theEmulator->write(0x43+adlib_opadd[voice],
( (int)op2_scale_mdl.value() & 0x03 << 6) +
( vel_adjusted & 0x3f ) );
// printf("vel %d for voice %d (%f)\n",vel_adjusted,voice,op2_lvl_mdl.value() );
}
// Pop least recently used voice - why does it sometimes lose a voice (mostly 0)?
int opl2instrument::popVoice() {
int tmp = voiceLRU[0];
for( int i=0; i<8; ++i) {
voiceLRU[i] = voiceLRU[i+1];
}
voiceLRU[8] = OPL2_NO_VOICE;
/* printf("pop: %d %d %d %d %d %d %d %d %d \n",
voiceLRU[0],voiceLRU[1],voiceLRU[2],
voiceLRU[3],voiceLRU[4],voiceLRU[5],
voiceLRU[6],voiceLRU[7],voiceLRU[8]); */
return tmp;
}
int opl2instrument::pushVoice(int v) {
int i;
for(i=8; i>0; --i) {
if( voiceLRU[i-1] != OPL2_NO_VOICE ) {
break;
}
}
voiceLRU[i] = v;
/*printf("%d %d %d %d %d %d %d %d %d \n",
voiceLRU[0],voiceLRU[1],voiceLRU[2],
voiceLRU[3],voiceLRU[4],voiceLRU[5],
voiceLRU[6],voiceLRU[7],voiceLRU[8]); */
return i;
}
bool opl2instrument::handleMidiEvent( const midiEvent & _me,
const midiTime & _time )
{
emulatorMutex.lock();
// Real dummy version... Should at least add:
// - smarter voice allocation:
// - reuse same note, now we have round robin-ish
// - what to do when voices run out and so on...
// - mono mode
//
int key;
static int lastvoice=0;
if( _me.m_type == MidiNoteOn ) {
// to get us in line with MIDI
int key, vel, voice, tmp_pb;
switch(_me.m_type) {
case MidiNoteOn:
// to get us in line with MIDI(?)
key = _me.key() +12;
for(int i=lastvoice+1; i!=lastvoice; ++i,i%=9) {
if( voiceNote[i] == OPL2_VOICE_FREE ) {
theEmulator->write(0xA0+i, fnums[key] & 0xff);
theEmulator->write(0xB0+i, 32 + ((fnums[key] & 0x1f00) >> 8) );
// printf("%d: %d %d\n", key, (fnums[key] & 0x1c00) >> 10, fnums[key] & 0x3ff);
voiceNote[i] = key;
// printf("Voice %d on\n",i);
lastvoice=i;
break;
vel = _me.velocity();
voice = popVoice();
if( voice != OPL2_NO_VOICE ) {
// Turn voice on, NB! the frequencies are straight by voice number,
// not by the adlib_opadd table!
theEmulator->write(0xA0+voice, fnums[key] & 0xff);
theEmulator->write(0xB0+voice, 32 + ((fnums[key] & 0x1f00) >> 8) );
setVoiceVelocity(voice, vel);
voiceNote[voice] = key;
velocities[key] = vel;
// printf("%d %d\n",voice,vel);
}
break;
case MidiNoteOff:
key = _me.key() +12;
for(voice=0; voice<9; ++voice) {
if( voiceNote[voice] == key ) {
theEmulator->write(0xA0+voice, fnums[key] & 0xff);
theEmulator->write(0xB0+voice, (fnums[key] & 0x1f00) >> 8 );
voiceNote[voice] = OPL2_VOICE_FREE;
pushVoice(voice);
}
}
velocities[key] = 0;
break;
case MidiKeyPressure:
key = _me.key() +12;
vel = _me.velocity();
if( velocities[key] != 0) {
velocities[key] = vel;
}
for(voice=0; voice<9; ++voice) {
if(voiceNote[voice] == key) {
setVoiceVelocity(voice, vel);
}
}
} else if( _me.m_type == MidiNoteOff ) {
key = _me.key() +12;
for(int i=0; i<9; ++i) {
if( voiceNote[i] == key ) {
theEmulator->write(0xA0+i, fnums[key] & 0xff);
theEmulator->write(0xB0+i, (fnums[key] & 0x1f00) >> 8 );
voiceNote[i] = OPL2_VOICE_FREE;
}
break;
case MidiPitchBend:
// Update fnumber table
// Pitchbend should be in the range 0...16383 but the new range knob gets it wrong.
// tmp_pb = (2*BEND_CENTS)*((float)_me.m_data.m_param[0]/16383)-BEND_CENTS;
// Something like 100 cents = 8192, but offset by 8192 so the +/-100 cents range goes from 0...16383?
tmp_pb = ( _me.m_data.m_param[0]-8192 ) * BEND_CENTS / 8192;
printf("Pitch bend: %d -> %d cents\n",_me.m_data.m_param[0],tmp_pb);
if( tmp_pb != pitchbend ) {
pitchbend = tmp_pb;
tuneEqual(69, 440.0);
}
} else {
printf("Midi event type %d\n",_me.m_type);
// 224 - pitch wheel
// 160 - aftertouch?
}
// Update pitch of sounding notes
for( int v=0; v<9; ++v ) {
if( voiceNote[v] != OPL2_VOICE_FREE ) {
theEmulator->write(0xA0+v, fnums[voiceNote[v] ] & 0xff);
theEmulator->write(0xB0+v, 32 + ((fnums[voiceNote[v]] & 0x1f00) >> 8) );
}
}
break;
default:
printf("Midi event type %d\n",_me.m_type);
}
emulatorMutex.unlock();
return true;
}
@@ -332,7 +430,6 @@ void opl2instrument::saveSettings( QDomDocument & _doc, QDomElement & _this )
void opl2instrument::loadSettings( const QDomElement & _this )
{
printf("loadSettings!\n");
op1_a_mdl.loadSettings( _this, "op1_a" );
op1_d_mdl.loadSettings( _this, "op1_d" );
op1_s_mdl.loadSettings( _this, "op1_s" );
@@ -366,19 +463,14 @@ void opl2instrument::loadSettings( const QDomElement & _this )
}
// Load a preset in binary form
// Load a patch into the emulator
void opl2instrument::loadPatch(unsigned char inst[14]) {
const unsigned int adlib_opadd[] = {0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12};
// Set all voices
printf("%02x %02x %02x %02x %02x ",inst[0],inst[1],inst[2],inst[3],inst[4]);
printf("%02x %02x %02x %02x %02x %02x\n",inst[5],inst[6],inst[7],inst[8],inst[9],inst[10]);
emulatorMutex.lock();
for(int v=0; v<9; ++v) {
theEmulator->write(0x20+adlib_opadd[v],inst[0]); // op1 AM/VIB/EG/KSR/Multiplier
theEmulator->write(0x23+adlib_opadd[v],inst[1]); // op2
theEmulator->write(0x40+adlib_opadd[v],inst[2]); // op1 KSL/Output Level
theEmulator->write(0x43+adlib_opadd[v],inst[3]); // op2
// theEmulator->write(0x40+adlib_opadd[v],inst[2]); // op1 KSL/Output Level - these are handled by noteon/aftertouch code
// theEmulator->write(0x43+adlib_opadd[v],inst[3]); // op2
theEmulator->write(0x60+adlib_opadd[v],inst[4]); // op1 A/D
theEmulator->write(0x63+adlib_opadd[v],inst[5]); // op2
theEmulator->write(0x80+adlib_opadd[v],inst[6]); // op1 S/R
@@ -391,10 +483,10 @@ void opl2instrument::loadPatch(unsigned char inst[14]) {
}
void opl2instrument::tuneEqual(int center, float Hz) {
float tmp;
for(int n=0; n<128; ++n) {
float tmp = Hz*pow(2, (n-center)/12.0);
tmp = Hz*pow( 2, ( n - center ) / 12.0 + pitchbend / 1200.0 );
fnums[n] = Hz2fnum( tmp );
//printf("%d: %d %d %f\n", n, (fnums[n] & 0x1c00) >> 10, fnums[n] & 0x3ff,tmp);
}
}
@@ -412,7 +504,6 @@ int opl2instrument::Hz2fnum(float Hz) {
// Load one of the default patches
void opl2instrument::loadGMPatch() {
unsigned char *inst = midi_fm_instruments[m_patchModel.value()];
// printf("loadGMPatch: %d ", m_patchModel.value());
loadPatch(inst);
}
@@ -423,7 +514,6 @@ void opl2instrument::loadGMPatch() {
// Update patch from the models to the chip emulation
void opl2instrument::updatePatch() {
printf("updatePatch()\n");
unsigned char *inst = midi_fm_instruments[0];
inst[0] = ( op1_trem_mdl.value() ? 128 : 0 ) +
( op1_vib_mdl.value() ? 64 : 0 ) +
@@ -456,10 +546,16 @@ void opl2instrument::updatePatch() {
inst[12] = 0;
inst[13] = 0;
// Not part of the patch per se
// Not part of the per-voice patch info
theEmulator->write(0xBD, (trem_depth_mdl.value() ? 128 : 0 ) +
(vib_depth_mdl.value() ? 64 : 0 ));
// have to do this, as the level knobs might've changed
for( int voice = 0; voice < 9 ; ++voice) {
if(voiceNote[voice]!=OPL2_VOICE_FREE) {
setVoiceVelocity(voice, velocities[voiceNote[voice]] );
}
}
loadPatch(inst);
}
@@ -552,9 +648,9 @@ opl2instrumentView::opl2instrumentView( Instrument * _instrument,
pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap(
"artwork" ) );
setPalette( pal );
}
opl2instrumentView::~opl2instrumentView() {
// Nobody else seems to delete their knobs and buttons?
}
void opl2instrumentView::modelChanged()
@@ -604,4 +700,5 @@ void opl2instrumentView::modelChanged()
}
#include "moc_opl2instrument.cxx"

View File

@@ -34,12 +34,17 @@
#include "pixmap_button.h"
#define OPL2_VOICE_FREE 255
#define OPL2_NO_VOICE 255
// The "normal" range for LMMS pitchbends
#define BEND_CENTS 100
class opl2instrument : public Instrument
{
Q_OBJECT
public:
opl2instrument( InstrumentTrack * _instrument_track );
virtual ~opl2instrument();
virtual QString nodeName() const;
virtual PluginView * instantiateView( QWidget * _parent );
@@ -107,20 +112,33 @@ private:
fpp_t frameCount;
short *renderbuffer;
int voiceNote[9];
int heldNotes[128];
// Least recently used voices
int voiceLRU[9];
// 0 - no note, >0 - note on velocity
int velocities[128];
// These include both octave and Fnumber
int fnums[128];
// in cents, range defaults to +/-100 cents (should this be changeable?)
int pitchbend;
int popVoice();
int pushVoice(int v);
int Hz2fnum(float Hz);
static QMutex emulatorMutex;
void setVoiceVelocity(int voice, int vel);
};
class opl2instrumentView : public InstrumentView
{
Q_OBJECT
public:
opl2instrumentView( Instrument * _instrument, QWidget * _parent );
virtual ~opl2instrumentView();
lcdSpinBox *m_patch;
void modelChanged();