From 2116cf840b5589379f013e06304d91303e2b33a4 Mon Sep 17 00:00:00 2001 From: "Raine M. Ekman" Date: Sat, 25 Jan 2014 21:48:24 +0200 Subject: [PATCH] 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