123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410 |
- /*
- * 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.cpp"
- 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 ) :
- InstrumentView( _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 other GIG file" ) );
- m_fileDialogButton->setWhatsThis( tr( "Click here to open another 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 the patch" ) );
- m_patchDialogButton->setWhatsThis( tr( "Click here to change which patch of the GIG file to use" ) );
- // 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 );
- m_bankNumLcd->setWhatsThis( tr( "Change which instrument of the GIG file is being played" ) );
- m_patchNumLcd->setWhatsThis( tr( "Change which instrument of the GIG file is being played" ) );
- // 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 );
- m_filenameLabel->setWhatsThis( tr( "Which GIG file is currently being used" ) );
- m_patchLabel->setWhatsThis( tr( "Which patch of the GIG file is currently being used" ) );
- // Gain
- m_gainKnob = new gigKnob( this );
- m_gainKnob->setHintText( tr( "Gain" ) + " ", "" );
- m_gainKnob->move( 32, 140 );
- m_gainKnob->setWhatsThis( tr( "Factor to multiply samples by" ) );
- 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 * PLUGIN_EXPORT lmms_plugin_main( Model *, void * _data )
- {
- return new GigInstrument( static_cast<InstrumentTrack *>( _data ) );
- }
- }
|