12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400 |
- /*
- * 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 "GigPlayer.h"
- #include <cstring>
- #include <QDebug>
- #include <QLayout>
- #include <QLabel>
- #include <QDomDocument>
- #include "ConfigManager.h"
- #include "endian_handling.h"
- #include "Engine.h"
- #include "FileDialog.h"
- #include "InstrumentTrack.h"
- #include "InstrumentPlayHandle.h"
- #include "Knob.h"
- #include "Mixer.h"
- #include "NotePlayHandle.h"
- #include "PathUtil.h"
- #include "SampleBuffer.h"
- #include "Song.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( PathUtil::toAbsolute( _gigFile ) );
- m_filename = PathUtil::toShortestRelative( _gigFile );
- }
- catch( ... )
- {
- m_instance = NULL;
- m_filename = "";
- }
- }
- emit fileChanged();
- if( updateTrackName == true )
- {
- instrumentTrack()->setName(PathUtil::cleanName( _gigFile ) );
- 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 = PathUtil::toAbsolute( 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 ) );
- }
- }
|