/* * sid_instrument.cpp - ResID based software-synthesizer * * Copyright (c) 2008 Csaba Hruska * Attila Herman * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * 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. * */ #include #include #include #include "sid.h" #include "sid_instrument.h" #include "engine.h" #include "InstrumentTrack.h" #include "knob.h" #include "note_play_handle.h" #include "pixmap_button.h" #include "tooltip.h" #include "embed.cpp" #define C64_PAL_CYCLES_PER_SEC 985248 #define NUMSIDREGS 0x19 #define SIDWRITEDELAY 9 // lda $xxxx,x 4 cycles, sta $d400,x 5 cycles #define SIDWAVEDELAY 4 // and $xxxx,x 4 cycles extra unsigned char sidorder[] = {0x15,0x16,0x18,0x17, 0x05,0x06,0x02,0x03,0x00,0x01,0x04, 0x0c,0x0d,0x09,0x0a,0x07,0x08,0x0b, 0x13,0x14,0x10,0x11,0x0e,0x0f,0x12}; static const char *attackTime[16] = { "2 mS", "8 mS", "16 mS", "24 mS", "38 mS", "56 mS", "68 mS", "80 mS", "100 mS", "250 mS", "500 mS", "800 mS", "1 S", "3 S", "5 S", "8 S" }; static const char *decRelTime[16] = { "6 mS", "24 mS", "48 mS", "72 mS", "114 mS", "168 mS", "204 mS", "240 mS", "300 mS", "750 mS", "1.5 S", "2.4 S", "3 S", "9 S", "15 S", "24 S" }; // release time time in ms static const int relTime[16] = { 6, 24, 48, 72, 114, 168, 204, 240, 300, 750, 1500, 2400, 3000, 9000, 15000, 24000 }; extern "C" { Plugin::Descriptor PLUGIN_EXPORT sid_plugin_descriptor = { STRINGIFY( PLUGIN_NAME ), "SID", QT_TRANSLATE_NOOP( "pluginBrowser", "Emulation of the MOS6581 and MOS8580 " "SID.\nThis chip was used in the Commodore 64 computer." ), "Csaba Hruska " "Attila Herman ", 0x0100, Plugin::Instrument, new PluginPixmapLoader( "logo" ), NULL, NULL } ; } voiceObject::voiceObject( Model * _parent, int _idx ) : Model( _parent ), m_pulseWidthModel( 2048.0f, 0.0f, 4095.0f, 1.0f, this, tr( "Voice %1 pulse width" ).arg( _idx+1 ) ), m_attackModel( 8.0f, 0.0f, 15.0f, 1.0f, this, tr( "Voice %1 attack" ).arg( _idx+1 ) ), m_decayModel( 8.0f, 0.0f, 15.0f, 1.0f, this, tr( "Voice %1 decay" ).arg( _idx+1 ) ), m_sustainModel( 15.0f, 0.0f, 15.0f, 1.0f, this, tr( "Voice %1 sustain" ).arg( _idx+1 ) ), m_releaseModel( 8.0f, 0.0f, 15.0f, 1.0f, this, tr( "Voice %1 release" ).arg( _idx+1 ) ), m_coarseModel( 0.0f, -24.0, 24.0, 1.0f, this, tr( "Voice %1 coarse detuning" ).arg( _idx+1 ) ), m_waveFormModel( TriangleWave, 0, NumWaveShapes-1, this, tr( "Voice %1 wave shape" ).arg( _idx+1 ) ), m_syncModel( false, this, tr( "Voice %1 sync" ).arg( _idx+1 ) ), m_ringModModel( false, this, tr( "Voice %1 ring modulate" ).arg( _idx+1 ) ), m_filteredModel( false, this, tr( "Voice %1 filtered" ).arg( _idx+1 ) ), m_testModel( false, this, tr( "Voice %1 test" ).arg( _idx+1 ) ) { } voiceObject::~voiceObject() { } sidInstrument::sidInstrument( InstrumentTrack * _instrument_track ) : Instrument( _instrument_track, &sid_plugin_descriptor ), // filter m_filterFCModel( 1024.0f, 0.0f, 2047.0f, 1.0f, this, tr( "Cutoff" ) ), m_filterResonanceModel( 8.0f, 0.0f, 15.0f, 1.0f, this, tr( "Resonance" ) ), m_filterModeModel( LowPass, 0, NumFilterTypes-1, this, tr( "Filter type" )), // misc m_voice3OffModel( false, this, tr( "Voice 3 off" ) ), m_volumeModel( 15.0f, 0.0f, 15.0f, 1.0f, this, tr( "Volume" ) ), m_chipModel( sidMOS8580, 0, NumChipModels-1, this, tr( "Chip model" ) ) { for( int i = 0; i < 3; ++i ) { m_voice[i] = new voiceObject( this, i ); } } sidInstrument::~sidInstrument() { } void sidInstrument::saveSettings( QDomDocument & _doc, QDomElement & _this ) { // voices for( int i = 0; i < 3; ++i ) { const QString is = QString::number( i ); m_voice[i]->m_pulseWidthModel.saveSettings( _doc, _this, "pulsewidth" + is ); m_voice[i]->m_attackModel.saveSettings( _doc, _this, "attack" + is ); m_voice[i]->m_decayModel.saveSettings( _doc, _this, "decay" + is ); m_voice[i]->m_sustainModel.saveSettings( _doc, _this, "sustain" + is ); m_voice[i]->m_releaseModel.saveSettings( _doc, _this, "release" + is ); m_voice[i]->m_waveFormModel.saveSettings( _doc, _this,"waveform" + is ); m_voice[i]->m_syncModel.saveSettings( _doc, _this, "sync" + is ); m_voice[i]->m_ringModModel.saveSettings( _doc, _this, "ringmod" + is ); m_voice[i]->m_filteredModel.saveSettings( _doc, _this,"filtered" + is ); m_voice[i]->m_testModel.saveSettings( _doc, _this, "test" + is ); } // filter m_filterFCModel.saveSettings( _doc, _this, "filterFC" ); m_filterResonanceModel.saveSettings( _doc, _this, "filterResonance" ); m_filterModeModel.saveSettings( _doc, _this, "filterMode" ); // misc m_voice3OffModel.saveSettings( _doc, _this, "voice3Off" ); m_volumeModel.saveSettings( _doc, _this, "volume" ); m_chipModel.saveSettings( _doc, _this, "chipModel" ); } void sidInstrument::loadSettings( const QDomElement & _this ) { // voices for( int i = 0; i < 3; ++i ) { const QString is = QString::number( i ); m_voice[i]->m_pulseWidthModel.loadSettings( _this, "pulsewidth" + is ); m_voice[i]->m_attackModel.loadSettings( _this, "attack" + is ); m_voice[i]->m_decayModel.loadSettings( _this, "decay" + is ); m_voice[i]->m_sustainModel.loadSettings( _this, "sustain" + is ); m_voice[i]->m_releaseModel.loadSettings( _this, "release" + is ); m_voice[i]->m_waveFormModel.loadSettings( _this, "waveform" + is ); m_voice[i]->m_syncModel.loadSettings( _this, "sync" + is ); m_voice[i]->m_ringModModel.loadSettings( _this, "ringmod" + is ); m_voice[i]->m_filteredModel.loadSettings( _this, "filtered" + is ); m_voice[i]->m_testModel.loadSettings( _this, "test" + is ); } // filter m_filterFCModel.loadSettings( _this, "filterFC" ); m_filterResonanceModel.loadSettings( _this, "filterResonance" ); m_filterModeModel.loadSettings( _this, "filterMode" ); // misc m_voice3OffModel.loadSettings( _this, "voice3Off" ); m_volumeModel.loadSettings( _this, "volume" ); m_chipModel.loadSettings( _this, "chipModel" ); } QString sidInstrument::nodeName() const { return( sid_plugin_descriptor.name ); } f_cnt_t sidInstrument::desiredReleaseFrames() const { const float samplerate = engine::getMixer()->processingSampleRate(); int maxrel = 0; for( int i = 0 ; i < 3 ; ++i ) { if( maxrel < m_voice[i]->m_releaseModel.value() ) maxrel = (int)m_voice[i]->m_releaseModel.value(); } return f_cnt_t( float(relTime[maxrel])*samplerate/1000.0 ); } static int sid_fillbuffer(unsigned char* sidreg, cSID *sid, int tdelta, short *ptr, int samples) { int tdelta2; int result; int total = 0; int c; // customly added int residdelay = 0; int badline = rand() % NUMSIDREGS; for (c = 0; c < NUMSIDREGS; c++) { unsigned char o = sidorder[c]; // Extra delay for loading the waveform (and mt_chngate,x) if ((o == 4) || (o == 11) || (o == 18)) { tdelta2 = SIDWAVEDELAY; result = sid->clock(tdelta2, ptr, samples); total += result; ptr += result; samples -= result; tdelta -= SIDWAVEDELAY; } // Possible random badline delay once per writing if ((badline == c) && (residdelay)) { tdelta2 = residdelay; result = sid->clock(tdelta2, ptr, samples); total += result; ptr += result; samples -= result; tdelta -= residdelay; } sid->write(o, sidreg[o]); tdelta2 = SIDWRITEDELAY; result = sid->clock(tdelta2, ptr, samples); total += result; ptr += result; samples -= result; tdelta -= SIDWRITEDELAY; } result = sid->clock(tdelta, ptr, samples); total += result; return total; } void sidInstrument::playNote( notePlayHandle * _n, sampleFrame * _working_buffer ) { const f_cnt_t tfp = _n->totalFramesPlayed(); const int clockrate = C64_PAL_CYCLES_PER_SEC; const int samplerate = engine::getMixer()->processingSampleRate(); if ( tfp == 0 ) { cSID *sid = new cSID(); sid->set_sampling_parameters( clockrate, SAMPLE_FAST, samplerate ); sid->set_chip_model( MOS8580 ); sid->enable_filter( true ); sid->reset(); _n->m_pluginData = sid; } const fpp_t frames = _n->framesLeftForCurrentPeriod(); cSID *sid = static_cast( _n->m_pluginData ); int delta_t = clockrate * frames / samplerate + 4; short buf[frames]; unsigned char sidreg[NUMSIDREGS]; for (int c = 0; c < NUMSIDREGS; c++) { sidreg[c] = 0x00; } if( (ChipModel)m_chipModel.value() == sidMOS6581 ) { sid->set_chip_model( MOS6581 ); } else { sid->set_chip_model( MOS8580 ); } // voices reg8 data8 = 0; reg8 data16 = 0; reg8 base = 0; float freq = 0.0; float note = 0.0; for( reg8 i = 0 ; i < 3 ; ++i ) { base = i*7; // freq ( Fn = Fout / Fclk * 16777216 ) + coarse detuning freq = _n->frequency(); note = 69.0 + 12.0 * log( freq / 440.0 ) / log( 2 ); note += m_voice[i]->m_coarseModel.value(); freq = 440.0 * pow( 2.0, (note-69.0)/12.0 ); data16 = int( freq / float(clockrate) * 16777216.0 ); sidreg[base+0] = data16&0x00FF; sidreg[base+1] = (data16>>8)&0x00FF; // pw data16 = (int)m_voice[i]->m_pulseWidthModel.value(); sidreg[base+2] = data16&0x00FF; sidreg[base+3] = (data16>>8)&0x000F; // control: wave form, (test), ringmod, sync, gate data8 = _n->released()?0:1; data8 += m_voice[i]->m_syncModel.value()?2:0; data8 += m_voice[i]->m_ringModModel.value()?4:0; data8 += m_voice[i]->m_testModel.value()?8:0; switch( m_voice[i]->m_waveFormModel.value() ) { default: break; case voiceObject::NoiseWave: data8 += 128; break; case voiceObject::SquareWave: data8 += 64; break; case voiceObject::SawWave: data8 += 32; break; case voiceObject::TriangleWave: data8 += 16; break; } sidreg[base+4] = data8&0x00FF; // ad data16 = (int)m_voice[i]->m_attackModel.value(); data8 = (data16&0x0F)<<4; data16 = (int)m_voice[i]->m_decayModel.value(); data8 += (data16&0x0F); sidreg[base+5] = data8&0x00FF; // sr data16 = (int)m_voice[i]->m_sustainModel.value(); data8 = (data16&0x0F)<<4; data16 = (int)m_voice[i]->m_releaseModel.value(); data8 += (data16&0x0F); sidreg[base+6] = data8&0x00FF; } // filtered // FC (FilterCutoff) data16 = (int)m_filterFCModel.value(); sidreg[21] = data16&0x0007; sidreg[22] = (data16>>3)&0x00FF; // res, filt ex,3,2,1 data16 = (int)m_filterResonanceModel.value(); data8 = (data16&0x000F)<<4; data8 += m_voice[2]->m_filteredModel.value()?4:0; data8 += m_voice[1]->m_filteredModel.value()?2:0; data8 += m_voice[0]->m_filteredModel.value()?1:0; sidreg[23] = data8&0x00FF; // mode vol data16 = (int)m_volumeModel.value(); data8 = data16&0x000F; data8 += m_voice3OffModel.value()?128:0; switch( m_filterModeModel.value() ) { default: break; case LowPass: data8 += 16; break; case BandPass: data8 += 32; break; case HighPass: data8 += 64; break; } sidreg[24] = data8&0x00FF; int num = sid_fillbuffer(sidreg, sid,delta_t,buf, frames); if(num!=frames) printf("!!!Not enough samples\n"); for( fpp_t frame = 0; frame < frames; ++frame ) { sample_t s = float(buf[frame])/32768.0; for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) { _working_buffer[frame][ch] = s; } } instrumentTrack()->processAudioBuffer( _working_buffer, frames, _n ); } void sidInstrument::deleteNotePluginData( notePlayHandle * _n ) { delete static_cast( _n->m_pluginData ); } PluginView * sidInstrument::instantiateView( QWidget * _parent ) { return( new sidInstrumentView( this, _parent ) ); } class sidKnob : public knob { public: sidKnob( QWidget * _parent ) : knob( knobStyled, _parent ) { setFixedSize( 26, 25 ); setCenterPointX( 13.0 ); setCenterPointY( 14.0 ); setInnerRadius( 0 ); setOuterRadius( 5 ); setTotalAngle( 270.0 ); setLineWidth( 1 ); } }; sidInstrumentView::sidInstrumentView( Instrument * _instrument, QWidget * _parent ) : InstrumentView( _instrument, _parent ) { setAutoFillBackground( true ); QPalette pal; pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( "artwork" ) ); setPalette( pal ); m_volKnob = new sidKnob( this ); m_volKnob->setHintText( tr( "Volume:" ) + " ", "" ); m_volKnob->move( 7, 60 ); m_resKnob = new sidKnob( this ); m_resKnob->setHintText( tr( "Resonance:" ) + " ", "" ); m_resKnob->move( 7 + 29, 60 ); m_cutKnob = new sidKnob( this ); m_cutKnob->setHintText( tr( "Cutoff frequency:" ) + " ", "Hz" ); m_cutKnob->move( 7 + 2*29, 60 ); pixmapButton * hp_btn = new pixmapButton( this, NULL ); hp_btn->move( 176, 53 ); hp_btn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "hpred" ) ); hp_btn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "hp" ) ); toolTip::add( hp_btn, tr( "High-Pass filter ") ); pixmapButton * bp_btn = new pixmapButton( this, NULL ); bp_btn->move( 176 + 14, 53 ); bp_btn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "bpred" ) ); bp_btn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "bp" ) ); toolTip::add( bp_btn, tr( "Band-Pass filter ") ); pixmapButton * lp_btn = new pixmapButton( this, NULL ); lp_btn->move( 176 +2*14, 53 ); lp_btn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "lpred" ) ); lp_btn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "lp" ) ); toolTip::add( lp_btn, tr( "Low-Pass filter ") ); m_passBtnGrp = new automatableButtonGroup( this ); m_passBtnGrp->addButton( hp_btn ); m_passBtnGrp->addButton( bp_btn ); m_passBtnGrp->addButton( lp_btn ); m_offButton = new pixmapButton( this, NULL ); m_offButton->setCheckable( true ); m_offButton->move( 176 + 3*14, 53 ); m_offButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "3offred" ) ); m_offButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "3off" ) ); toolTip::add( m_offButton, tr( "Voice3 Off ") ); pixmapButton * mos6581_btn = new pixmapButton( this, NULL ); mos6581_btn->move( 190, 67 ); mos6581_btn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "6581red" ) ); mos6581_btn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "6581" ) ); toolTip::add( mos6581_btn, tr( "MOS6581 SID ") ); pixmapButton * mos8580_btn = new pixmapButton( this, NULL ); mos8580_btn->move( 218, 67 ); mos8580_btn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "8580red" ) ); mos8580_btn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "8580" ) ); toolTip::add( mos8580_btn, tr( "MOS8580 SID ") ); m_sidTypeBtnGrp = new automatableButtonGroup( this ); m_sidTypeBtnGrp->addButton( mos6581_btn ); m_sidTypeBtnGrp->addButton( mos8580_btn ); for( int i = 0; i < 3; i++ ) { knob *ak = new sidKnob( this ); ak->setHintText( tr("Attack:") + " ", "" ); ak->move( 7, 110 + i*50 ); ak->setWhatsThis( tr ( "Attack rate determines how rapidly the output " "of Voice %1 rises from zero to peak amplitude." ).arg( i+1 ) ); knob *dk = new sidKnob( this ); dk->setHintText( tr("Decay:") + " ", "" ); dk->move( 7 + 29, 110 + i*50 ); dk->setWhatsThis( tr ( "Decay rate determines how rapidly the output " "falls from the peak amplitude to the selected Sustain level." ) ); knob *sk = new sidKnob( this ); sk->setHintText( tr("Sustain:") + " ", "" ); sk->move( 7 + 2*29, 110 + i*50 ); sk->setWhatsThis( tr ( "Output of Voice %1 will remain at the selected " "Sustain amplitude as long as the note is held." ).arg( i+1 ) ); knob *rk = new sidKnob( this ); rk->setHintText( tr("Release:") + " ", "" ); rk->move( 7 + 3*29, 110 + i*50 ); rk->setWhatsThis( tr ( "The output of of Voice %1 will fall from " "Sustain amplitude to zero amplitude at the selected Release " "rate." ).arg( i+1 ) ); knob *pwk = new sidKnob( this ); pwk->setHintText( tr("Pulse Width:") + " ", "" ); pwk->move( 7 + 4*29, 110 + i*50 ); pwk->setWhatsThis( tr ( "The Pulse Width resolution allows the width " "to be smoothly swept with no discernable stepping. The Pulse " "waveform on Oscillator %1 must be selected to have any audible" " effect." ).arg( i+1 ) ); knob *crsk = new sidKnob( this ); crsk->setHintText( tr("Coarse:") + " ", " semitones" ); crsk->move( 160, 110 + i*50 ); crsk->setWhatsThis( tr ( "The Coarse detuning allows to detune Voice " "%1 one octave up or down." ).arg( i+1 ) ); pixmapButton * pulse_btn = new pixmapButton( this, NULL ); pulse_btn->move( 191, 103 + i*50 ); pulse_btn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "pulsered" ) ); pulse_btn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "pulse" ) ); toolTip::add( pulse_btn, tr( "Pulse Wave" ) ); pixmapButton * triangle_btn = new pixmapButton( this, NULL ); triangle_btn->move( 191 + 14, 103 + i*50 ); triangle_btn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "trianglered" ) ); triangle_btn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "triangle" ) ); toolTip::add( triangle_btn, tr( "Triangle Wave" ) ); pixmapButton * saw_btn = new pixmapButton( this, NULL ); saw_btn->move( 191 + 2*14, 103 + i*50 ); saw_btn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "sawred" ) ); saw_btn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "saw" ) ); toolTip::add( saw_btn, tr( "SawTooth" ) ); pixmapButton * noise_btn = new pixmapButton( this, NULL ); noise_btn->move( 191 + 3*14, 103 + i*50 ); noise_btn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "noisered" ) ); noise_btn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "noise" ) ); toolTip::add( noise_btn, tr( "Noise" ) ); automatableButtonGroup * wfbg = new automatableButtonGroup( this ); wfbg->addButton( pulse_btn ); wfbg->addButton( triangle_btn ); wfbg->addButton( saw_btn ); wfbg->addButton( noise_btn ); int syncRingWidth[] = { 3, 1, 2 }; pixmapButton * sync_btn = new pixmapButton( this, NULL ); sync_btn->setCheckable( true ); sync_btn->move( 191, 117 + i*50 ); sync_btn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "syncred" ) ); sync_btn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "sync" ) ); toolTip::add( sync_btn, tr( "Sync" ) ); sync_btn->setWhatsThis( tr ( "Sync synchronizes the fundamental " "frequency of Oscillator %1 with the fundamental frequency of " "Oscillator %2 producing \"Hard Sync\" effects." ).arg( i+1 ) .arg( syncRingWidth[i] ) ); pixmapButton * ringMod_btn = new pixmapButton( this, NULL ); ringMod_btn->setCheckable( true ); ringMod_btn->move( 191 + 2*14, 117 + i*50 ); ringMod_btn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "ringred" ) ); ringMod_btn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "ring" ) ); toolTip::add( ringMod_btn, tr( "Ring-Mod" ) ); ringMod_btn->setWhatsThis( tr ( "Ring-mod replaces the Triangle " "Waveform output of Oscillator %1 with a \"Ring Modulated\" " "combination of Oscillators %1 and %2." ).arg( i+1 ) .arg( syncRingWidth[i] ) ); pixmapButton * filter_btn = new pixmapButton( this, NULL ); filter_btn->setCheckable( true ); filter_btn->move( 191, 131 + i*50 ); filter_btn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "filterred" ) ); filter_btn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "filter" ) ); toolTip::add( filter_btn, tr( "Filtered" ) ); filter_btn->setWhatsThis( tr ( "When Filtered is on, Voice %1 will be " "processed through the Filter. When Filtered is off, Voice %1 " "appears directly at the output, and the Filter has no effect on " "it." ).arg( i+1 ) ); pixmapButton * test_btn = new pixmapButton( this, NULL ); test_btn->setCheckable( true ); test_btn->move( 191 +2*14, 131 + i*50 ); test_btn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "testred" ) ); test_btn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "test" ) ); toolTip::add( test_btn, tr( "Test" ) ); test_btn->setWhatsThis( tr ( "Test, when set, resets and locks " "Oscillator %1 at zero until Test is turned off." ).arg( i+1 ) ); m_voiceKnobs[i] = voiceKnobs( ak, dk, sk, rk, pwk, crsk, wfbg, sync_btn, ringMod_btn, filter_btn, test_btn ); } } sidInstrumentView::~sidInstrumentView() { } void sidInstrumentView::updateKnobHint() { sidInstrument * k = castModel(); for( int i = 0; i < 3; ++i ) { m_voiceKnobs[i].m_attKnob->setHintText( tr( "Attack:" ) + " ", " (" + QString::fromAscii( attackTime[(int)k->m_voice[i]-> m_attackModel.value()] ) + ")" ); toolTip::add( m_voiceKnobs[i].m_attKnob, attackTime[(int)k->m_voice[i]->m_attackModel.value()] ); m_voiceKnobs[i].m_decKnob->setHintText( tr( "Decay:" ) + " ", " (" + QString::fromAscii( decRelTime[(int)k->m_voice[i]-> m_decayModel.value()] ) + ")" ); toolTip::add( m_voiceKnobs[i].m_decKnob, decRelTime[(int)k->m_voice[i]->m_decayModel.value()] ); m_voiceKnobs[i].m_relKnob->setHintText( tr( "Release:" ) + " ", " (" + QString::fromAscii( decRelTime[(int)k->m_voice[i]-> m_releaseModel.value()] ) + ")" ); toolTip::add( m_voiceKnobs[i].m_relKnob, decRelTime[(int)k->m_voice[i]->m_releaseModel.value()]); m_voiceKnobs[i].m_pwKnob->setHintText( tr( "Pulse Width:" )+ " ", " (" + QString::number( (double)k->m_voice[i]-> m_pulseWidthModel.value() / 40.95 ) + "%)" ); toolTip::add( m_voiceKnobs[i].m_pwKnob, QString::number( (double)k->m_voice[i]-> m_pulseWidthModel.value() / 40.95 ) + "%" ); } m_cutKnob->setHintText( tr( "Cutoff frequency:" ) + " ", " (" + QString::number ( (int) ( 9970.0 / 2047.0 * (double)k->m_filterFCModel.value() + 30.0 ) ) + "Hz)" ); toolTip::add( m_cutKnob, QString::number( (int) ( 9970.0 / 2047.0 * (double)k->m_filterFCModel.value() + 30.0 ) ) + "Hz" ); } void sidInstrumentView::updateKnobToolTip() { sidInstrument * k = castModel(); for( int i = 0; i < 3; ++i ) { toolTip::add( m_voiceKnobs[i].m_sustKnob, QString::number( (int)k->m_voice[i]->m_sustainModel.value() ) ); toolTip::add( m_voiceKnobs[i].m_crsKnob, QString::number( (int)k->m_voice[i]->m_coarseModel.value() ) + " semitones" ); } toolTip::add( m_volKnob, QString::number( (int)k->m_volumeModel.value() ) ); toolTip::add( m_resKnob, QString::number( (int)k->m_filterResonanceModel.value() ) ); } void sidInstrumentView::modelChanged() { sidInstrument * k = castModel(); m_volKnob->setModel( &k->m_volumeModel ); m_resKnob->setModel( &k->m_filterResonanceModel ); m_cutKnob->setModel( &k->m_filterFCModel ); m_passBtnGrp->setModel( &k->m_filterModeModel ); m_offButton->setModel( &k->m_voice3OffModel ); m_sidTypeBtnGrp->setModel( &k->m_chipModel ); for( int i = 0; i < 3; ++i ) { m_voiceKnobs[i].m_attKnob->setModel( &k->m_voice[i]->m_attackModel ); m_voiceKnobs[i].m_decKnob->setModel( &k->m_voice[i]->m_decayModel ); m_voiceKnobs[i].m_sustKnob->setModel( &k->m_voice[i]->m_sustainModel ); m_voiceKnobs[i].m_relKnob->setModel( &k->m_voice[i]->m_releaseModel ); m_voiceKnobs[i].m_pwKnob->setModel( &k->m_voice[i]->m_pulseWidthModel ); m_voiceKnobs[i].m_crsKnob->setModel( &k->m_voice[i]->m_coarseModel ); m_voiceKnobs[i].m_waveFormBtnGrp->setModel( &k->m_voice[i]->m_waveFormModel ); m_voiceKnobs[i].m_syncButton->setModel( &k->m_voice[i]->m_syncModel ); m_voiceKnobs[i].m_ringModButton->setModel( &k->m_voice[i]->m_ringModModel ); m_voiceKnobs[i].m_filterButton->setModel( &k->m_voice[i]->m_filteredModel ); m_voiceKnobs[i].m_testButton->setModel( &k->m_voice[i]->m_testModel ); } for( int i = 0; i < 3; ++i ) { connect( &k->m_voice[i]->m_attackModel, SIGNAL( dataChanged() ), this, SLOT( updateKnobHint() ) ); connect( &k->m_voice[i]->m_decayModel, SIGNAL( dataChanged() ), this, SLOT( updateKnobHint() ) ); connect( &k->m_voice[i]->m_releaseModel, SIGNAL( dataChanged() ), this, SLOT( updateKnobHint() ) ); connect( &k->m_voice[i]->m_pulseWidthModel, SIGNAL( dataChanged() ), this, SLOT( updateKnobHint() ) ); connect( &k->m_voice[i]->m_sustainModel, SIGNAL( dataChanged() ), this, SLOT( updateKnobToolTip() ) ); connect( &k->m_voice[i]->m_coarseModel, SIGNAL( dataChanged() ), this, SLOT( updateKnobToolTip() ) ); } connect( &k->m_volumeModel, SIGNAL( dataChanged() ), this, SLOT( updateKnobToolTip() ) ); connect( &k->m_filterResonanceModel, SIGNAL( dataChanged() ), this, SLOT( updateKnobToolTip() ) ); connect( &k->m_filterFCModel, SIGNAL( dataChanged() ), this, SLOT( updateKnobHint() ) ); updateKnobHint(); updateKnobToolTip(); } extern "C" { // necessary for getting instance out of shared lib Plugin * PLUGIN_EXPORT lmms_plugin_main( Model *, void * _data ) { return( new sidInstrument( static_cast( _data ) ) ); } } #include "moc_sid_instrument.cxx"