123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878 |
- /*
- * OpulenZ.cpp - AdLib OPL2 FM synth based instrument
- *
- * Copyright (c) 2014 Raine M. Ekman <raine/at/iki/fi>
- *
- * This file is part of LMMS - https://lmms.io
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This program 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public
- * License along with this program (see COPYING); if not, write to the
- * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301 USA.
- *
- */
- // TODO:
- // - Better voice allocation: long releases get cut short :(
- // - 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???)
- // - Unison: 2,3,4, or 9 voices with configurable spread?
- // - Portamento (needs mono mode?)
- // - Pre-bend/post-bend in poly mode could use portamento speed?
- #include "OpulenZ.h"
- #include "Instrument.h"
- #include "Engine.h"
- #include "InstrumentPlayHandle.h"
- #include "InstrumentTrack.h"
- #include "Mixer.h"
- #include <QDomDocument>
- #include <QFile>
- #include <QFileInfo>
- #include <QByteArray>
- #include <assert.h>
- #include <math.h>
- #include "opl.h"
- #include "temuopl.h"
- #include "mididata.h"
- #include "embed.h"
- #include "debug.h"
- #include "Knob.h"
- #include "LcdSpinBox.h"
- #include "PixmapButton.h"
- #include "ToolTip.h"
- #include "plugin_export.h"
- extern "C"
- {
- Plugin::Descriptor PLUGIN_EXPORT opulenz_plugin_descriptor =
- {
- STRINGIFY( PLUGIN_NAME ),
- "OpulenZ",
- QT_TRANSLATE_NOOP( "pluginBrowser",
- "2-operator FM Synth" ),
- "Raine M. Ekman <raine/at/iki/fi>",
- 0x0100,
- Plugin::Instrument,
- new PluginPixmapLoader( "logo" ),
- "sbi",
- NULL
- };
- // necessary for getting instance out of shared lib
- PLUGIN_EXPORT Plugin * lmms_plugin_main( Model *m, void * )
- {
- return( new OpulenzInstrument( static_cast<InstrumentTrack *>( m ) ) );
- }
- }
- // I'd much rather do without a mutex, but it looks like
- // the emulator code isn't really ready for threads
- QMutex OpulenzInstrument::emulatorMutex;
- // Weird ordering of voice parameters
- const unsigned int adlib_opadd[OPL2_VOICES] = {0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12};
- OpulenzInstrument::OpulenzInstrument( InstrumentTrack * _instrument_track ) :
- Instrument( _instrument_track, &opulenz_plugin_descriptor ),
- m_patchModel( 0, 0, 127, this, tr( "Patch" ) ),
- op1_a_mdl(14.0, 0.0, 15.0, 1.0, this, tr( "Op 1 attack" ) ),
- op1_d_mdl(14.0, 0.0, 15.0, 1.0, this, tr( "Op 1 decay" ) ),
- op1_s_mdl(3.0, 0.0, 15.0, 1.0, this, tr( "Op 1 sustain" ) ),
- op1_r_mdl(10.0, 0.0, 15.0, 1.0, this, tr( "Op 1 release" ) ),
- op1_lvl_mdl(62.0, 0.0, 63.0, 1.0, this, tr( "Op 1 level" ) ),
- op1_scale_mdl(0.0, 0.0, 3.0, 1.0, this, tr( "Op 1 level scaling" ) ),
- op1_mul_mdl(0.0, 0.0, 15.0, 1.0, this, tr( "Op 1 frequency multiplier" ) ),
- feedback_mdl(0.0, 0.0, 7.0, 1.0, this, tr( "Op 1 feedback" ) ),
- op1_ksr_mdl(false, this, tr( "Op 1 key scaling rate" ) ),
- op1_perc_mdl(false, this, tr( "Op 1 percussive envelope" ) ),
- op1_trem_mdl(true, this, tr( "Op 1 tremolo" ) ),
- op1_vib_mdl(false, this, tr( "Op 1 vibrato" ) ),
- op1_w0_mdl( ),
- op1_w1_mdl( ),
- op1_w2_mdl( ),
- op1_w3_mdl( ),
- op1_waveform_mdl(0,0,3,this, tr( "Op 1 waveform" ) ),
- op2_a_mdl(1.0, 0.0, 15.0, 1.0, this, tr( "Op 2 attack" ) ),
- op2_d_mdl(3.0, 0.0, 15.0, 1.0, this, tr( "Op 2 decay" ) ),
- op2_s_mdl(14.0, 0.0, 15.0, 1.0, this, tr( "Op 2 sustain" ) ),
- op2_r_mdl(12.0, 0.0, 15.0, 1.0, this, tr( "Op 2 release" ) ),
- op2_lvl_mdl(63.0, 0.0, 63.0, 1.0, this, tr( "Op 2 level" ) ),
- op2_scale_mdl(0.0, 0.0, 3.0, 1.0, this, tr( "Op 2 level scaling" ) ),
- op2_mul_mdl(1.0, 0.0, 15.0, 1.0, this, tr( "Op 2 frequency multiplier" ) ),
- op2_ksr_mdl(false, this, tr( "Op 2 key scaling rate" ) ),
- op2_perc_mdl(false, this, tr( "Op 2 percussive envelope" ) ),
- op2_trem_mdl(false, this, tr( "Op 2 tremolo" ) ),
- op2_vib_mdl(true, this, tr( "Op 2 vibrato" ) ),
- op2_w0_mdl( ),
- op2_w1_mdl( ),
- op2_w2_mdl( ),
- op2_w3_mdl( ),
- op2_waveform_mdl(0,0,3,this, tr( "Op 2 waveform" ) ),
- fm_mdl(true, this, tr( "FM" ) ),
- vib_depth_mdl(false, this, tr( "Vibrato depth" ) ),
- trem_depth_mdl(false, this, tr( "Tremolo depth" ) )
- {
- // Create an emulator - samplerate, 16 bit, mono
- emulatorMutex.lock();
- theEmulator = new CTemuopl(Engine::mixer()->processingSampleRate(), true, false);
- theEmulator->init();
- // Enable waveform selection
- theEmulator->write(0x01,0x20);
- emulatorMutex.unlock();
- //Initialize voice values
- // voiceNote[0] = 0;
- // voiceLRU[0] = 0;
- for(int i=0; i<OPL2_VOICES; ++i) {
- voiceNote[i] = OPL2_VOICE_FREE;
- voiceLRU[i] = i;
- }
- storedname = displayName();
- updatePatch();
- // Can the buffer size change suddenly? I bet that would break lots of stuff
- frameCount = Engine::mixer()->framesPerPeriod();
- renderbuffer = new short[frameCount];
- // Some kind of sane defaults
- pitchbend = 0;
- pitchBendRange = 100; // cents
- RPNcoarse = RPNfine = 255;
- tuneEqual(69, 440);
- connect( Engine::mixer(), SIGNAL( sampleRateChanged() ),
- this, SLOT( reloadEmulator() ) );
- // Connect knobs
- // This one's for testing...
- connect( &m_patchModel, SIGNAL( dataChanged() ), this, SLOT( loadGMPatch() ) );
- #define MOD_CON( model ) connect( &model, SIGNAL( dataChanged() ), this, SLOT( updatePatch() ) );
- MOD_CON( op1_a_mdl );
- MOD_CON( op1_d_mdl );
- MOD_CON( op1_s_mdl );
- MOD_CON( op1_r_mdl );
- MOD_CON( op1_lvl_mdl );
- MOD_CON( op1_scale_mdl );
- MOD_CON( op1_mul_mdl );
- MOD_CON( feedback_mdl );
- MOD_CON( op1_ksr_mdl );
- MOD_CON( op1_perc_mdl );
- MOD_CON( op1_trem_mdl );
- MOD_CON( op1_vib_mdl );
- MOD_CON( op1_w0_mdl );
- MOD_CON( op1_w1_mdl );
- MOD_CON( op1_w2_mdl );
- MOD_CON( op1_w3_mdl );
- MOD_CON( op1_waveform_mdl );
- MOD_CON( op2_a_mdl );
- MOD_CON( op2_d_mdl );
- MOD_CON( op2_s_mdl );
- MOD_CON( op2_r_mdl );
- MOD_CON( op2_lvl_mdl );
- MOD_CON( op2_scale_mdl );
- MOD_CON( op2_mul_mdl );
- MOD_CON( op2_ksr_mdl );
- MOD_CON( op2_perc_mdl );
- MOD_CON( op2_trem_mdl );
- MOD_CON( op2_vib_mdl );
- MOD_CON( op2_w0_mdl );
- MOD_CON( op2_w1_mdl );
- MOD_CON( op2_w2_mdl );
- MOD_CON( op2_w3_mdl );
- MOD_CON( op2_waveform_mdl );
- MOD_CON( fm_mdl );
- MOD_CON( vib_depth_mdl );
- MOD_CON( trem_depth_mdl );
- // Connect the plugin to the mixer...
- InstrumentPlayHandle * iph = new InstrumentPlayHandle( this, _instrument_track );
- Engine::mixer()->addPlayHandle( iph );
- }
- OpulenzInstrument::~OpulenzInstrument() {
- delete theEmulator;
- Engine::mixer()->removePlayHandlesOfTypes( instrumentTrack(),
- PlayHandle::TypeNotePlayHandle
- | PlayHandle::TypeInstrumentPlayHandle );
- delete [] renderbuffer;
- }
- // Samplerate changes when choosing oversampling, so this is more or less mandatory
- void OpulenzInstrument::reloadEmulator() {
- delete theEmulator;
- emulatorMutex.lock();
- theEmulator = new CTemuopl(Engine::mixer()->processingSampleRate(), true, false);
- theEmulator->init();
- theEmulator->write(0x01,0x20);
- emulatorMutex.unlock();
- for(int i=0; i<OPL2_VOICES; ++i) {
- voiceNote[i] = OPL2_VOICE_FREE;
- voiceLRU[i] = i;
- }
- updatePatch();
- }
- // This shall only be called from code protected by the holy Mutex!
- void OpulenzInstrument::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 ) );
- }
- // Pop least recently used voice
- int OpulenzInstrument::popVoice() {
- int tmp = voiceLRU[0];
- for( int i=0; i<OPL2_VOICES-1; ++i) {
- voiceLRU[i] = voiceLRU[i+1];
- }
- voiceLRU[OPL2_VOICES-1] = OPL2_NO_VOICE;
- #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 tmp;
- }
- // Push voice into first free slot
- int OpulenzInstrument::pushVoice(int v) {
- int i;
- assert(voiceLRU[OPL2_VOICES-1]==OPL2_NO_VOICE);
- for(i=OPL2_VOICES-1; i>0; --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;
- }
- bool OpulenzInstrument::handleMidiEvent( const MidiEvent& event, const MidiTime& time, f_cnt_t offset )
- {
- emulatorMutex.lock();
- int key, vel, voice, tmp_pb;
- switch(event.type()) {
- case MidiNoteOn:
- // to get us in line with MIDI(?)
- key = event.key() +12;
- vel = event.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;
- }
- break;
- case MidiNoteOff:
- key = event.key() +12;
- for(voice=0; voice<OPL2_VOICES; ++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 = event.key() +12;
- vel = event.velocity();
- if( velocities[key] != 0) {
- velocities[key] = vel;
- }
- for(voice=0; voice<OPL2_VOICES; ++voice) {
- if(voiceNote[voice] == key) {
- setVoiceVelocity(voice, vel);
- }
- }
- break;
- case MidiPitchBend:
- // Update fnumber table
- // Neutral = 8192, full downbend = 0, full upbend = 16383
- tmp_pb = ( event.pitchBend()-8192 ) * pitchBendRange / 8192;
- if( tmp_pb != pitchbend ) {
- pitchbend = tmp_pb;
- tuneEqual(69, 440.0);
- }
- // Update pitch of all voices (also released ones)
- for( int v=0; v<OPL2_VOICES; ++v ) {
- int vn = (voiceNote[v] & ~OPL2_VOICE_FREE); // remove the flag bit
- int playing = (voiceNote[v] & OPL2_VOICE_FREE) == 0; // just the flag bit
- theEmulator->write(0xA0+v, fnums[vn] & 0xff);
- theEmulator->write(0xB0+v, (playing ? 32 : 0) + ((fnums[vn] & 0x1f00) >> 8) );
- }
- break;
- case MidiControlChange:
- switch (event.controllerNumber()) {
- case MidiControllerRegisteredParameterNumberLSB:
- RPNfine = event.controllerValue();
- break;
- case MidiControllerRegisteredParameterNumberMSB:
- RPNcoarse = event.controllerValue();
- break;
- case MidiControllerDataEntry:
- if( (RPNcoarse << 8) + RPNfine == MidiPitchBendSensitivityRPN) {
- pitchBendRange = event.controllerValue() * 100;
- }
- 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;
- }
- QString OpulenzInstrument::nodeName() const
- {
- return( opulenz_plugin_descriptor.name );
- }
- PluginView * OpulenzInstrument::instantiateView( QWidget * _parent )
- {
- return( new OpulenzInstrumentView( this, _parent ) );
- }
- void OpulenzInstrument::play( sampleFrame * _working_buffer )
- {
- emulatorMutex.lock();
- theEmulator->update(renderbuffer, frameCount);
- for( fpp_t frame = 0; frame < frameCount; ++frame )
- {
- sample_t s = float(renderbuffer[frame]) / 8192.0;
- for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch )
- {
- _working_buffer[frame][ch] = s;
- }
- }
- emulatorMutex.unlock();
- // Throw the data to the track...
- instrumentTrack()->processAudioBuffer( _working_buffer, frameCount, NULL );
- }
- void OpulenzInstrument::saveSettings( QDomDocument & _doc, QDomElement & _this )
- {
- op1_a_mdl.saveSettings( _doc, _this, "op1_a" );
- op1_d_mdl.saveSettings( _doc, _this, "op1_d" );
- op1_s_mdl.saveSettings( _doc, _this, "op1_s" );
- op1_r_mdl.saveSettings( _doc, _this, "op1_r" );
- op1_lvl_mdl.saveSettings( _doc, _this, "op1_lvl" );
- op1_scale_mdl.saveSettings( _doc, _this, "op1_scale" );
- op1_mul_mdl.saveSettings( _doc, _this, "op1_mul" );
- feedback_mdl.saveSettings( _doc, _this, "feedback" );
- op1_ksr_mdl.saveSettings( _doc, _this, "op1_ksr" );
- op1_perc_mdl.saveSettings( _doc, _this, "op1_perc" );
- op1_trem_mdl.saveSettings( _doc, _this, "op1_trem" );
- op1_vib_mdl.saveSettings( _doc, _this, "op1_vib" );
- op1_waveform_mdl.saveSettings( _doc, _this, "op1_waveform" );
- op2_a_mdl.saveSettings( _doc, _this, "op2_a" );
- op2_d_mdl.saveSettings( _doc, _this, "op2_d" );
- op2_s_mdl.saveSettings( _doc, _this, "op2_s" );
- op2_r_mdl.saveSettings( _doc, _this, "op2_r" );
- op2_lvl_mdl.saveSettings( _doc, _this, "op2_lvl" );
- op2_scale_mdl.saveSettings( _doc, _this, "op2_scale" );
- op2_mul_mdl.saveSettings( _doc, _this, "op2_mul" );
- op2_ksr_mdl.saveSettings( _doc, _this, "op2_ksr" );
- op2_perc_mdl.saveSettings( _doc, _this, "op2_perc" );
- op2_trem_mdl.saveSettings( _doc, _this, "op2_trem" );
- op2_vib_mdl.saveSettings( _doc, _this, "op2_vib" );
- op2_waveform_mdl.saveSettings( _doc, _this, "op2_waveform" );
- fm_mdl.saveSettings( _doc, _this, "fm" );
- vib_depth_mdl.saveSettings( _doc, _this, "vib_depth" );
- trem_depth_mdl.saveSettings( _doc, _this, "trem_depth" );
- }
- void OpulenzInstrument::loadSettings( const QDomElement & _this )
- {
- op1_a_mdl.loadSettings( _this, "op1_a" );
- op1_d_mdl.loadSettings( _this, "op1_d" );
- op1_s_mdl.loadSettings( _this, "op1_s" );
- op1_r_mdl.loadSettings( _this, "op1_r" );
- op1_lvl_mdl.loadSettings( _this, "op1_lvl" );
- op1_scale_mdl.loadSettings( _this, "op1_scale" );
- op1_mul_mdl.loadSettings( _this, "op1_mul" );
- feedback_mdl.loadSettings( _this, "feedback" );
- op1_ksr_mdl.loadSettings( _this, "op1_ksr" );
- op1_perc_mdl.loadSettings( _this, "op1_perc" );
- op1_trem_mdl.loadSettings( _this, "op1_trem" );
- op1_vib_mdl.loadSettings( _this, "op1_vib" );
- op1_waveform_mdl.loadSettings( _this, "op1_waveform" );
- op2_a_mdl.loadSettings( _this, "op2_a" );
- op2_d_mdl.loadSettings( _this, "op2_d" );
- op2_s_mdl.loadSettings( _this, "op2_s" );
- op2_r_mdl.loadSettings( _this, "op2_r" );
- op2_lvl_mdl.loadSettings( _this, "op2_lvl" );
- op2_scale_mdl.loadSettings( _this, "op2_scale" );
- op2_mul_mdl.loadSettings( _this, "op2_mul" );
- op2_ksr_mdl.loadSettings( _this, "op2_ksr" );
- op2_perc_mdl.loadSettings( _this, "op2_perc" );
- op2_trem_mdl.loadSettings( _this, "op2_trem" );
- op2_vib_mdl.loadSettings( _this, "op2_vib" );
- op2_waveform_mdl.loadSettings( _this, "op2_waveform" );
- fm_mdl.loadSettings( _this, "fm" );
- vib_depth_mdl.loadSettings( _this, "vib_depth" );
- trem_depth_mdl.loadSettings( _this, "trem_depth" );
- }
- // Load a patch into the emulator
- void OpulenzInstrument::loadPatch(const unsigned char inst[14]) {
- emulatorMutex.lock();
- for(int v=0; v<OPL2_VOICES; ++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 - 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
- theEmulator->write(0x83+adlib_opadd[v],inst[7]); // op2
- theEmulator->write(0xe0+adlib_opadd[v],inst[8]); // op1 waveform
- theEmulator->write(0xe3+adlib_opadd[v],inst[9]); // op2
- theEmulator->write(0xc0+v,inst[10]); // feedback/algorithm
- }
- emulatorMutex.unlock();
- }
- void OpulenzInstrument::tuneEqual(int center, float Hz) {
- float tmp;
- for(int n=0; n<128; ++n) {
- tmp = Hz*pow( 2.0, ( n - center ) * ( 1.0 / 12.0 ) + pitchbend * ( 1.0 / 1200.0 ) );
- fnums[n] = Hz2fnum( tmp );
- }
- }
- // Find suitable F number in lowest possible block
- int OpulenzInstrument::Hz2fnum(float Hz) {
- for(int block=0; block<8; ++block) {
- unsigned int fnum = Hz * pow( 2.0, 20.0 - (double)block ) * ( 1.0 / 49716.0 );
- if(fnum<1023) {
- return fnum + (block << 10);
- }
- }
- return 0;
- }
- // Load one of the default patches
- void OpulenzInstrument::loadGMPatch() {
- unsigned char *inst = midi_fm_instruments[m_patchModel.value()];
- loadPatch(inst);
- }
- // Update patch from the models to the chip emulation
- void OpulenzInstrument::updatePatch() {
- 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"
- ( op1_ksr_mdl.value() ? 16 : 0 ) +
- ((int)op1_mul_mdl.value() & 0x0f);
- inst[1] = ( op2_trem_mdl.value() ? 128 : 0 ) +
- ( op2_vib_mdl.value() ? 64 : 0 ) +
- ( 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 ) +
- (63 - ( (int)op1_lvl_mdl.value() & 0x3f ) );
- 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 ) );
- inst[5] = ((15 - ( (int)op2_a_mdl.value() & 0x0f ) ) << 4 )+
- (15 - ( (int)op2_d_mdl.value() & 0x0f ) );
- inst[6] = ((15 - ( (int)op1_s_mdl.value() & 0x0f ) ) << 4 ) +
- (15 - ( (int)op1_r_mdl.value() & 0x0f ) );
- inst[7] = ((15 - ( (int)op2_s_mdl.value() & 0x0f ) ) << 4 ) +
- (15 - ( (int)op2_r_mdl.value() & 0x0f ) );
- inst[8] = (int)op1_waveform_mdl.value() & 0x03;
- inst[9] = (int)op2_waveform_mdl.value() & 0x03;
- inst[10] = (fm_mdl.value() ? 0 : 1 ) +
- (((int)feedback_mdl.value() & 0x07 )<< 1);
- // These are always 0 in the list I had?
- inst[11] = 0;
- inst[12] = 0;
- inst[13] = 0;
- // 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 < OPL2_VOICES ; ++voice) {
- 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 OpulenzInstrument::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 ) );
- }
- }
- OpulenzInstrumentView::OpulenzInstrumentView( Instrument * _instrument,
- QWidget * _parent ) :
- InstrumentViewFixedSize( _instrument, _parent )
- {
- #define KNOB_GEN(knobname, hinttext, hintunit,xpos,ypos) \
- knobname = new Knob( knobStyled, this );\
- knobname->setHintText( tr(hinttext), hintunit );\
- knobname->setFixedSize(22,22);\
- knobname->setCenterPointX(11.0);\
- knobname->setCenterPointY(11.0);\
- knobname->setTotalAngle(270.0);\
- knobname->move(xpos,ypos);
- #define BUTTON_GEN(buttname, tooltip, xpos, ypos) \
- buttname = new PixmapButton( this, NULL );\
- buttname->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "led_on" ) );\
- buttname->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "led_off" ) );\
- buttname->setCheckable( true );\
- ToolTip::add( buttname, tr( tooltip ) );\
- buttname->move( xpos, ypos );
- #define WAVEBUTTON_GEN(buttname, tooltip, xpos, ypos, icon_on, icon_off, buttgroup) \
- buttname = new PixmapButton( this, NULL );\
- buttname->setActiveGraphic( PLUGIN_NAME::getIconPixmap( icon_on ) ); \
- buttname->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( icon_off ) ); \
- ToolTip::add( buttname, tr( tooltip ) );\
- buttname->move( xpos, ypos );\
- buttgroup->addButton(buttname);
- // OP1 knobs & buttons...
- KNOB_GEN(op1_a_kn, "Attack", "", 6, 48);
- KNOB_GEN(op1_d_kn, "Decay", "", 34, 48);
- KNOB_GEN(op1_s_kn, "Sustain", "", 62, 48);
- KNOB_GEN(op1_r_kn, "Release", "", 90, 48);
- KNOB_GEN(op1_lvl_kn, "Level", "", 166, 48);
- KNOB_GEN(op1_scale_kn, "Scale", "", 194, 48);
- KNOB_GEN(op1_mul_kn, "Frequency multiplier", "", 222, 48);
- BUTTON_GEN(op1_ksr_btn, "Keyboard scaling rate", 9, 87);
- BUTTON_GEN(op1_perc_btn, "Percussive envelope", 36, 87);
- BUTTON_GEN(op1_trem_btn, "Tremolo", 65, 87);
- BUTTON_GEN(op1_vib_btn, "Vibrato", 93, 87);
- KNOB_GEN(feedback_kn, "Feedback", "", 128, 48);
- op1_waveform = new automatableButtonGroup( this );
- WAVEBUTTON_GEN(op1_w0_btn,"Sine", 154, 86, "wave1_on", "wave1_off", op1_waveform);
- WAVEBUTTON_GEN(op1_w1_btn,"Half sine", 178, 86, "wave2_on", "wave2_off", op1_waveform);
- WAVEBUTTON_GEN(op1_w2_btn,"Absolute sine", 199, 86, "wave3_on", "wave3_off", op1_waveform);
- WAVEBUTTON_GEN(op1_w3_btn,"Quarter sine", 220, 86, "wave4_on", "wave4_off", op1_waveform);
- // And the same for OP2
- KNOB_GEN(op2_a_kn, "Attack", "", 6, 138);
- KNOB_GEN(op2_d_kn, "Decay", "", 34, 138);
- KNOB_GEN(op2_s_kn, "Sustain", "", 62, 138);
- KNOB_GEN(op2_r_kn, "Release", "", 90, 138);
- KNOB_GEN(op2_lvl_kn, "Level", "", 166, 138);
- KNOB_GEN(op2_scale_kn, "Scale", "", 194, 138);
- KNOB_GEN(op2_mul_kn, "Frequency multiplier", "", 222, 138);
- BUTTON_GEN(op2_ksr_btn, "Keyboard scaling rate", 9, 177);
- BUTTON_GEN(op2_perc_btn, "Percussive envelope", 36, 177);
- BUTTON_GEN(op2_trem_btn, "Tremolo", 65, 177);
- BUTTON_GEN(op2_vib_btn, "Vibrato", 93, 177);
- op2_waveform = new automatableButtonGroup( this );
- WAVEBUTTON_GEN(op2_w0_btn,"Sine", 154, 176, "wave1_on", "wave1_off", op2_waveform);
- WAVEBUTTON_GEN(op2_w1_btn,"Half sine", 178, 176, "wave2_on", "wave2_off", op2_waveform);
- WAVEBUTTON_GEN(op2_w2_btn,"Absolute sine", 199, 176, "wave3_on", "wave3_off", op2_waveform);
- WAVEBUTTON_GEN(op2_w3_btn,"Quarter Sine", 220, 176, "wave4_on", "wave4_off", op2_waveform);
- BUTTON_GEN(fm_btn, "FM", 9, 220);
- BUTTON_GEN(vib_depth_btn, "Vibrato depth", 65, 220);
- BUTTON_GEN(trem_depth_btn, "Tremolo depth", 93, 220);
- setAutoFillBackground( true );
- QPalette pal;
- pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap(
- "artwork" ) );
- setPalette( pal );
- }
- OpulenzInstrumentView::~OpulenzInstrumentView() {
- // Knobs are QWidgets and our children, so they're
- // destroyed automagically
- }
- // Returns text for time knob formatted nicely
- inline QString OpulenzInstrumentView::knobHintHelper(float n) {
- if(n>1000) {
- return QString::number(n/1000, 'f', 0)+ " s";
- } else if(n>10) {
- return QString::number(n, 'f', 0)+ " ms";
- } else {
- return QString::number(n, 'f', 1)+ " ms";
- }
- }
- void OpulenzInstrumentView::updateKnobHints()
- {
- // Envelope times in ms: t[0] = 0, t[n] = ( 1<<n ) * X, X = 0.11597 for A, 0.6311 for D/R
- // Here some rounding has been applied.
- const float attack_times[16] = {
- 0.0, 0.2, 0.4, 0.9, 1.8, 3.7, 7.4,
- 15.0, 30.0, 60.0, 120.0, 240.0, 480.0,
- 950.0, 1900.0, 3800.0
- };
- const float dr_times[16] = {
- 0.0, 1.2, 2.5, 5.0, 10.0, 20.0, 40.0,
- 80.0, 160.0, 320.0, 640.0, 1300.0, 2600.0,
- 5200.0, 10000.0, 20000.0
- };
-
- const int fmultipliers[16] = {
- -12, 0, 12, 19, 24, 28, 31, 34, 36, 38, 40, 40, 43, 43, 47, 47
- };
- OpulenzInstrument * m = castModel<OpulenzInstrument>();
-
- op1_a_kn->setHintText( tr( "Attack" ),
- " (" + knobHintHelper(attack_times[(int)m->op1_a_mdl.value()]) + ")");
- op2_a_kn->setHintText( tr( "Attack" ),
- " (" + knobHintHelper(attack_times[(int)m->op2_a_mdl.value()]) + ")");
- op1_d_kn->setHintText( tr( "Decay" ),
- " (" + knobHintHelper(dr_times[(int)m->op1_d_mdl.value()]) + ")");
- op2_d_kn->setHintText( tr( "Decay" ),
- " (" + knobHintHelper(dr_times[(int)m->op2_d_mdl.value()]) + ")");
- op1_r_kn->setHintText( tr( "Release" ),
- " (" + knobHintHelper(dr_times[(int)m->op1_r_mdl.value()]) + ")");
- op2_r_kn->setHintText( tr( "Release" ),
- " (" + knobHintHelper(dr_times[(int)m->op2_r_mdl.value()]) + ")");
- op1_mul_kn->setHintText( tr( "Frequency multiplier" ),
- " (" + QString::number(fmultipliers[(int)m->op1_mul_mdl.value()]) + " semitones)");
- op2_mul_kn->setHintText( tr( "Frequency multiplier" ),
- " (" + QString::number(fmultipliers[(int)m->op2_mul_mdl.value()]) + " semitones)");
- }
- void OpulenzInstrumentView::modelChanged()
- {
- OpulenzInstrument * m = castModel<OpulenzInstrument>();
- // m_patch->setModel( &m->m_patchModel );
- op1_a_kn->setModel( &m->op1_a_mdl );
- op1_d_kn->setModel( &m->op1_d_mdl );
- op1_s_kn->setModel( &m->op1_s_mdl );
- op1_r_kn->setModel( &m->op1_r_mdl );
- op1_lvl_kn->setModel( &m->op1_lvl_mdl );
- op1_scale_kn->setModel( &m->op1_scale_mdl );
- op1_mul_kn->setModel( &m->op1_mul_mdl );
- feedback_kn->setModel( &m->feedback_mdl );
- op1_ksr_btn->setModel( &m->op1_ksr_mdl );
- op1_perc_btn->setModel( &m->op1_perc_mdl );
- op1_trem_btn->setModel( &m->op1_trem_mdl );
- op1_vib_btn->setModel( &m->op1_vib_mdl );
- op1_waveform->setModel( &m->op1_waveform_mdl );
- op2_a_kn->setModel( &m->op2_a_mdl );
- op2_d_kn->setModel( &m->op2_d_mdl );
- op2_s_kn->setModel( &m->op2_s_mdl );
- op2_r_kn->setModel( &m->op2_r_mdl );
- op2_lvl_kn->setModel( &m->op2_lvl_mdl );
- op2_scale_kn->setModel( &m->op2_scale_mdl );
- op2_mul_kn->setModel( &m->op2_mul_mdl );
- op2_ksr_btn->setModel( &m->op2_ksr_mdl );
- op2_perc_btn->setModel( &m->op2_perc_mdl );
- op2_trem_btn->setModel( &m->op2_trem_mdl );
- op2_vib_btn->setModel( &m->op2_vib_mdl );
- op2_waveform->setModel( &m->op2_waveform_mdl );
- fm_btn->setModel( &m->fm_mdl );
- vib_depth_btn->setModel( &m->vib_depth_mdl );
- trem_depth_btn->setModel( &m->trem_depth_mdl );
- // All knobs needing a user friendly unit
- connect( &m->op1_a_mdl, SIGNAL( dataChanged() ), this, SLOT( updateKnobHints() ) );
- connect( &m->op2_a_mdl, SIGNAL( dataChanged() ), this, SLOT( updateKnobHints() ) );
- connect( &m->op1_d_mdl, SIGNAL( dataChanged() ), this, SLOT( updateKnobHints() ) );
- connect( &m->op2_d_mdl, SIGNAL( dataChanged() ), this, SLOT( updateKnobHints() ) );
- connect( &m->op1_r_mdl, SIGNAL( dataChanged() ), this, SLOT( updateKnobHints() ) );
- connect( &m->op2_r_mdl, SIGNAL( dataChanged() ), this, SLOT( updateKnobHints() ) );
- connect( &m->op1_mul_mdl, SIGNAL( dataChanged() ), this, SLOT( updateKnobHints() ) );
- connect( &m->op2_mul_mdl, SIGNAL( dataChanged() ), this, SLOT( updateKnobHints() ) );
- updateKnobHints();
- }
|