Restructured into GigNotes which contain GigSamples

Now notes are added/removed by locking only a note mutex when pressing
or releasing a note. Then, while processing we actually find and play
the samples using libgig.
This commit is contained in:
Garrett
2014-10-23 22:34:55 -07:00
parent 74ded6b7ef
commit 5a3b8d3da1
4 changed files with 308 additions and 265 deletions

View File

@@ -4,7 +4,7 @@
* Copyright (c) 2008 Paul Giblock <drfaygo/at/gmail/dot/com>
* Copyright (c) 2009-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* Based partially on some code from LinuxSampler (also GPL):
* 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
@@ -72,29 +72,23 @@ Plugin::Descriptor PLUGIN_EXPORT gigplayer_plugin_descriptor =
struct GIGPluginData
{
int midiNote;
int lastPanning;
float lastVelocity;
} ;
gigInstrument::gigInstrument( InstrumentTrack * _instrument_track ) :
Instrument( _instrument_track, &gigplayer_plugin_descriptor ),
m_srcState( NULL ),
m_instance( NULL ),
m_instrument( NULL ),
m_RandomSeed( 0 ),
m_currentKeyDimension( 0 ),
m_filename( "" ),
m_lastMidiPitch( -1 ),
m_lastMidiPitchRange( -1 ),
m_channel( 1 ),
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_noteData( NULL ),
m_noteDataSize( 0 )
m_sampleData( NULL ),
m_sampleDataSize( 0 ),
m_RandomSeed( 0 ),
m_currentKeyDimension( 0 )
{
InstrumentPlayHandle * iph = new InstrumentPlayHandle( this );
engine::mixer()->addPlayHandle( iph );
@@ -107,16 +101,16 @@ gigInstrument::gigInstrument( InstrumentTrack * _instrument_track ) :
// Buffer for reading samples
const fpp_t frames = engine::mixer()->framesPerPeriod();
m_noteData = new sampleFrame[frames];
m_noteDataSize = frames;
m_sampleData = new sampleFrame[frames];
m_sampleDataSize = frames;
}
gigInstrument::~gigInstrument()
{
if( m_noteData != NULL )
delete[] m_noteData;
if( m_sampleData != NULL )
delete[] m_sampleData;
engine::mixer()->removePlayHandles( instrumentTrack() );
freeInstance();
@@ -138,7 +132,6 @@ void gigInstrument::saveSettings( QDomDocument & _doc, QDomElement & _this )
void gigInstrument::loadSettings( const QDomElement & _this )
{
openFile( _this.attribute( "src" ), false );
@@ -152,7 +145,6 @@ void gigInstrument::loadSettings( const QDomElement & _this )
void gigInstrument::loadFile( const QString & _file )
{
if( !_file.isEmpty() && QFileInfo( _file ).exists() )
@@ -165,18 +157,15 @@ void gigInstrument::loadFile( const QString & _file )
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;
}
@@ -189,10 +178,10 @@ QString gigInstrument::nodeName() const
void gigInstrument::freeInstance()
{
QMutexLocker locker(&m_synthMutex);
QMutexLocker synthLock(&m_synthMutex);
QMutexLocker notesLock(&m_notesMutex);
if( m_instance != NULL )
{
@@ -213,11 +202,7 @@ void gigInstrument::openFile( const QString & _gigFile, bool updateTrackName )
{
emit fileLoading();
// Used for loading file
char * gigAscii = qstrdup( qPrintable( SampleBuffer::tryToMakeAbsolute( _gigFile ) ) );
QString relativePath = SampleBuffer::tryToMakeRelative( _gigFile );
// free reference to gig file if one is selected
// Remove the current instrument if one is selected
freeInstance();
{
@@ -225,8 +210,8 @@ void gigInstrument::openFile( const QString & _gigFile, bool updateTrackName )
try
{
m_instance = new gigInstance( _gigFile );
m_filename = relativePath;
m_instance = new GigInstance( _gigFile );
m_filename = SampleBuffer::tryToMakeRelative( _gigFile );
}
catch( ... )
{
@@ -237,8 +222,6 @@ void gigInstrument::openFile( const QString & _gigFile, bool updateTrackName )
emit fileChanged();
delete[] gigAscii;
if( updateTrackName )
{
instrumentTrack()->setName( QFileInfo( _gigFile ).baseName() );
@@ -248,7 +231,6 @@ void gigInstrument::openFile( const QString & _gigFile, bool updateTrackName )
void gigInstrument::updatePatch()
{
if( m_bankNum.value() >= 0 && m_patchNum.value() >= 0 )
@@ -257,7 +239,6 @@ void gigInstrument::updatePatch()
QString gigInstrument::getCurrentPatchName()
{
QMutexLocker locker(&m_synthMutex);
@@ -295,6 +276,7 @@ QString gigInstrument::getCurrentPatchName()
// A key has been pressed
void gigInstrument::playNote( NotePlayHandle * _n, sampleFrame * )
{
const float LOG440 = 2.643452676f;
@@ -313,44 +295,38 @@ void gigInstrument::playNote( NotePlayHandle * _n, sampleFrame * )
{
GIGPluginData * pluginData = new GIGPluginData;
pluginData->midiNote = midiNote;
pluginData->lastPanning = -1;
pluginData->lastVelocity = 127;
_n->m_pluginData = pluginData;
bool instance = false;
bool instrument = false;
const int baseVelocity = instrumentTrack()->midiPort()->baseVelocity();
const uint velocity = _n->midiVelocity( baseVelocity );
{
QMutexLocker locker(&m_synthMutex);
instance = m_instance;
instrument = m_instrument;
}
if( instance )
{
if( !instrument )
getInstrument();
const int baseVelocity = instrumentTrack()->midiPort()->baseVelocity();
const uint velocity = _n->midiVelocity( baseVelocity );
addNotes( midiNote, velocity, false );
}
QMutexLocker locker(&m_notesMutex);
m_notes.push_back(GigNote(midiNote, velocity));
}
}
// Could we get iph-based instruments support sample-exact models by using a
// frame-length of 1 while rendering?
// 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();
// Initialize to zeros
std::memset(&_working_buffer[0][0], 0, 2*frames*sizeof(float)); // *2 for channels
std::memset(&_working_buffer[0][0], 0, DEFAULT_CHANNELS*frames*sizeof(float));
// Determine if we need to convert sample rates
m_synthMutex.lock();
m_notesMutex.lock();
if( !m_instance || !m_instrument )
{
m_synthMutex.unlock();
m_notesMutex.unlock();
return;
}
// Data for converting sample rate
int oldRate = -1;
int newRate = engine::mixer()->processingSampleRate();
bool sampleError = false;
@@ -360,102 +336,152 @@ void gigInstrument::play( sampleFrame * _working_buffer )
// How many frames we'll be grabbing from the sample
int samples = frames;
m_synthMutex.lock();
for( QList<gigNote>::iterator note = m_notes.begin(); note != m_notes.end(); ++note )
for( QList<GigNote>::iterator note = m_notes.begin(); note != m_notes.end(); ++note )
{
if( note->sample )
// Process notes in the KeyUp state, adding release samples if desired
if( note->state == KeyUp )
{
// Verify all the samples have the same rate
int currentRate = note->sample->SamplesPerSecond;
if( oldRate == -1 )
// If there are no samples, we're done
if( note->samples.empty() )
{
oldRate = currentRate;
note->state = Completed;
}
else if( oldRate != currentRate )
else
{
qCritical() << "gigInstrument: not all samples are the same rate, not converting";
sampleError = true;
note->state = PlayingKeyUp;
// Notify each sample that the key has been released
for( QList<GigSample>::iterator sample = note->samples.begin();
sample != note->samples.end(); ++sample )
sample->adsr.keyup();
// Add release samples if available
if( note->release )
addSamples(*note, true);
}
}
// Process notes in the KeyDown state, adding samples for the notes
else if( note->state == KeyDown )
{
note->state = PlayingKeyDown;
addSamples(*note, false);
}
// Delete ended notes
if( note->adsr.done() || note->pos >= note->sample->SamplesTotal-1 )
for( QList<GigSample>::iterator sample = note->samples.begin();
sample != note->samples.end(); ++sample )
{
// Delete ended samples
if( !sample->sample || sample->adsr.done() || sample->pos >= sample->sample->SamplesTotal-1 )
{
note = m_notes.erase(note);
sample = note->samples.erase(sample);
if( note == m_notes.end() )
if( sample == note->samples.end() )
break;
}
// Verify all the samples have the same rate
else
{
int currentRate = sample->sample->SamplesPerSecond;
if( oldRate == -1 )
{
oldRate = currentRate;
}
else if( oldRate != currentRate )
{
qCritical() << "gigInstrument: not all samples are the same rate, not converting";
sampleError = true;
}
}
}
// Delete ended notes (either in the completed state or all the samples ended)
if( note->state == Completed || note->samples.empty() )
{
note = m_notes.erase(note);
if( note == m_notes.end() )
break;
}
}
// If all samples have the same sample rate and it's not the output sample
// rate, then we'll convert sample rates
if( oldRate != -1 && !sampleError && oldRate != newRate )
{
sampleConvert = true;
// At a higher sample rate, we'll go output the samples slightly
// faster while maintaining the same note
// Read a different number of samples depending on the sample rate, but
// resample it to always output the right number of frames
samples = frames*oldRate/newRate;
// Buffer for the resampled data
convertBuf = new sampleFrame[samples];
std::memset(&convertBuf[0][0], 0, 2*samples*sizeof(float)); // *2 for channels
std::memset(&convertBuf[0][0], 0, DEFAULT_CHANNELS*samples*sizeof(float));
}
// Recreate buffer if it is of a different size
if( m_noteDataSize != samples )
if( m_sampleDataSize != samples )
{
delete[] m_noteData;
m_noteData = new sampleFrame[samples];
m_noteDataSize = samples;
delete[] m_sampleData;
m_sampleData = new sampleFrame[samples];
m_sampleDataSize = samples;
}
// Fill with portions of the note samples
for( QList<gigNote>::iterator note = m_notes.begin(); note != m_notes.end(); ++note )
// Fill buffer with portions of the note samples
for( QList<GigNote>::iterator note = m_notes.begin(); note != m_notes.end(); ++note )
{
if( note->sample )
// Only process the notes if we're in a playing state
if( !(note->state == PlayingKeyDown ||
note->state == PlayingKeyUp) )
continue;
for( QList<GigSample>::iterator sample = note->samples.begin();
sample != note->samples.end(); ++sample )
{
if( !sample->sample )
continue;
// Set the position to where we currently are in the sample
note->sample->SetPos(note->pos); // Note: not thread safe
sample->sample->SetPos(sample->pos); // Note: not thread safe
// Load the next portion of the sample
gig::buffer_t buf;
unsigned long allocationsize = samples * note->sample->FrameSize;
unsigned long allocationsize = samples * sample->sample->FrameSize;
buf.pStart = new int8_t[allocationsize];
buf.Size = note->sample->Read(buf.pStart, samples) * note->sample->FrameSize;
buf.Size = sample->sample->Read(buf.pStart, samples) * sample->sample->FrameSize;
buf.NullExtensionSize = allocationsize - buf.Size;
std::memset((int8_t*)buf.pStart + buf.Size, 0, buf.NullExtensionSize);
// Save the new position in the sample
note->pos = note->sample->GetPos();
sample->pos = sample->sample->GetPos();
// Convert from 16 or 24 bit into 32-bit float
if( note->sample->BitDepth == 24 ) // 24 bit
if( sample->sample->BitDepth == 24 ) // 24 bit
{
uint8_t* pInt = static_cast<uint8_t*>( buf.pStart );
for( int i = 0; i < samples; ++i )
{
int32_t valueLeft = (pInt[3*note->sample->Channels*i]<<8) |
(pInt[3*note->sample->Channels*i+1]<<16) |
(pInt[3*note->sample->Channels*i+2]<<24);
int32_t valueLeft = (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
m_noteData[i][0] = 1.0/0x100000000*note->attenuation*valueLeft;
m_sampleData[i][0] = 1.0/0x100000000*sample->attenuation*valueLeft;
if( note->sample->Channels == 1 )
if( sample->sample->Channels == 1 )
{
m_noteData[i][1] = m_noteData[i][0];
m_sampleData[i][1] = m_sampleData[i][0];
}
else
{
int32_t valueRight = (pInt[3*note->sample->Channels*i+3]<<8) |
(pInt[3*note->sample->Channels*i+4]<<16) |
(pInt[3*note->sample->Channels*i+5]<<24);
int32_t valueRight = (pInt[3*sample->sample->Channels*i+3]<<8) |
(pInt[3*sample->sample->Channels*i+4]<<16) |
(pInt[3*sample->sample->Channels*i+5]<<24);
m_noteData[i][1] = 1.0/0x100000000*note->attenuation*valueRight;
m_sampleData[i][1] = 1.0/0x100000000*sample->attenuation*valueRight;
}
}
}
@@ -465,12 +491,12 @@ void gigInstrument::play( sampleFrame * _working_buffer )
for( int i = 0; i < samples; ++i )
{
m_noteData[i][0] = 1.0/0x10000*pInt[note->sample->Channels*i] * note->attenuation;
m_sampleData[i][0] = 1.0/0x10000*pInt[sample->sample->Channels*i] * sample->attenuation;
if( note->sample->Channels == 1 )
m_noteData[i][1] = m_noteData[i][0];
if( sample->sample->Channels == 1 )
m_sampleData[i][1] = m_sampleData[i][0];
else
m_noteData[i][1] = 1.0/0x10000*pInt[note->sample->Channels*i+1] * note->attenuation;
m_sampleData[i][1] = 1.0/0x10000*pInt[sample->sample->Channels*i+1] * sample->attenuation;
}
}
@@ -480,9 +506,9 @@ void gigInstrument::play( sampleFrame * _working_buffer )
// Apply ADSR
for( int i = 0; i < samples; ++i )
{
double amplitude = note->adsr.value();
m_noteData[i][0] *= amplitude;
m_noteData[i][1] *= amplitude;
double amplitude = sample->adsr.value();
m_sampleData[i][0] *= amplitude;
m_sampleData[i][1] *= amplitude;
}
// Save to output buffer
@@ -490,23 +516,21 @@ void gigInstrument::play( sampleFrame * _working_buffer )
{
for( int i = 0; i < samples; ++i )
{
convertBuf[i][0] += m_noteData[i][0];
convertBuf[i][1] += m_noteData[i][1];
convertBuf[i][0] += m_sampleData[i][0];
convertBuf[i][1] += m_sampleData[i][1];
}
}
else
{
for( int i = 0; i < frames; ++i )
{
_working_buffer[i][0] += m_noteData[i][0];
_working_buffer[i][1] += m_noteData[i][1];
_working_buffer[i][0] += m_sampleData[i][0];
_working_buffer[i][1] += m_sampleData[i][1];
}
}
}
}
m_synthMutex.unlock();
// Convert sample rate if needed
if( sampleConvert )
{
@@ -518,6 +542,9 @@ void gigInstrument::play( sampleFrame * _working_buffer )
delete[] convertBuf;
}
m_notesMutex.unlock();
m_synthMutex.unlock();
// Set gain properly based on volume control
for( int i = 0; i < frames; ++i)
{
@@ -530,44 +557,26 @@ void gigInstrument::play( sampleFrame * _working_buffer )
// A key has been released
void gigInstrument::deleteNotePluginData( NotePlayHandle * _n )
{
GIGPluginData * pluginData = static_cast<GIGPluginData *>( _n->m_pluginData );
QMutexLocker locker(&m_notesMutex);
m_synthMutex.lock();
// 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 )
if( i->midiNote == pluginData->midiNote &&
( i->state == KeyDown || i->state == PlayingKeyDown ) )
i->state = KeyUp;
// Is the note supposed to have a release sample played?
bool noteRelease = false;
// Fade out the note we want to end
for( QList<gigNote>::iterator i = m_notes.begin(); i != m_notes.end(); ++i )
{
if( i->midiNote == pluginData->midiNote)
{
noteRelease = i->release;
i->adsr.keyup();
// TODO: not sample exact? What about in the middle of us writing out the sample?
}
}
m_synthMutex.unlock();
if( noteRelease )
{
// Add the release notes if available
const int baseVelocity = instrumentTrack()->midiPort()->baseVelocity();
const uint velocity = _n->midiVelocity( baseVelocity );
addNotes( pluginData->midiNote , velocity, true );
}
// 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 );
@@ -575,7 +584,68 @@ PluginView * gigInstrument::instantiateView( QWidget * _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& note, bool wantReleaseSample)
{
// Change key dimension, e.g. change samples based on what key is pressed
// in a certain range. From LinuxSampler
if( !wantReleaseSample &&
note.midiNote >= m_instrument->DimensionKeyRange.low &&
note.midiNote <= m_instrument->DimensionKeyRange.high )
m_currentKeyDimension = float( note.midiNote -
m_instrument->DimensionKeyRange.low ) / (
m_instrument->DimensionKeyRange.high -
m_instrument->DimensionKeyRange.low + 1 );
gig::Region* pRegion = m_instrument->GetFirstRegion();
while( pRegion )
{
Dimension dim = getDimensions( pRegion, note.velocity, wantReleaseSample );
gig::DimensionRegion* pDimRegion = pRegion->GetDimensionRegionByValue( dim.DimValues );
gig::Sample* pSample = pDimRegion->pSample;
// Does this note have release samples? Set this only on the original
// notes and not when we get the release samples.
if( !wantReleaseSample )
note.release = dim.release;
if( pSample && pSample->SamplesTotal != 0 )
{
int keyLow = pRegion->KeyRange.low;
int keyHigh = pRegion->KeyRange.high;
if( note.midiNote >= keyLow && note.midiNote <= keyHigh )
{
float attenuation = pDimRegion->GetVelocityAttenuation( note.velocity );;
float length = (double) pSample->SamplesTotal / engine::mixer()->processingSampleRate();
// TODO: sample panning? looping? crossfade different layers?
if( wantReleaseSample )
// From LinuxSampler, not sure how it was created
attenuation *= 1 - 0.01053 * (256 >> pDimRegion->ReleaseTriggerDecay) * length;
else
attenuation *= pDimRegion->SampleAttenuation;
note.samples.push_back(GigSample(pSample, attenuation,
ADSR(pDimRegion, pSample->SamplesPerSecond)));
}
}
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;
@@ -649,7 +719,8 @@ Dimension gigInstrument::getDimensions( gig::Region* pRegion, int velocity, bool
// 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
@@ -711,7 +782,6 @@ bool gigInstrument::convertSampleRate( sampleFrame& oldBuf, sampleFrame& newBuf,
void gigInstrument::updateSampleRate()
{
QMutexLocker locker(&m_srcMutex);
@@ -729,62 +799,6 @@ void gigInstrument::updateSampleRate()
// Find the sample we want to play (based on velocity, etc.)
void gigInstrument::addNotes( int midiNote, int velocity, bool release )
{
QMutexLocker synthLock(&m_synthMutex);
if( m_instrument )
{
// Change key dimension, e.g. change samples based on what key is pressed
// in a certain range. From LinuxSampler
if( midiNote >= m_instrument->DimensionKeyRange.low && midiNote
<= m_instrument->DimensionKeyRange.high )
m_currentKeyDimension = float( midiNote -
m_instrument->DimensionKeyRange.low ) / (
m_instrument->DimensionKeyRange.high -
m_instrument->DimensionKeyRange.low + 1 );
gig::Region* pRegion = m_instrument->GetFirstRegion();
while( pRegion )
{
Dimension dim = getDimensions( pRegion, velocity, release );
gig::DimensionRegion* pDimRegion = pRegion->GetDimensionRegionByValue( dim.DimValues );
gig::Sample* pSample = pDimRegion->pSample;
if( pSample && pSample->SamplesTotal != 0 )
{
int keyLow = pRegion->KeyRange.low;
int keyHigh = pRegion->KeyRange.high;
if( midiNote >= keyLow && midiNote <= keyHigh )
{
float attenuation = pDimRegion->GetVelocityAttenuation( velocity );;
float length = (double) pSample->SamplesTotal / engine::mixer()->processingSampleRate();
// TODO: sample panning? looping? crossfade different layers?
if( release )
// From LinuxSampler, not sure how it was created
attenuation *= 1 - 0.01053 * (256 >> pDimRegion->ReleaseTriggerDecay) * length;
else
attenuation *= pDimRegion->SampleAttenuation;
m_notes.push_back(gigNote(pSample, midiNote, attenuation, dim.release,
ADSR(pDimRegion, pSample->SamplesPerSecond)));
}
}
pRegion = m_instrument->GetNextRegion();
}
}
}
class gigKnob : public knob
{
public:
@@ -793,7 +807,7 @@ public:
{
setFixedSize( 31, 38 );
}
};
} ;
@@ -859,15 +873,12 @@ gigInstrumentView::gigInstrumentView( Instrument * _instrument, QWidget * _paren
gigInstrumentView::~gigInstrumentView()
{
}
void gigInstrumentView::modelChanged()
{
gigInstrument * k = castModel<gigInstrument>();
@@ -884,7 +895,6 @@ void gigInstrumentView::modelChanged()
void gigInstrumentView::updateFilename()
{
gigInstrument * i = castModel<gigInstrument>();
@@ -902,7 +912,6 @@ void gigInstrumentView::updateFilename()
void gigInstrumentView::updatePatchName()
{
gigInstrument * i = castModel<gigInstrument>();
@@ -915,7 +924,6 @@ void gigInstrumentView::updatePatchName()
void gigInstrumentView::invalidateFile()
{
m_patchDialogButton->setEnabled( false );
@@ -923,7 +931,6 @@ void gigInstrumentView::invalidateFile()
void gigInstrumentView::showFileDialog()
{
gigInstrument * k = castModel<gigInstrument>();
@@ -972,7 +979,6 @@ void gigInstrumentView::showFileDialog()
void gigInstrumentView::showPatchDialog()
{
gigInstrument * k = castModel<gigInstrument>();
@@ -983,27 +989,18 @@ void gigInstrumentView::showPatchDialog()
gigNote::gigNote( gig::Sample* pSample, int midiNote, float attenuation,
bool release, const ADSR& adsr ) :
sample( pSample ),
midiNote( midiNote ),
attenuation( attenuation ),
release( release ),
adsr( adsr ),
pos( 0 )
// Store information related to playing a sample from the GIG file
GigSample::GigSample( gig::Sample* pSample, float attenuation, const ADSR& adsr )
: sample( pSample ),
attenuation( attenuation ),
adsr( adsr ),
pos( 0 )
{
}
gigNote::gigNote( const gigNote& g ) :
sample( g.sample ),
midiNote( g.midiNote ),
attenuation( g.attenuation ),
release( g.release ),
adsr( g.adsr ),
pos( g.pos )
{
}
// 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),
@@ -1030,19 +1027,25 @@ ADSR::ADSR( gig::DimensionRegion* region, int sampleRate )
}
}
// Next time we get the amplitude, we'll be releasing the note
void ADSR::keyup()
{
isRelease = true;
}
// Can we delete the note now?
// Can we delete the sample now?
bool ADSR::done()
{
return isDone;
}
// Return the current amplitude and increment
// Return the current amplitude and increment internal positions
double ADSR::value()
{
double currentAmplitude = amplitude;
@@ -1088,6 +1091,7 @@ double ADSR::value()
}
extern "C"
{

View File

@@ -1,5 +1,5 @@
/*
* gig_player.h - a gig player using libgig
* gig_player.h - 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>
@@ -41,16 +41,16 @@
#include "gig.h"
class gigInstrumentView;
class gigInstance;
class NotePlayHandle;
class patchesDialog;
class QLabel;
class gigInstance
// Load a GIG file using libgig
class GigInstance
{
public:
gigInstance( QString filename ) :
GigInstance( QString filename ) :
riff( filename.toUtf8().constData() ),
gig( &riff )
{}
@@ -60,10 +60,10 @@ private:
public:
gig::File gig;
};
} ;
// Stores options for the notes, e.g. velocity and release time
struct Dimension
{
Dimension() :
@@ -75,9 +75,12 @@ struct Dimension
uint DimValues[8];
bool release;
};
} ;
// Takes information from the GIG file for a certain note and provides the
// amplitude (0-1) to multiply the signal by (internally incrementing the
// position in the envelope when asking for the amplitude).
class ADSR
{
// From the file
@@ -103,25 +106,51 @@ class ADSR
public:
ADSR( gig::DimensionRegion* region, int sampleRate );
void keyup(); // We will begin releasing starting now
bool done(); // Are we done?
bool done(); // Is this sample done playing?
double value(); // What's the current amplitude
};
} ;
class gigNote
// The sample from the GIG file with our current position in both the sample
// and the envelope
class GigSample
{
public:
gigNote( gig::Sample* pSample, int midiNote, float attenuation,
bool release, const ADSR& adsr );
gigNote( const gigNote& g );
GigSample( gig::Sample* pSample, float attenuation, const ADSR& adsr );
gig::Sample* sample;
int midiNote;
float attenuation;
bool release; // Whether to trigger a release sample on key up
ADSR adsr;
int pos; // Position in sample
};
} ;
// What portion of a note are we in?
enum GigState
{
KeyDown, // We just pressed the key
PlayingKeyDown, // The note is currently playing
KeyUp, // We just released the key
PlayingKeyUp, // The note is being released, e.g. a release sample is playing
Completed // The note is done playing, you can delete this note now
} ;
// Corresponds to a certain midi note pressed, but may contain multiple samples
class GigNote
{
public:
int midiNote;
int velocity;
bool release; // Whether to trigger a release sample on key up
GigState state;
QList<GigSample> samples;
GigNote( int midiNote, int velocity )
: midiNote(midiNote), velocity(velocity), release(false), state(KeyDown)
{
}
} ;
@@ -177,45 +206,55 @@ public slots:
private:
static QMutex s_instancesMutex;
static QMap<QString, gigInstance*> s_instances;
QList<gigNote> m_notes;
// Used to convert sample rates
SRC_STATE * m_srcState;
gigInstance* m_instance;
// The GIG file and instrument we're using
GigInstance* m_instance;
gig::Instrument* m_instrument;
uint32_t m_RandomSeed;
float m_currentKeyDimension;
// Part of the UI
QString m_filename;
// Protect synth when we are re-creating it.
QMutex m_synthMutex;
QMutex m_loadMutex;
QMutex m_srcMutex;
sample_rate_t m_internalSampleRate;
int m_lastMidiPitch;
int m_lastMidiPitchRange;
int m_channel;
LcdSpinBoxModel m_bankNum;
LcdSpinBoxModel m_patchNum;
FloatModel m_gain;
// Locking for the data
QMutex m_synthMutex;
QMutex m_srcMutex;
QMutex m_notesMutex;
// List of all the currently playing notes
QList<GigNote> m_notes;
// Buffer for note samples
sampleFrame* m_noteData;
unsigned int m_noteDataSize;
sampleFrame* m_sampleData;
unsigned int m_sampleDataSize;
// Used when determining which samples to use
uint32_t m_RandomSeed;
float m_currentKeyDimension;
private:
// Delete the current GIG instance if one is open
void freeInstance();
// Open the instrument in the currently-open GIG file
void getInstrument();
// Create "dimension" to select desired samples from GIG file based on
// parameters such as velocity
Dimension getDimensions( gig::Region* pRegion, int velocity, bool release );
// Add the desired samples to the note, either normal samples or release
// samples
void addSamples( GigNote& note, bool wantReleaseSample );
// Convert sample rates
bool convertSampleRate( sampleFrame& oldBuf, sampleFrame& newBuf,
int oldSize, int newSize, int oldRate, int newRate );
void addNotes( int midiNote, int velocity, bool release ); // Locks m_synthMutex internally
friend class gigInstrumentView;

View File

@@ -115,7 +115,7 @@ patchesDialog::~patchesDialog()
// Dialog setup loader.
void patchesDialog::setup ( gigInstance * pSynth, int iChan,
void patchesDialog::setup ( GigInstance * pSynth, int iChan,
const QString & _chanName,
LcdSpinBoxModel * _bankModel,
LcdSpinBoxModel * _progModel,

View File

@@ -50,7 +50,7 @@ public:
virtual ~patchesDialog();
void setup(gigInstance * pSynth, int iChan, const QString & _chanName,
void setup(GigInstance * pSynth, int iChan, const QString & _chanName,
LcdSpinBoxModel * _bankModel, LcdSpinBoxModel * _progModel, QLabel *_patchLabel );
public slots:
@@ -76,7 +76,7 @@ protected:
private:
// Instance variables.
gigInstance *m_pSynth;
GigInstance *m_pSynth;
int m_iChan;
int m_iBank;