From ce6dc80c9ecaa2c2e624606cea1d7a8a041f9955 Mon Sep 17 00:00:00 2001 From: "Raine M. Ekman" Date: Fri, 17 Jan 2014 23:21:54 +0200 Subject: [PATCH 1/4] OpulenZ: new logo, some refactoring. --- plugins/opl2/logo.png | Bin 285 -> 3195 bytes plugins/opl2/opl2instrument.cpp | 92 ++++++++++++++++++-------------- plugins/opl2/opl2instrument.h | 3 ++ 3 files changed, 55 insertions(+), 40 deletions(-) diff --git a/plugins/opl2/logo.png b/plugins/opl2/logo.png index 913056fe22bd0dd1bcff8862a35b3b0b9ef55e6f..fab0301603f15125e7cc98c5a9bfc13e4b658744 100644 GIT binary patch delta 3176 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 diff --git a/plugins/opl2/opl2instrument.cpp b/plugins/opl2/opl2instrument.cpp index 0117d1650..323945738 100644 --- a/plugins/opl2/opl2instrument.cpp +++ b/plugins/opl2/opl2instrument.cpp @@ -1,7 +1,7 @@ /* * OPL2 FM synth * - * Copyright (c) 2013 Raine M. Ekman + * Copyright (c) 2014 Raine M. Ekman * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -27,6 +27,8 @@ // - Velocity (and aftertouch) sensitivity // - in FM mode: OP2 level, add mode: OP1 and OP2 levels // - .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???) @@ -213,8 +215,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(); @@ -236,36 +245,45 @@ bool opl2instrument::handleMidiEvent( const midiEvent & _me, // - what to do when voices run out and so on... // - mono mode // - int key; + int key, vel; static int lastvoice=0; - if( _me.m_type == MidiNoteOn && !isMuted() ) { - // 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; - } - } - } 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; - } - } - } else { - printf("Midi event type %d\n",_me.m_type); - // 224 - pitch wheel - // 160 - aftertouch? - } + switch(_me.m_type) { + case MidiNoteOn: + if( !isMuted() ) { + // to get us in line with MIDI + key = _me.key() +12; + vel = _me.velocity(); + 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) ); + voiceNote[i] = key; + lastvoice=i; + break; + } + } + } + break; + case 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 MidiKeyPressure: + key = _me.key() +12; + vel = _me.velocity(); + break; + case MidiPitchBend: + printf("TODO: Pitch bend\n"); + break; + default: + printf("Midi event type %d\n",_me.m_type); + } emulatorMutex.unlock(); return true; } @@ -338,7 +356,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" ); @@ -375,9 +392,6 @@ void opl2instrument::loadSettings( const QDomElement & _this ) // Load a preset in binary form 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) { @@ -400,7 +414,6 @@ void opl2instrument::tuneEqual(int center, float Hz) { for(int n=0; n<128; ++n) { float tmp = Hz*pow(2, (n-center)/12.0); fnums[n] = Hz2fnum( tmp ); - //printf("%d: %d %d %f\n", n, (fnums[n] & 0x1c00) >> 10, fnums[n] & 0x3ff,tmp); } } @@ -418,7 +431,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); } @@ -429,7 +441,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 ) + @@ -558,9 +569,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() @@ -610,4 +621,5 @@ void opl2instrumentView::modelChanged() } + #include "moc_opl2instrument.cxx" diff --git a/plugins/opl2/opl2instrument.h b/plugins/opl2/opl2instrument.h index 6933d7479..a02088167 100644 --- a/plugins/opl2/opl2instrument.h +++ b/plugins/opl2/opl2instrument.h @@ -40,6 +40,8 @@ class opl2instrument : public Instrument Q_OBJECT public: opl2instrument( InstrumentTrack * _instrument_track ); + virtual ~opl2instrument(); + virtual QString nodeName() const; virtual PluginView * instantiateView( QWidget * _parent ); @@ -121,6 +123,7 @@ class opl2instrumentView : public InstrumentView Q_OBJECT public: opl2instrumentView( Instrument * _instrument, QWidget * _parent ); + virtual ~opl2instrumentView(); lcdSpinBox *m_patch; void modelChanged(); From 9a9a867e7d3997b64332f3e75adbfd985dd187c1 Mon Sep 17 00:00:00 2001 From: "Raine M. Ekman" Date: Wed, 22 Jan 2014 20:57:02 +0200 Subject: [PATCH 2/4] Added pitch bend --- plugins/opl2/opl2instrument.cpp | 40 ++++++++++++++++++++++----------- plugins/opl2/opl2instrument.h | 6 ++++- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/plugins/opl2/opl2instrument.cpp b/plugins/opl2/opl2instrument.cpp index 323945738..dd8fda55a 100644 --- a/plugins/opl2/opl2instrument.cpp +++ b/plugins/opl2/opl2instrument.cpp @@ -23,9 +23,8 @@ */ // TODO: -// - Pitch bend // - Velocity (and aftertouch) sensitivity -// - in FM mode: OP2 level, add mode: OP1 and OP2 levels +// * in FM mode: OP2 level, add mode: OP1 and OP2 levels // - .sbi (or similar) file loading into models // - RT safety = get rid of mutex = make emulator code thread-safe @@ -163,12 +162,14 @@ 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; } + connect( engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( reloadEmulator() ) ); // Connect knobs @@ -239,18 +240,12 @@ 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, vel; + int key, vel, tmp_pb; static int lastvoice=0; switch(_me.m_type) { case MidiNoteOn: if( !isMuted() ) { - // to get us in line with MIDI + // to get us in line with MIDI(?) key = _me.key() +12; vel = _me.velocity(); for(int i=lastvoice+1; i!=lastvoice; ++i,i%=9) { @@ -258,6 +253,7 @@ bool opl2instrument::handleMidiEvent( const midiEvent & _me, theEmulator->write(0xA0+i, fnums[key] & 0xff); theEmulator->write(0xB0+i, 32 + ((fnums[key] & 0x1f00) >> 8) ); voiceNote[i] = key; + velocities[key] = vel; lastvoice=i; break; } @@ -273,13 +269,30 @@ bool opl2instrument::handleMidiEvent( const midiEvent & _me, voiceNote[i] = OPL2_VOICE_FREE; } } + velocities[key] = 0; break; case MidiKeyPressure: key = _me.key() +12; vel = _me.velocity(); + if( velocities[key] != 0) { + velocities[key] = vel; + } break; case MidiPitchBend: - printf("TODO: Pitch bend\n"); + // Update fnumber table + tmp_pb = (2*BEND_CENTS)*((float)_me.m_data.m_param[0]/16383)-BEND_CENTS; + if( tmp_pb != pitchbend ) { + pitchbend = tmp_pb; + tuneEqual(69, 440.0); + } + // Update pitch of sounding notes + for( int i=0; i<9; ++i ) { + if( voiceNote[i] != OPL2_VOICE_FREE ) { + theEmulator->write(0xA0+i, fnums[voiceNote[i] ] & 0xff); + theEmulator->write(0xB0+i, 32 + ((fnums[voiceNote[i]] & 0x1f00) >> 8) ); + } + } + // printf("Pitch bend: %d\n", pitchbend); break; default: printf("Midi event type %d\n",_me.m_type); @@ -411,8 +424,9 @@ 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 ); } } diff --git a/plugins/opl2/opl2instrument.h b/plugins/opl2/opl2instrument.h index a02088167..7c81ba192 100644 --- a/plugins/opl2/opl2instrument.h +++ b/plugins/opl2/opl2instrument.h @@ -109,9 +109,13 @@ private: fpp_t frameCount; short *renderbuffer; int voiceNote[9]; - int heldNotes[128]; + // 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; + #define BEND_CENTS 100 int Hz2fnum(float Hz); static QMutex emulatorMutex; From 67382654eb27c6402393f257d67da584dc2a70eb Mon Sep 17 00:00:00 2001 From: "Raine M. Ekman" Date: Fri, 24 Jan 2014 22:37:36 +0200 Subject: [PATCH 3/4] OpulenZ: added velocity & aftertouch, cleaned up some unneeded code --- plugins/opl2/CMakeLists.txt | 2 +- plugins/opl2/adlibemu.c | 608 -------------------------------- plugins/opl2/adlibemu.h | 26 -- plugins/opl2/kemuopl.h | 61 ---- plugins/opl2/opl2instrument.cpp | 1 - 5 files changed, 1 insertion(+), 697 deletions(-) delete mode 100644 plugins/opl2/adlibemu.c delete mode 100644 plugins/opl2/adlibemu.h delete mode 100644 plugins/opl2/kemuopl.h diff --git a/plugins/opl2/CMakeLists.txt b/plugins/opl2/CMakeLists.txt index 0b9bd32cd..981ec53a3 100644 --- a/plugins/opl2/CMakeLists.txt +++ b/plugins/opl2/CMakeLists.txt @@ -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) \ No newline at end of file +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) \ No newline at end of file diff --git a/plugins/opl2/adlibemu.c b/plugins/opl2/adlibemu.c deleted file mode 100644 index 9aac3a6fc..000000000 --- a/plugins/opl2/adlibemu.c +++ /dev/null @@ -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 -#include - -#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<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<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=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< (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>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;icellfunc)((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;icellfunc)((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=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)); - } -} diff --git a/plugins/opl2/adlibemu.h b/plugins/opl2/adlibemu.h deleted file mode 100644 index 8600d787d..000000000 --- a/plugins/opl2/adlibemu.h +++ /dev/null @@ -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]; diff --git a/plugins/opl2/kemuopl.h b/plugins/opl2/kemuopl.h deleted file mode 100644 index d2ca6e288..000000000 --- a/plugins/opl2/kemuopl.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Adplug - Replayer for many OPL2/OPL3 audio file formats. - * Copyright (C) 1999 - 2005 Simon Peter, , 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 - * - */ - -#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 diff --git a/plugins/opl2/opl2instrument.cpp b/plugins/opl2/opl2instrument.cpp index 1ce116c1a..ab94eb834 100644 --- a/plugins/opl2/opl2instrument.cpp +++ b/plugins/opl2/opl2instrument.cpp @@ -54,7 +54,6 @@ #include "opl.h" #include "temuopl.h" -#include "kemuopl.h" #include "embed.cpp" #include "math.h" From 2116cf840b5589379f013e06304d91303e2b33a4 Mon Sep 17 00:00:00 2001 From: "Raine M. Ekman" Date: Sat, 25 Jan 2014 21:48:24 +0200 Subject: [PATCH 4/4] OpulenZ: Velocity works OK now. --- plugins/opl2/fmopl.c | 2 +- plugins/opl2/opl2instrument.cpp | 138 ++++++++++++++++++++++++-------- plugins/opl2/opl2instrument.h | 13 ++- 3 files changed, 119 insertions(+), 34 deletions(-) diff --git a/plugins/opl2/fmopl.c b/plugins/opl2/fmopl.c index 2b0e82b0c..db5180189 100644 --- a/plugins/opl2/fmopl.c +++ b/plugins/opl2/fmopl.c @@ -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]; diff --git a/plugins/opl2/opl2instrument.cpp b/plugins/opl2/opl2instrument.cpp index ab94eb834..0718a3796 100644 --- a/plugins/opl2/opl2instrument.cpp +++ b/plugins/opl2/opl2instrument.cpp @@ -23,8 +23,7 @@ */ // TODO: -// - 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 @@ -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 @@ -161,6 +164,7 @@ opl2instrument::opl2instrument( InstrumentTrack * _instrument_track ) : for(int i=1; i<9; ++i) { voiceNote[i] = OPL2_VOICE_FREE; + voiceLRU[i] = i; } connect( engine::mixer(), SIGNAL( sampleRateChanged() ), @@ -225,39 +229,95 @@ 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(); - int key, vel, tmp_pb; - static int lastvoice=0; + int key, vel, voice, tmp_pb; + switch(_me.m_type) { case MidiNoteOn: // to get us in line with MIDI(?) key = _me.key() +12; vel = _me.velocity(); - 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) ); - voiceNote[i] = key; - velocities[key] = vel; - lastvoice=i; - break; - } + + 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(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; + 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; @@ -268,22 +328,32 @@ bool opl2instrument::handleMidiEvent( const midiEvent & _me, if( velocities[key] != 0) { velocities[key] = vel; } + for(voice=0; voice<9; ++voice) { + if(voiceNote[voice] == key) { + setVoiceVelocity(voice, vel); + } + } break; case MidiPitchBend: // Update fnumber table - tmp_pb = (2*BEND_CENTS)*((float)_me.m_data.m_param[0]/16383)-BEND_CENTS; + // 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); } // Update pitch of sounding notes - for( int i=0; i<9; ++i ) { - if( voiceNote[i] != OPL2_VOICE_FREE ) { - theEmulator->write(0xA0+i, fnums[voiceNote[i] ] & 0xff); - theEmulator->write(0xB0+i, 32 + ((fnums[voiceNote[i]] & 0x1f00) >> 8) ); + 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) ); } } - // printf("Pitch bend: %d\n", pitchbend); break; default: printf("Midi event type %d\n",_me.m_type); @@ -393,16 +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}; - 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 @@ -478,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); } diff --git a/plugins/opl2/opl2instrument.h b/plugins/opl2/opl2instrument.h index 7c81ba192..af2493f95 100644 --- a/plugins/opl2/opl2instrument.h +++ b/plugins/opl2/opl2instrument.h @@ -34,6 +34,9 @@ #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 { @@ -109,19 +112,27 @@ private: fpp_t frameCount; short *renderbuffer; int voiceNote[9]; + // 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; - #define BEND_CENTS 100 + + + + 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