1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399 |
- /*
- * GigPlayer.cpp - a GIG player using libgig (based on Sf2 player plugin)
- *
- * Copyright (c) 2008 Paul Giblock <drfaygo/at/gmail/dot/com>
- * Copyright (c) 2009-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
- *
- * A few lines of code taken from LinuxSampler (also GPLv2) where noted:
- * Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck
- * Copyright (C) 2005-2008 Christian Schoenebeck
- * Copyright (C) 2009-2010 Christian Schoenebeck and Grigor Iliev
- *
- * 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.
- *
- */
- #include <cstring>
- #include <QDebug>
- #include <QLayout>
- #include <QLabel>
- #include <QDomDocument>
- #include "FileDialog.h"
- #include "GigPlayer.h"
- #include "Engine.h"
- #include "InstrumentTrack.h"
- #include "InstrumentPlayHandle.h"
- #include "Mixer.h"
- #include "NotePlayHandle.h"
- #include "Knob.h"
- #include "SampleBuffer.h"
- #include "Song.h"
- #include "ConfigManager.h"
- #include "endian_handling.h"
- #include "PatchesDialog.h"
- #include "ToolTip.h"
- #include "LcdSpinBox.h"
- #include "embed.h"
- #include "plugin_export.h"
- extern "C"
- {
- Plugin::Descriptor PLUGIN_EXPORT gigplayer_plugin_descriptor =
- {
- STRINGIFY( PLUGIN_NAME ),
- "GIG Player",
- QT_TRANSLATE_NOOP( "pluginBrowser", "Player for GIG files" ),
- "Garrett Wilson <g/at/floft/dot/net>",
- 0x0100,
- Plugin::Instrument,
- new PluginPixmapLoader( "logo" ),
- "gig",
- NULL
- } ;
- }
- GigInstrument::GigInstrument( InstrumentTrack * _instrument_track ) :
- Instrument( _instrument_track, &gigplayer_plugin_descriptor ),
- m_instance( NULL ),
- m_instrument( NULL ),
- m_filename( "" ),
- m_bankNum( 0, 0, 999, this, tr( "Bank" ) ),
- m_patchNum( 0, 0, 127, this, tr( "Patch" ) ),
- m_gain( 1.0f, 0.0f, 5.0f, 0.01f, this, tr( "Gain" ) ),
- m_interpolation( SRC_LINEAR ),
- m_RandomSeed( 0 ),
- m_currentKeyDimension( 0 )
- {
- InstrumentPlayHandle * iph = new InstrumentPlayHandle( this, _instrument_track );
- Engine::mixer()->addPlayHandle( iph );
- updateSampleRate();
- connect( &m_bankNum, SIGNAL( dataChanged() ), this, SLOT( updatePatch() ) );
- connect( &m_patchNum, SIGNAL( dataChanged() ), this, SLOT( updatePatch() ) );
- connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSampleRate() ) );
- }
- GigInstrument::~GigInstrument()
- {
- Engine::mixer()->removePlayHandlesOfTypes( instrumentTrack(),
- PlayHandle::TypeNotePlayHandle
- | PlayHandle::TypeInstrumentPlayHandle );
- freeInstance();
- }
- void GigInstrument::saveSettings( QDomDocument & _doc, QDomElement & _this )
- {
- _this.setAttribute( "src", m_filename );
- m_patchNum.saveSettings( _doc, _this, "patch" );
- m_bankNum.saveSettings( _doc, _this, "bank" );
- m_gain.saveSettings( _doc, _this, "gain" );
- }
- void GigInstrument::loadSettings( const QDomElement & _this )
- {
- openFile( _this.attribute( "src" ), false );
- m_patchNum.loadSettings( _this, "patch" );
- m_bankNum.loadSettings( _this, "bank" );
- m_gain.loadSettings( _this, "gain" );
- updatePatch();
- }
- void GigInstrument::loadFile( const QString & _file )
- {
- if( !_file.isEmpty() && QFileInfo( _file ).exists() )
- {
- openFile( _file, false );
- updatePatch();
- updateSampleRate();
- }
- }
- AutomatableModel * GigInstrument::childModel( const QString & _modelName )
- {
- if( _modelName == "bank" )
- {
- return &m_bankNum;
- }
- else if( _modelName == "patch" )
- {
- return &m_patchNum;
- }
- qCritical() << "requested unknown model " << _modelName;
- return NULL;
- }
- QString GigInstrument::nodeName() const
- {
- return gigplayer_plugin_descriptor.name;
- }
- void GigInstrument::freeInstance()
- {
- QMutexLocker synthLock( &m_synthMutex );
- QMutexLocker notesLock( &m_notesMutex );
- if( m_instance != NULL )
- {
- delete m_instance;
- m_instance = NULL;
- // If we're changing instruments, we got to make sure that we
- // remove all pointers to the old samples and don't try accessing
- // that instrument again
- m_instrument = NULL;
- m_notes.clear();
- }
- }
- void GigInstrument::openFile( const QString & _gigFile, bool updateTrackName )
- {
- emit fileLoading();
- // Remove the current instrument if one is selected
- freeInstance();
- {
- QMutexLocker locker( &m_synthMutex );
- try
- {
- m_instance = new GigInstance( SampleBuffer::tryToMakeAbsolute( _gigFile ) );
- m_filename = SampleBuffer::tryToMakeRelative( _gigFile );
- }
- catch( ... )
- {
- m_instance = NULL;
- m_filename = "";
- }
- }
- emit fileChanged();
- if( updateTrackName == true )
- {
- instrumentTrack()->setName( QFileInfo( _gigFile ).baseName() );
- updatePatch();
- }
- }
- void GigInstrument::updatePatch()
- {
- if( m_bankNum.value() >= 0 && m_patchNum.value() >= 0 )
- {
- getInstrument();
- }
- }
- QString GigInstrument::getCurrentPatchName()
- {
- QMutexLocker locker( &m_synthMutex );
- if( m_instance == NULL )
- {
- return "";
- }
- int iBankSelected = m_bankNum.value();
- int iProgSelected = m_patchNum.value();
- gig::Instrument * pInstrument = m_instance->gig.GetFirstInstrument();
- while( pInstrument != NULL )
- {
- int iBank = pInstrument->MIDIBank;
- int iProg = pInstrument->MIDIProgram;
- if( iBank == iBankSelected && iProg == iProgSelected )
- {
- QString name = QString::fromStdString( pInstrument->pInfo->Name );
- if( name == "" )
- {
- name = "<no name>";
- }
- return name;
- }
- pInstrument = m_instance->gig.GetNextInstrument();
- }
- return "";
- }
- // A key has been pressed
- void GigInstrument::playNote( NotePlayHandle * _n, sampleFrame * )
- {
- const float LOG440 = 2.643452676f;
- const f_cnt_t tfp = _n->totalFramesPlayed();
- int midiNote = (int) floor( 12.0 * ( log2( _n->unpitchedFrequency() ) - LOG440 ) - 4.0 );
- // out of range?
- if( midiNote <= 0 || midiNote >= 128 )
- {
- return;
- }
- if( tfp == 0 )
- {
- GIGPluginData * pluginData = new GIGPluginData;
- pluginData->midiNote = midiNote;
- _n->m_pluginData = pluginData;
- const int baseVelocity = instrumentTrack()->midiPort()->baseVelocity();
- const uint velocity = _n->midiVelocity( baseVelocity );
- QMutexLocker locker( &m_notesMutex );
- m_notes.push_back( GigNote( midiNote, velocity, _n->unpitchedFrequency(), pluginData ) );
- }
- }
- // Process the notes and output a certain number of frames (e.g. 256, set in
- // the preferences)
- void GigInstrument::play( sampleFrame * _working_buffer )
- {
- const fpp_t frames = Engine::mixer()->framesPerPeriod();
- const int rate = Engine::mixer()->processingSampleRate();
- // Initialize to zeros
- std::memset( &_working_buffer[0][0], 0, DEFAULT_CHANNELS * frames * sizeof( float ) );
- m_synthMutex.lock();
- m_notesMutex.lock();
- if( m_instance == NULL || m_instrument == NULL )
- {
- m_synthMutex.unlock();
- m_notesMutex.unlock();
- return;
- }
- for( QList<GigNote>::iterator it = m_notes.begin(); it != m_notes.end(); ++it )
- {
- // Process notes in the KeyUp state, adding release samples if desired
- if( it->state == KeyUp )
- {
- // If there are no samples, we're done
- if( it->samples.empty() )
- {
- it->state = Completed;
- }
- else
- {
- it->state = PlayingKeyUp;
- // Notify each sample that the key has been released
- for( QList<GigSample>::iterator sample = it->samples.begin();
- sample != it->samples.end(); ++sample )
- {
- sample->adsr.keyup();
- }
- // Add release samples if available
- if( it->release == true )
- {
- addSamples( *it, true );
- }
- }
- }
- // Process notes in the KeyDown state, adding samples for the notes
- else if( it->state == KeyDown )
- {
- it->state = PlayingKeyDown;
- addSamples( *it, false );
- }
- // Delete ended samples
- for( QList<GigSample>::iterator sample = it->samples.begin();
- sample != it->samples.end(); ++sample )
- {
- // Delete if the ADSR for a sample is complete for normal
- // notes, or if a release sample, then if we've reached
- // the end of the sample
- if( sample->sample == NULL || sample->adsr.done() ||
- ( it->isRelease == true &&
- sample->pos >= sample->sample->SamplesTotal - 1 ) )
- {
- sample = it->samples.erase( sample );
- if( sample == it->samples.end() )
- {
- break;
- }
- }
- }
- // Delete ended notes (either in the completed state or all the samples ended)
- if( it->state == Completed || it->samples.empty() )
- {
- it = m_notes.erase( it );
- if( it == m_notes.end() )
- {
- break;
- }
- }
- }
- // Fill buffer with portions of the note samples
- for( QList<GigNote>::iterator it = m_notes.begin(); it != m_notes.end(); ++it )
- {
- // Only process the notes if we're in a playing state
- if( !( it->state == PlayingKeyDown ||
- it->state == PlayingKeyUp ) )
- {
- continue;
- }
- for( QList<GigSample>::iterator sample = it->samples.begin();
- sample != it->samples.end(); ++sample )
- {
- if( sample->sample == NULL || sample->region == NULL )
- {
- continue;
- }
- // Will change if resampling
- bool resample = false;
- f_cnt_t samples = frames; // How many to grab
- f_cnt_t used = frames; // How many we used
- float freq_factor = 1.0; // How to resample
- // Resample to be the correct pitch when the sample provided isn't
- // solely for this one note (e.g. one or two samples per octave) or
- // we are processing at a different sample rate
- if( sample->region->PitchTrack == true || rate != sample->sample->SamplesPerSecond )
- {
- resample = true;
- // Factor just for resampling
- freq_factor = 1.0 * rate / sample->sample->SamplesPerSecond;
- // Factor for pitch shifting as well as resampling
- if( sample->region->PitchTrack == true )
- {
- freq_factor *= sample->freqFactor;
- }
- // We need a bit of margin so we don't get glitching
- samples = frames / freq_factor + MARGIN[m_interpolation];
- }
- // Load this note's data
- sampleFrame sampleData[samples];
- loadSample( *sample, sampleData, samples );
- // Apply ADSR using a copy so if we don't use these samples when
- // resampling, the ADSR doesn't get messed up
- ADSR copy = sample->adsr;
- for( f_cnt_t i = 0; i < samples; ++i )
- {
- float amplitude = copy.value();
- sampleData[i][0] *= amplitude;
- sampleData[i][1] *= amplitude;
- }
- // Output the data resampling if needed
- if( resample == true )
- {
- sampleFrame convertBuf[frames];
- // Only output if resampling is successful (note that "used" is output)
- if( sample->convertSampleRate( *sampleData, *convertBuf, samples, frames,
- freq_factor, used ) )
- {
- for( f_cnt_t i = 0; i < frames; ++i )
- {
- _working_buffer[i][0] += convertBuf[i][0];
- _working_buffer[i][1] += convertBuf[i][1];
- }
- }
- }
- else
- {
- for( f_cnt_t i = 0; i < frames; ++i )
- {
- _working_buffer[i][0] += sampleData[i][0];
- _working_buffer[i][1] += sampleData[i][1];
- }
- }
- // Update note position with how many samples we actually used
- sample->pos += used;
- sample->adsr.inc( used );
- }
- }
- m_notesMutex.unlock();
- m_synthMutex.unlock();
- // Set gain properly based on volume control
- for( f_cnt_t i = 0; i < frames; ++i )
- {
- _working_buffer[i][0] *= m_gain.value();
- _working_buffer[i][1] *= m_gain.value();
- }
- instrumentTrack()->processAudioBuffer( _working_buffer, frames, NULL );
- }
- void GigInstrument::loadSample( GigSample& sample, sampleFrame* sampleData, f_cnt_t samples )
- {
- if( sampleData == NULL || samples < 1 )
- {
- return;
- }
- // Determine if we need to loop part of this sample
- bool loop = false;
- gig::loop_type_t loopType = gig::loop_type_normal;
- f_cnt_t loopStart = 0;
- f_cnt_t loopLength = 0;
- if( sample.region->pSampleLoops != NULL )
- {
- for( uint32_t i = 0; i < sample.region->SampleLoops; ++i )
- {
- loop = true;
- loopType = static_cast<gig::loop_type_t>( sample.region->pSampleLoops[i].LoopType );
- loopStart = sample.region->pSampleLoops[i].LoopStart;
- loopLength = sample.region->pSampleLoops[i].LoopLength;
- // Currently only support at max one loop
- break;
- }
- }
- unsigned long allocationsize = samples * sample.sample->FrameSize;
- int8_t buffer[allocationsize];
- // Load the sample in different ways depending on if we're looping or not
- if( loop == true && ( sample.pos >= loopStart || sample.pos + samples > loopStart ) )
- {
- // Calculate the new position based on the type of loop
- if( loopType == gig::loop_type_bidirectional )
- {
- sample.pos = getPingPongIndex( sample.pos, loopStart, loopStart + loopLength );
- }
- else
- {
- sample.pos = getLoopedIndex( sample.pos, loopStart, loopStart + loopLength );
- // TODO: also implement loop_type_backward support
- }
- sample.sample->SetPos( sample.pos );
- // Load the samples (based on gig::Sample::ReadAndLoop) even around the end
- // of a loop boundary wrapping to the beginning of the loop region
- long samplestoread = samples;
- long samplestoloopend = 0;
- long readsamples = 0;
- long totalreadsamples = 0;
- long loopEnd = loopStart + loopLength;
- do
- {
- samplestoloopend = loopEnd - sample.sample->GetPos();
- readsamples = sample.sample->Read( &buffer[totalreadsamples * sample.sample->FrameSize],
- min( samplestoread, samplestoloopend ) );
- samplestoread -= readsamples;
- totalreadsamples += readsamples;
- if( readsamples >= samplestoloopend )
- {
- sample.sample->SetPos( loopStart );
- }
- }
- while( samplestoread > 0 && readsamples > 0 );
- }
- else
- {
- sample.sample->SetPos( sample.pos );
- unsigned long size = sample.sample->Read( &buffer, samples ) * sample.sample->FrameSize;
- std::memset( (int8_t*) &buffer + size, 0, allocationsize - size );
- }
- // Convert from 16 or 24 bit into 32-bit float
- if( sample.sample->BitDepth == 24 ) // 24 bit
- {
- uint8_t * pInt = reinterpret_cast<uint8_t*>( &buffer );
- for( f_cnt_t i = 0; i < samples; ++i )
- {
- // libgig gives 24-bit data as little endian, so we must
- // convert if on a big endian system
- int32_t valueLeft = swap32IfBE(
- ( pInt[ 3 * sample.sample->Channels * i ] << 8 ) |
- ( pInt[ 3 * sample.sample->Channels * i + 1 ] << 16 ) |
- ( pInt[ 3 * sample.sample->Channels * i + 2 ] << 24 ) );
- // Store the notes to this buffer before saving to output
- // so we can fade them out as needed
- sampleData[i][0] = 1.0 / 0x100000000 * sample.attenuation * valueLeft;
- if( sample.sample->Channels == 1 )
- {
- sampleData[i][1] = sampleData[i][0];
- }
- else
- {
- int32_t valueRight = swap32IfBE(
- ( pInt[ 3 * sample.sample->Channels * i + 3 ] << 8 ) |
- ( pInt[ 3 * sample.sample->Channels * i + 4 ] << 16 ) |
- ( pInt[ 3 * sample.sample->Channels * i + 5 ] << 24 ) );
- sampleData[i][1] = 1.0 / 0x100000000 * sample.attenuation * valueRight;
- }
- }
- }
- else // 16 bit
- {
- int16_t * pInt = reinterpret_cast<int16_t*>( &buffer );
- for( f_cnt_t i = 0; i < samples; ++i )
- {
- sampleData[i][0] = 1.0 / 0x10000 *
- pInt[ sample.sample->Channels * i ] * sample.attenuation;
- if( sample.sample->Channels == 1 )
- {
- sampleData[i][1] = sampleData[i][0];
- }
- else
- {
- sampleData[i][1] = 1.0 / 0x10000 *
- pInt[ sample.sample->Channels * i + 1 ] * sample.attenuation;
- }
- }
- }
- }
- // These two loop index functions taken from SampleBuffer.cpp
- f_cnt_t GigInstrument::getLoopedIndex( f_cnt_t index, f_cnt_t startf, f_cnt_t endf ) const
- {
- if( index < endf )
- {
- return index;
- }
- return startf + ( index - startf )
- % ( endf - startf );
- }
- f_cnt_t GigInstrument::getPingPongIndex( f_cnt_t index, f_cnt_t startf, f_cnt_t endf ) const
- {
- if( index < endf )
- {
- return index;
- }
- const f_cnt_t looplen = endf - startf;
- const f_cnt_t looppos = ( index - endf ) % ( looplen * 2 );
- return ( looppos < looplen )
- ? endf - looppos
- : startf + ( looppos - looplen );
- }
- // A key has been released
- void GigInstrument::deleteNotePluginData( NotePlayHandle * _n )
- {
- GIGPluginData * pluginData = static_cast<GIGPluginData *>( _n->m_pluginData );
- QMutexLocker locker( &m_notesMutex );
- // Mark the note as being released, but only if it was playing or was just
- // pressed (i.e., not if the key was already released)
- for( QList<GigNote>::iterator i = m_notes.begin(); i != m_notes.end(); ++i )
- {
- // Find the note by matching pointers to the plugin data
- if( i->handle == pluginData &&
- ( i->state == KeyDown || i->state == PlayingKeyDown ) )
- {
- i->state = KeyUp;
- }
- }
- // TODO: not sample exact? What about in the middle of us writing out the sample?
- delete pluginData;
- }
- PluginView * GigInstrument::instantiateView( QWidget * _parent )
- {
- return new GigInstrumentView( this, _parent );
- }
- // Add the desired samples (either the normal samples or the release samples)
- // to the GigNote
- //
- // Note: not thread safe since libgig stores current region position data in
- // the instrument object
- void GigInstrument::addSamples( GigNote & gignote, bool wantReleaseSample )
- {
- // Change key dimension, e.g. change samples based on what key is pressed
- // in a certain range. From LinuxSampler
- if( wantReleaseSample == true &&
- gignote.midiNote >= m_instrument->DimensionKeyRange.low &&
- gignote.midiNote <= m_instrument->DimensionKeyRange.high )
- {
- m_currentKeyDimension = float( gignote.midiNote -
- m_instrument->DimensionKeyRange.low ) / (
- m_instrument->DimensionKeyRange.high -
- m_instrument->DimensionKeyRange.low + 1 );
- }
- gig::Region* pRegion = m_instrument->GetFirstRegion();
- while( pRegion != NULL )
- {
- Dimension dim = getDimensions( pRegion, gignote.velocity, wantReleaseSample );
- gig::DimensionRegion * pDimRegion = pRegion->GetDimensionRegionByValue( dim.DimValues );
- gig::Sample * pSample = pDimRegion->pSample;
- // If this is a release sample, the note won't ever be
- // released, so we handle it differently
- gignote.isRelease = wantReleaseSample;
- // Does this note have release samples? Set this only on the original
- // notes and not when we get the release samples.
- if( wantReleaseSample != true )
- {
- gignote.release = dim.release;
- }
- if( pSample != NULL && pSample->SamplesTotal != 0 )
- {
- int keyLow = pRegion->KeyRange.low;
- int keyHigh = pRegion->KeyRange.high;
- if( gignote.midiNote >= keyLow && gignote.midiNote <= keyHigh )
- {
- float attenuation = pDimRegion->GetVelocityAttenuation( gignote.velocity );
- float length = (float) pSample->SamplesTotal / Engine::mixer()->processingSampleRate();
- // TODO: sample panning? crossfade different layers?
- if( wantReleaseSample == true )
- {
- // From LinuxSampler, not sure how it was created
- attenuation *= 1 - 0.01053 * ( 256 >> pDimRegion->ReleaseTriggerDecay ) * length;
- }
- else
- {
- attenuation *= pDimRegion->SampleAttenuation;
- }
- gignote.samples.push_back( GigSample( pSample, pDimRegion,
- attenuation, m_interpolation, gignote.frequency ) );
- }
- }
- pRegion = m_instrument->GetNextRegion();
- }
- }
- // Based on our input parameters, generate a "dimension" that specifies which
- // note we wish to select from the GIG file with libgig. libgig will use this
- // information to select the sample.
- Dimension GigInstrument::getDimensions( gig::Region * pRegion, int velocity, bool release )
- {
- Dimension dim;
- if( pRegion == NULL )
- {
- return dim;
- }
- for( int i = pRegion->Dimensions - 1; i >= 0; --i )
- {
- switch( pRegion->pDimensionDefinitions[i].dimension )
- {
- case gig::dimension_layer:
- // TODO: implement this
- dim.DimValues[i] = 0;
- break;
- case gig::dimension_velocity:
- dim.DimValues[i] = velocity;
- break;
- case gig::dimension_releasetrigger:
- dim.release = true;
- dim.DimValues[i] = (uint) release;
- break;
- case gig::dimension_keyboard:
- dim.DimValues[i] = (uint) ( m_currentKeyDimension * pRegion->pDimensionDefinitions[i].zones );
- break;
- case gig::dimension_roundrobin:
- case gig::dimension_roundrobinkeyboard:
- // TODO: implement this
- dim.DimValues[i] = 0;
- break;
- case gig::dimension_random:
- // From LinuxSampler, untested
- m_RandomSeed = m_RandomSeed * 1103515245 + 12345;
- dim.DimValues[i] = uint(
- m_RandomSeed / 4294967296.0f * pRegion->pDimensionDefinitions[i].bits );
- break;
- case gig::dimension_samplechannel:
- case gig::dimension_channelaftertouch:
- case gig::dimension_modwheel:
- case gig::dimension_breath:
- case gig::dimension_foot:
- case gig::dimension_portamentotime:
- case gig::dimension_effect1:
- case gig::dimension_effect2:
- case gig::dimension_genpurpose1:
- case gig::dimension_genpurpose2:
- case gig::dimension_genpurpose3:
- case gig::dimension_genpurpose4:
- case gig::dimension_sustainpedal:
- case gig::dimension_portamento:
- case gig::dimension_sostenutopedal:
- case gig::dimension_softpedal:
- case gig::dimension_genpurpose5:
- case gig::dimension_genpurpose6:
- case gig::dimension_genpurpose7:
- case gig::dimension_genpurpose8:
- case gig::dimension_effect1depth:
- case gig::dimension_effect2depth:
- case gig::dimension_effect3depth:
- case gig::dimension_effect4depth:
- case gig::dimension_effect5depth:
- case gig::dimension_none:
- default:
- dim.DimValues[i] = 0;
- break;
- }
- }
- return dim;
- }
- // Get the selected instrument from the GIG file we opened if we haven't gotten
- // it already. This is based on the bank and patch numbers.
- void GigInstrument::getInstrument()
- {
- // Find instrument
- int iBankSelected = m_bankNum.value();
- int iProgSelected = m_patchNum.value();
- QMutexLocker locker( &m_synthMutex );
- if( m_instance != NULL )
- {
- gig::Instrument * pInstrument = m_instance->gig.GetFirstInstrument();
- while( pInstrument != NULL )
- {
- int iBank = pInstrument->MIDIBank;
- int iProg = pInstrument->MIDIProgram;
- if( iBank == iBankSelected && iProg == iProgSelected )
- {
- break;
- }
- pInstrument = m_instance->gig.GetNextInstrument();
- }
- m_instrument = pInstrument;
- }
- }
- // Since the sample rate changes when we start an export, clear all the
- // currently-playing notes when we get this signal. Then, the export won't
- // include leftover notes that were playing in the program.
- void GigInstrument::updateSampleRate()
- {
- QMutexLocker locker( &m_notesMutex );
- m_notes.clear();
- }
- class gigKnob : public Knob
- {
- public:
- gigKnob( QWidget * _parent ) :
- Knob( knobBright_26, _parent )
- {
- setFixedSize( 31, 38 );
- }
- } ;
- GigInstrumentView::GigInstrumentView( Instrument * _instrument, QWidget * _parent ) :
- InstrumentViewFixedSize( _instrument, _parent )
- {
- GigInstrument * k = castModel<GigInstrument>();
- connect( &k->m_bankNum, SIGNAL( dataChanged() ), this, SLOT( updatePatchName() ) );
- connect( &k->m_patchNum, SIGNAL( dataChanged() ), this, SLOT( updatePatchName() ) );
- // File Button
- m_fileDialogButton = new PixmapButton( this );
- m_fileDialogButton->setCursor( QCursor( Qt::PointingHandCursor ) );
- m_fileDialogButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "fileselect_on" ) );
- m_fileDialogButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "fileselect_off" ) );
- m_fileDialogButton->move( 223, 68 );
- connect( m_fileDialogButton, SIGNAL( clicked() ), this, SLOT( showFileDialog() ) );
- ToolTip::add( m_fileDialogButton, tr( "Open GIG file" ) );
- // Patch Button
- m_patchDialogButton = new PixmapButton( this );
- m_patchDialogButton->setCursor( QCursor( Qt::PointingHandCursor ) );
- m_patchDialogButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "patches_on" ) );
- m_patchDialogButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "patches_off" ) );
- m_patchDialogButton->setEnabled( false );
- m_patchDialogButton->move( 223, 94 );
- connect( m_patchDialogButton, SIGNAL( clicked() ), this, SLOT( showPatchDialog() ) );
- ToolTip::add( m_patchDialogButton, tr( "Choose patch" ) );
- // LCDs
- m_bankNumLcd = new LcdSpinBox( 3, "21pink", this );
- m_bankNumLcd->move( 111, 150 );
- m_patchNumLcd = new LcdSpinBox( 3, "21pink", this );
- m_patchNumLcd->move( 161, 150 );
- // Next row
- m_filenameLabel = new QLabel( this );
- m_filenameLabel->setGeometry( 61, 70, 156, 14 );
- m_patchLabel = new QLabel( this );
- m_patchLabel->setGeometry( 61, 94, 156, 14 );
- // Gain
- m_gainKnob = new gigKnob( this );
- m_gainKnob->setHintText( tr( "Gain:" ) + " ", "" );
- m_gainKnob->move( 32, 140 );
- setAutoFillBackground( true );
- QPalette pal;
- pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( "artwork" ) );
- setPalette( pal );
- updateFilename();
- }
- GigInstrumentView::~GigInstrumentView()
- {
- }
- void GigInstrumentView::modelChanged()
- {
- GigInstrument * k = castModel<GigInstrument>();
- m_bankNumLcd->setModel( &k->m_bankNum );
- m_patchNumLcd->setModel( &k->m_patchNum );
- m_gainKnob->setModel( &k->m_gain );
- connect( k, SIGNAL( fileChanged() ), this, SLOT( updateFilename() ) );
- connect( k, SIGNAL( fileLoading() ), this, SLOT( invalidateFile() ) );
- updateFilename();
- }
- void GigInstrumentView::updateFilename()
- {
- GigInstrument * i = castModel<GigInstrument>();
- QFontMetrics fm( m_filenameLabel->font() );
- QString file = i->m_filename.endsWith( ".gig", Qt::CaseInsensitive ) ?
- i->m_filename.left( i->m_filename.length() - 4 ) :
- i->m_filename;
- m_filenameLabel->setText( fm.elidedText( file, Qt::ElideLeft, m_filenameLabel->width() ) );
- m_patchDialogButton->setEnabled( !i->m_filename.isEmpty() );
- updatePatchName();
- update();
- }
- void GigInstrumentView::updatePatchName()
- {
- GigInstrument * i = castModel<GigInstrument>();
- QFontMetrics fm( font() );
- QString patch = i->getCurrentPatchName();
- m_patchLabel->setText( fm.elidedText( patch, Qt::ElideLeft, m_patchLabel->width() ) );
- update();
- }
- void GigInstrumentView::invalidateFile()
- {
- m_patchDialogButton->setEnabled( false );
- }
- void GigInstrumentView::showFileDialog()
- {
- GigInstrument * k = castModel<GigInstrument>();
- FileDialog ofd( NULL, tr( "Open GIG file" ) );
- ofd.setFileMode( FileDialog::ExistingFiles );
- QStringList types;
- types << tr( "GIG Files (*.gig)" );
- ofd.setNameFilters( types );
- if( k->m_filename != "" )
- {
- QString f = SampleBuffer::tryToMakeAbsolute( k->m_filename );
- ofd.setDirectory( QFileInfo( f ).absolutePath() );
- ofd.selectFile( QFileInfo( f ).fileName() );
- }
- else
- {
- ofd.setDirectory( ConfigManager::inst()->gigDir() );
- }
- m_fileDialogButton->setEnabled( false );
- if( ofd.exec() == QDialog::Accepted && !ofd.selectedFiles().isEmpty() )
- {
- QString f = ofd.selectedFiles()[0];
- if( f != "" )
- {
- k->openFile( f );
- Engine::getSong()->setModified();
- }
- }
- m_fileDialogButton->setEnabled( true );
- }
- void GigInstrumentView::showPatchDialog()
- {
- GigInstrument * k = castModel<GigInstrument>();
- PatchesDialog pd( this );
- pd.setup( k->m_instance, 1, k->instrumentTrack()->name(), &k->m_bankNum, &k->m_patchNum, m_patchLabel );
- pd.exec();
- }
- // Store information related to playing a sample from the GIG file
- GigSample::GigSample( gig::Sample * pSample, gig::DimensionRegion * pDimRegion,
- float attenuation, int interpolation, float desiredFreq )
- : sample( pSample ), region( pDimRegion ), attenuation( attenuation ),
- pos( 0 ), interpolation( interpolation ), srcState( NULL ),
- sampleFreq( 0 ), freqFactor( 1 )
- {
- if( sample != NULL && region != NULL )
- {
- // Note: we don't create the libsamplerate object here since we always
- // also call the copy constructor when appending to the end of the
- // QList. We'll create it only in the copy constructor so we only have
- // to create it once.
- // Calculate note pitch and frequency factor only if we're actually
- // going to be changing the pitch of the notes
- if( region->PitchTrack == true )
- {
- // Calculate what frequency the provided sample is
- sampleFreq = 440.0 * powf( 2, 1.0 / 12 * (
- 1.0 * region->UnityNote - 69 -
- 0.01 * region->FineTune ) );
- freqFactor = sampleFreq / desiredFreq;
- }
- // The sample rate we pass in is affected by how we are going to be
- // resampling the note so that a 1.5 second release ends up being 1.5
- // seconds after resampling
- adsr = ADSR( region, sample->SamplesPerSecond / freqFactor );
- }
- }
- GigSample::~GigSample()
- {
- if( srcState != NULL )
- {
- src_delete( srcState );
- }
- }
- GigSample::GigSample( const GigSample& g )
- : sample( g.sample ), region( g.region ), attenuation( g.attenuation ),
- adsr( g.adsr ), pos( g.pos ), interpolation( g.interpolation ),
- srcState( NULL ), sampleFreq( g.sampleFreq ), freqFactor( g.freqFactor )
- {
- // On the copy, we want to create the object
- updateSampleRate();
- }
- GigSample& GigSample::operator=( const GigSample& g )
- {
- sample = g.sample;
- region= g.region;
- attenuation = g.attenuation;
- adsr = g.adsr;
- pos = g.pos;
- interpolation = g.interpolation;
- srcState = NULL;
- sampleFreq = g.sampleFreq;
- freqFactor = g.freqFactor;
- if( g.srcState != NULL )
- {
- updateSampleRate();
- }
- return *this;
- }
- void GigSample::updateSampleRate()
- {
- if( srcState != NULL )
- {
- src_delete( srcState );
- }
- int error = 0;
- srcState = src_new( interpolation, DEFAULT_CHANNELS, &error );
- if( srcState == NULL || error != 0 )
- {
- qCritical( "error while creating libsamplerate data structure in GigSample" );
- }
- }
- bool GigSample::convertSampleRate( sampleFrame & oldBuf, sampleFrame & newBuf,
- f_cnt_t oldSize, f_cnt_t newSize, float freq_factor, f_cnt_t& used )
- {
- if( srcState == NULL )
- {
- return false;
- }
- SRC_DATA src_data;
- src_data.data_in = &oldBuf[0];
- src_data.data_out = &newBuf[0];
- src_data.input_frames = oldSize;
- src_data.output_frames = newSize;
- src_data.src_ratio = freq_factor;
- src_data.end_of_input = 0;
- // We don't need to lock this assuming that we're only outputting the
- // samples in one thread
- int error = src_process( srcState, &src_data );
- used = src_data.input_frames_used;
- if( error != 0 )
- {
- qCritical( "GigInstrument: error while resampling: %s", src_strerror( error ) );
- return false;
- }
- if( oldSize != 0 && src_data.output_frames_gen == 0 )
- {
- qCritical( "GigInstrument: could not resample, no frames generated" );
- return false;
- }
- if( src_data.output_frames_gen > 0 && src_data.output_frames_gen < newSize )
- {
- qCritical() << "GigInstrument: not enough frames, wanted"
- << newSize << "generated" << src_data.output_frames_gen;
- return false;
- }
- return true;
- }
- ADSR::ADSR()
- : preattack( 0 ), attack( 0 ), decay1( 0 ), decay2( 0 ), infiniteSustain( false ),
- sustain( 0 ), release( 0 ),
- amplitude( 0 ), isAttack( true ), isRelease( false ), isDone( false ),
- attackPosition( 0 ), attackLength( 0 ), decayLength( 0 ),
- releasePosition( 0 ), releaseLength( 0 )
- {
- }
- // Create the ADSR envelope from the settings in the GIG file
- ADSR::ADSR( gig::DimensionRegion * region, int sampleRate )
- : preattack( 0 ), attack( 0 ), decay1( 0 ), decay2( 0 ), infiniteSustain( false ),
- sustain( 0 ), release( 0 ),
- amplitude( 0 ), isAttack( true ), isRelease( false ), isDone( false ),
- attackPosition( 0 ), attackLength( 0 ), decayLength( 0 ),
- releasePosition( 0 ), releaseLength( 0 )
- {
- if( region != NULL )
- {
- // Parameters from GIG file
- preattack = 1.0 * region->EG1PreAttack / 1000; // EG1PreAttack is 0-1000 permille
- attack = region->EG1Attack;
- decay1 = region->EG1Decay1;
- decay2 = region->EG1Decay2;
- infiniteSustain = region->EG1InfiniteSustain;
- sustain = 1.0 * region->EG1Sustain / 1000; // EG1Sustain is 0-1000 permille
- release = region->EG1Release;
- // Simple ADSR using positions in sample
- amplitude = preattack;
- attackLength = attack * sampleRate;
- decayLength = decay1 * sampleRate; // TODO: ignoring decay2 for now
- releaseLength = release * sampleRate;
- // If there is no attack or decay, start at the sustain amplitude
- if( attackLength == 0 && decayLength == 0 )
- {
- amplitude = sustain;
- }
- // If there is no attack, start at the full amplitude
- else if( attackLength == 0 )
- {
- amplitude = 1.0;
- }
- }
- }
- // Next time we get the amplitude, we'll be releasing the note
- void ADSR::keyup()
- {
- isRelease = true;
- }
- // Can we delete the sample now?
- bool ADSR::done()
- {
- return isDone;
- }
- // Return the current amplitude and increment internal positions
- float ADSR::value()
- {
- float currentAmplitude = amplitude;
- // If we're done, don't output any signal
- if( isDone == true )
- {
- return 0;
- }
- // If we're still in the attack phase, release from the current volume
- // instead of jumping to the sustain volume and fading out
- else if( isAttack == true && isRelease == true )
- {
- sustain = amplitude;
- isAttack = false;
- }
- // If we're in the attack phase, start at the preattack amplitude and
- // increase to the full before decreasing to sustain
- if( isAttack == true )
- {
- if( attackPosition < attackLength )
- {
- amplitude = preattack + ( 1.0 - preattack ) / attackLength * attackPosition;
- }
- else if( attackPosition < attackLength + decayLength )
- {
- amplitude = 1.0 - ( 1.0 - sustain ) / decayLength * ( attackPosition - attackLength );
- }
- else
- {
- isAttack = false;
- }
- ++attackPosition;
- }
- // If we're in the sustain phase, decrease from sustain to zero
- else if( isRelease == true )
- {
- // Maybe not the best way of doing this, but it appears to be about right
- // Satisfies f(0) = sustain and f(releaseLength) = very small
- amplitude = ( sustain + 1e-3 ) * expf( -5.0 / releaseLength * releasePosition ) - 1e-3;
- // Don't have an infinite exponential decay
- if( amplitude <= 0 || releasePosition >= releaseLength )
- {
- amplitude = 0;
- isDone = true;
- }
- ++releasePosition;
- }
- return currentAmplitude;
- }
- // Increment internal positions a certain number of times
- void ADSR::inc( f_cnt_t num )
- {
- for( f_cnt_t i = 0; i < num; ++i )
- {
- value();
- }
- }
- extern "C"
- {
- // necessary for getting instance out of shared lib
- PLUGIN_EXPORT Plugin * lmms_plugin_main( Model *m, void * )
- {
- return new GigInstrument( static_cast<InstrumentTrack *>( m ) );
- }
- }
|