diff --git a/plugins/opl2/opl2instrument.cpp b/plugins/opl2/opl2instrument.cpp index d1613a1aa..b84edf0b9 100644 --- a/plugins/opl2/opl2instrument.cpp +++ b/plugins/opl2/opl2instrument.cpp @@ -50,12 +50,16 @@ #include "InstrumentTrack.h" #include +#include +#include +#include #include "opl.h" #include "temuopl.h" #include "embed.cpp" #include "math.h" +#include "debug.h" #include "Knob.h" #include "LcdSpinBox.h" @@ -75,7 +79,7 @@ Plugin::Descriptor PLUGIN_EXPORT OPL2_plugin_descriptor = 0x0100, Plugin::Instrument, new PluginPixmapLoader( "logo" ), - NULL, + "sbi", NULL }; @@ -92,7 +96,7 @@ Plugin * PLUGIN_EXPORT lmms_plugin_main( Model *, void * _data ) QMutex opl2instrument::emulatorMutex; // Weird ordering of voice parameters -const unsigned int adlib_opadd[9] = {0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12}; +const unsigned int adlib_opadd[OPL2_VOICES] = {0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12}; opl2instrument::opl2instrument( InstrumentTrack * _instrument_track ) : Instrument( _instrument_track, &OPL2_plugin_descriptor ), @@ -141,9 +145,6 @@ opl2instrument::opl2instrument( InstrumentTrack * _instrument_track ) : InstrumentPlayHandle * iph = new InstrumentPlayHandle( this, _instrument_track ); 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 emulatorMutex.lock(); theEmulator = new CTemuopl(Engine::mixer()->processingSampleRate(), true, false); @@ -153,13 +154,15 @@ opl2instrument::opl2instrument( InstrumentTrack * _instrument_track ) : emulatorMutex.unlock(); //Initialize voice values - voiceNote[0] = 0; - voiceLRU[0] = 0; - for(int i=1; i<9; ++i) { + // voiceNote[0] = 0; + // voiceLRU[0] = 0; + for(int i=0; iinit(); theEmulator->write(0x01,0x20); emulatorMutex.unlock(); - for(int i=1; i<9; ++i) { + for(int i=0; i0; --i) { if( voiceLRU[i-1] != OPL2_NO_VOICE ) { break; } } voiceLRU[i] = v; +#ifdef false + 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]); +#endif return i; } @@ -307,11 +319,11 @@ bool opl2instrument::handleMidiEvent( const MidiEvent& event, const MidiTime& ti break; case MidiNoteOff: key = event.key() +12; - for(voice=0; voice<9; ++voice) { + for(voice=0; voicewrite(0xA0+voice, fnums[key] & 0xff); theEmulator->write(0xB0+voice, (fnums[key] & 0x1f00) >> 8 ); - voiceNote[voice] = OPL2_VOICE_FREE; + voiceNote[voice] |= OPL2_VOICE_FREE; pushVoice(voice); } } @@ -323,7 +335,7 @@ bool opl2instrument::handleMidiEvent( const MidiEvent& event, const MidiTime& ti if( velocities[key] != 0) { velocities[key] = vel; } - for(voice=0; voice<9; ++voice) { + for(voice=0; voicewrite(0xA0+v, fnums[voiceNote[v] ] & 0xff); - theEmulator->write(0xB0+v, 32 + ((fnums[voiceNote[v]] & 0x1f00) >> 8) ); - } + // Update pitch of all voices (also released ones) + for( int v=0; vwrite(0xA0+v, fnums[vn] & 0xff); + theEmulator->write(0xB0+v, (playing ? 32 : 0) + ((fnums[vn] & 0x1f00) >> 8) ); } break; case MidiControlChange: @@ -363,12 +373,17 @@ bool opl2instrument::handleMidiEvent( const MidiEvent& event, const MidiTime& ti } break; default: +#ifdef LMMS_DEBUG printf("Midi CC %02x %02x\n", event.controllerNumber(), event.controllerValue() ); +#endif break; } break; default: +#ifdef LMMS_DEBUG printf("Midi event type %d\n",event.type()); +#endif + break; } emulatorMutex.unlock(); return true; @@ -476,7 +491,7 @@ void opl2instrument::loadSettings( const QDomElement & _this ) } // Load a patch into the emulator -void opl2instrument::loadPatch(unsigned char inst[14]) { +void opl2instrument::loadPatch(const unsigned char inst[14]) { emulatorMutex.lock(); for(int v=0; v<9; ++v) { theEmulator->write(0x20+adlib_opadd[v],inst[0]); // op1 AM/VIB/EG/KSR/Multiplier @@ -519,14 +534,9 @@ void opl2instrument::loadGMPatch() { loadPatch(inst); } -// -/* void opl2instrument::loadSBIFile() { - - } */ - // Update patch from the models to the chip emulation void opl2instrument::updatePatch() { - unsigned char *inst = midi_fm_instruments[0]; + unsigned char inst[14] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; inst[0] = ( op1_trem_mdl.value() ? 128 : 0 ) + ( op1_vib_mdl.value() ? 64 : 0 ) + ( op1_perc_mdl.value() ? 0 : 32 ) + // NB. This envelope mode is "perc", not "sus" @@ -537,9 +547,9 @@ void opl2instrument::updatePatch() { ( op2_perc_mdl.value() ? 0 : 32 ) + // NB. This envelope mode is "perc", not "sus" ( op2_ksr_mdl.value() ? 16 : 0 ) + ((int)op2_mul_mdl.value() & 0x0f); - inst[2] = ( (int)op1_scale_mdl.value() & 0x03 << 6 ) + + inst[2] = ( ((int)op1_scale_mdl.value() & 0x03) << 6 ) + (63 - ( (int)op1_lvl_mdl.value() & 0x3f ) ); - inst[3] = ( (int)op2_scale_mdl.value() & 0x03 << 6 ) + + inst[3] = ( ((int)op2_scale_mdl.value() & 0x03) << 6 ) + (63 - ( (int)op2_lvl_mdl.value() & 0x3f ) ); inst[4] = ((15 - ((int)op1_a_mdl.value() & 0x0f ) ) << 4 )+ (15 - ( (int)op1_d_mdl.value() & 0x0f ) ); @@ -564,13 +574,111 @@ void opl2instrument::updatePatch() { // have to do this, as the level knobs might've changed for( int voice = 0; voice < 9 ; ++voice) { - if(voiceNote[voice]!=OPL2_VOICE_FREE) { + if(voiceNote[voice] && OPL2_VOICE_FREE == 0) { setVoiceVelocity(voice, velocities[voiceNote[voice]] ); } } +#ifdef false + printf("UPD: %02x %02x %02x %02x %02x -- %02x %02x %02x %02x %02x %02x\n", + inst[0], inst[1], inst[2], inst[3], inst[4], + inst[5], inst[6], inst[7], inst[8], inst[9], inst[10]); +#endif + + loadPatch(inst); } +// Load an SBI file into the knob models +void opl2instrument::loadFile( const QString& file ) { + // http://cd.textfiles.com/soundsensations/SYNTH/SBINS/ + // http://cd.textfiles.com/soundsensations/SYNTH/SBI1198/1198SBI.ZIP + if( !file.isEmpty() && QFileInfo( file ).exists() ) + { + QFile sbifile(file); + if (!sbifile.open(QIODevice::ReadOnly )) { + printf("Can't open file\n"); + return; + } + + QByteArray sbidata = sbifile.read(52); + if( !sbidata.startsWith("SBI\0x1a") ) { + printf("No SBI signature\n"); + return; + } + if( sbidata.size() != 52 ) { + printf("SBI size error: expected 52, got %d\n",sbidata.size() ); + } + + // Minimum size of SBI if we ignore "reserved" bytes at end + // https://courses.engr.illinois.edu/ece390/resources/sound/cmf.txt.html + if( sbidata.size() < 47 ) { + return; + } + + QString sbiname = sbidata.mid(4, 32); + // If user has changed track name... let's hope my logic is valid. + if( sbiname.size() > 0 && instrumentTrack()->displayName() == storedname ) { + instrumentTrack()->setName(sbiname); + storedname = sbiname; + } + +#ifdef false + printf("SBI: %02x %02x %02x %02x %02x -- %02x %02x %02x %02x %02x %02x\n", + (unsigned char)sbidata[36], (unsigned char)sbidata[37], (unsigned char)sbidata[38], (unsigned char)sbidata[39], (unsigned char)sbidata[40], + (unsigned char)sbidata[41], (unsigned char)sbidata[42], (unsigned char)sbidata[43], (unsigned char)sbidata[44], (unsigned char)sbidata[45], (unsigned char)sbidata[46]); +#endif + // Modulator Sound Characteristic (Mult, KSR, EG, VIB, AM) + op1_trem_mdl.setValue( (sbidata[36] & 0x80 ) == 0x80 ? true : false ); + op1_vib_mdl.setValue( (sbidata[36] & 0x40 ) == 0x40 ? true : false ); + op1_perc_mdl.setValue( (sbidata[36] & 0x20 ) == 0x20 ? false : true ); + op1_ksr_mdl.setValue( (sbidata[36] & 0x10 ) == 0x10 ? true : false ); + op1_mul_mdl.setValue( sbidata[36] & 0x0f ); + + // Carrier Sound Characteristic + op2_trem_mdl.setValue( (sbidata[37] & 0x80 ) == 0x80 ? true : false ); + op2_vib_mdl.setValue( (sbidata[37] & 0x40 ) == 0x40 ? true : false ); + op2_perc_mdl.setValue( (sbidata[37] & 0x20 ) == 0x20 ? false : true ); + op2_ksr_mdl.setValue( (sbidata[37] & 0x10 ) == 0x10 ? true : false ); + op2_mul_mdl.setValue( sbidata[37] & 0x0f ); + + // Modulator Scaling/Output Level + op1_scale_mdl.setValue( (sbidata[38] & 0xc0 ) >> 6 ); + op1_lvl_mdl.setValue( 63 - (sbidata[38] & 0x3f) ); + + // Carrier Scaling/Output Level + op2_scale_mdl.setValue( (sbidata[39] & 0xc0) >> 6 ); + op2_lvl_mdl.setValue( 63 - (sbidata[39] & 0x3f) ); + + // Modulator Attack/Decay + op1_a_mdl.setValue( 15 - ( ( sbidata[40] & 0xf0 ) >> 4 ) ); + op1_d_mdl.setValue( 15 - ( sbidata[40] & 0x0f ) ); + + // Carrier Attack/Decay + op2_a_mdl.setValue( 15 - ( ( sbidata[41] & 0xf0 ) >> 4 ) ); + op2_d_mdl.setValue( 15 - ( sbidata[41] & 0x0f ) ); + + // Modulator Sustain/Release + op1_s_mdl.setValue( 15 - ( ( sbidata[42] & 0xf0 ) >> 4 ) ); + op1_r_mdl.setValue( 15 - ( sbidata[42] & 0x0f ) ); + + // Carrier Sustain/Release + op2_s_mdl.setValue( 15 - ( ( sbidata[43] & 0xf0 ) >> 4 ) ); + op2_r_mdl.setValue( 15 - ( sbidata[43] & 0x0f ) ); + + // Modulator Wave Select + op1_waveform_mdl.setValue( sbidata[44] & 0x03 ); + + // Carrier Wave Select + op2_waveform_mdl.setValue( sbidata[45] & 0x03 ); + + // Feedback/Connection + fm_mdl.setValue( (sbidata[46] & 0x01) == 0x01 ? false : true ); + feedback_mdl.setValue( ( (sbidata[46] & 0x0e ) >> 1 ) ); + } +} + + + opl2instrumentView::opl2instrumentView( Instrument * _instrument, diff --git a/plugins/opl2/opl2instrument.h b/plugins/opl2/opl2instrument.h index 7cd644942..b1cce9c3b 100644 --- a/plugins/opl2/opl2instrument.h +++ b/plugins/opl2/opl2instrument.h @@ -33,8 +33,11 @@ #include "Knob.h" #include "PixmapButton.h" -#define OPL2_VOICE_FREE 255 +// This one is a flag, MIDI notes take 7 low bits +#define OPL2_VOICE_FREE 128 #define OPL2_NO_VOICE 255 +#define OPL2_VOICES 9 + // The "normal" range for LMMS pitchbends #define DEFAULT_BEND_CENTS 100 @@ -58,8 +61,9 @@ public: void saveSettings( QDomDocument & _doc, QDomElement & _this ); void loadSettings( const QDomElement & _this ); - void loadPatch(unsigned char inst[14]); + void loadPatch(const unsigned char inst[14]); void tuneEqual(int center, float Hz); + virtual void loadFile( const QString& file ); IntModel m_patchModel; @@ -111,6 +115,7 @@ private slots: private: Copl *theEmulator; + QString storedname; fpp_t frameCount; short *renderbuffer; int voiceNote[9];