diff --git a/plugins/GigPlayer/GigPlayer.cpp b/plugins/GigPlayer/GigPlayer.cpp index 243038eb9..b46296ea4 100644 --- a/plugins/GigPlayer/GigPlayer.cpp +++ b/plugins/GigPlayer/GigPlayer.cpp @@ -379,8 +379,7 @@ void GigInstrument::play( sampleFrame * _working_buffer ) for( QList::iterator sample = it->samples.begin(); sample != it->samples.end(); ++sample ) { - if( sample->sample == NULL || sample->adsr.done() || - sample->pos >= sample->sample->SamplesTotal - 1 ) + if( sample->sample == NULL || sample->adsr.done() ) { sample = it->samples.erase( sample ); @@ -416,21 +415,21 @@ void GigInstrument::play( sampleFrame * _working_buffer ) for( QList::iterator sample = it->samples.begin(); sample != it->samples.end(); ++sample ) { - if( sample->sample == NULL ) + if( sample->sample == NULL || sample->region == NULL ) { continue; } // Will change if resampling bool resample = false; - int samples = frames; // How many to grab - int used = frames; // How many we used + 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->pitchtrack == true || rate != sample->sample->SamplesPerSecond ) + if( sample->region->PitchTrack == true || rate != sample->sample->SamplesPerSecond ) { resample = true; @@ -438,7 +437,7 @@ void GigInstrument::play( sampleFrame * _working_buffer ) freq_factor = 1.0 * rate / sample->sample->SamplesPerSecond; // Factor for pitch shifting as well as resampling - if( sample->pitchtrack == true ) + if( sample->region->PitchTrack == true ) { freq_factor *= sample->freqFactor; } @@ -447,78 +446,15 @@ void GigInstrument::play( sampleFrame * _working_buffer ) samples = frames / freq_factor + MARGIN[m_interpolation]; } - // This note's data + // Load this note's data sampleFrame sampleData[samples]; - - // Set the position to where we currently are in the sample - sample->sample->SetPos( sample->pos ); // Note: not thread safe - - // Load the next portion of the sample - unsigned long allocationsize = samples * sample->sample->FrameSize; - int8_t buffer[allocationsize]; - unsigned long size = sample->sample->Read( &buffer, samples ) * sample->sample->FrameSize; - unsigned long nullExtensionSize = allocationsize - size; - std::memset( (int8_t*) &buffer + size, 0, nullExtensionSize ); - - // Convert from 16 or 24 bit into 32-bit float - if( sample->sample->BitDepth == 24 ) // 24 bit - { - uint8_t * pInt = reinterpret_cast( &buffer ); - - for( int 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( &buffer ); - - for( int 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; - } - } - } + 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( int i = 0; i < samples; ++i ) + for( f_cnt_t i = 0; i < samples; ++i ) { float amplitude = copy.value(); sampleData[i][0] *= amplitude; @@ -534,7 +470,7 @@ void GigInstrument::play( sampleFrame * _working_buffer ) if( sample->convertSampleRate( *sampleData, *convertBuf, samples, frames, freq_factor, used ) ) { - for( int i = 0; i < frames; ++i ) + for( f_cnt_t i = 0; i < frames; ++i ) { _working_buffer[i][0] += convertBuf[i][0]; _working_buffer[i][1] += convertBuf[i][1]; @@ -543,7 +479,7 @@ void GigInstrument::play( sampleFrame * _working_buffer ) } else { - for( int i = 0; i < frames; ++i ) + for( f_cnt_t i = 0; i < frames; ++i ) { _working_buffer[i][0] += sampleData[i][0]; _working_buffer[i][1] += sampleData[i][1]; @@ -560,7 +496,7 @@ void GigInstrument::play( sampleFrame * _working_buffer ) m_synthMutex.unlock(); // Set gain properly based on volume control - for( int i = 0; i < frames; ++i) + for( f_cnt_t i = 0; i < frames; ++i) { _working_buffer[i][0] *= m_gain.value(); _working_buffer[i][1] *= m_gain.value(); @@ -572,6 +508,174 @@ void GigInstrument::play( sampleFrame * _working_buffer ) +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(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( &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( &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 ) { @@ -649,7 +753,7 @@ void GigInstrument::addSamples( GigNote & gignote, bool wantReleaseSample ) float attenuation = pDimRegion->GetVelocityAttenuation( gignote.velocity );; float length = (float) pSample->SamplesTotal / engine::mixer()->processingSampleRate(); - // TODO: sample panning? looping? crossfade different layers? + // TODO: sample panning? crossfade different layers? if( wantReleaseSample == true ) { @@ -1000,34 +1104,32 @@ void GigInstrumentView::showPatchDialog() // 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 ), attenuation( attenuation ), pos( 0 ), - pitchtrack( false ), interpolation( interpolation ), - srcState( NULL ), sampleFreq( 0 ), freqFactor( 1 ) + : sample( pSample ), region( pDimRegion ), attenuation( attenuation ), + pos( 0 ), interpolation( interpolation ), srcState( NULL ), + sampleFreq( 0 ), freqFactor( 1 ) { - if( pSample != NULL && pDimRegion != NULL ) + 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. - pitchtrack = pDimRegion->PitchTrack; - // Calculate note pitch and frequency factor only if we're actually // going to be changing the pitch of the notes - if( pitchtrack == true ) + if( region->PitchTrack == true ) { // Calculate what frequency the provided sample is - float semitones = 1.0 * pSample->FineTune / 0xFFFFFFFF; sampleFreq = 440.0 * powf( 2, 1.0 / 12 * ( - 1.0 * pDimRegion->UnityNote - 69 ) + semitones ); + 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( pDimRegion, pSample->SamplesPerSecond / freqFactor ); + adsr = ADSR( region, sample->SamplesPerSecond / freqFactor ); } } @@ -1046,8 +1148,8 @@ GigSample::~GigSample() GigSample::GigSample( const GigSample& g ) - : sample( g.sample ), attenuation( g.attenuation ), adsr( g.adsr ), - pos( g.pos ), pitchtrack( g.pitchtrack ), interpolation( g.interpolation ), + : 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 @@ -1060,10 +1162,10 @@ GigSample::GigSample( const GigSample& g ) GigSample& GigSample::operator=( const GigSample& g ) { sample = g.sample; + region= g.region; attenuation = g.attenuation; adsr = g.adsr; pos = g.pos; - pitchtrack = g.pitchtrack; interpolation = g.interpolation; srcState = NULL; sampleFreq = g.sampleFreq; @@ -1100,7 +1202,7 @@ void GigSample::updateSampleRate() bool GigSample::convertSampleRate( sampleFrame & oldBuf, sampleFrame & newBuf, - int oldSize, int newSize, float freq_factor, int& used ) + f_cnt_t oldSize, f_cnt_t newSize, float freq_factor, f_cnt_t& used ) { if( srcState == NULL ) { @@ -1183,8 +1285,13 @@ ADSR::ADSR( gig::DimensionRegion * region, int 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 - if( attackLength == 0 ) + else if( attackLength == 0 ) { amplitude = 1.0; } @@ -1254,10 +1361,10 @@ float ADSR::value() { // 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 ) * exp( -5.0 / releaseLength * releasePosition ) - 1e-3; + amplitude = ( sustain + 1e-3 ) * expf( -5.0 / releaseLength * releasePosition ) - 1e-3; // Don't have an infinite exponential decay - if( amplitude < 0 ) + if( amplitude <= 0 || releasePosition >= releaseLength ) { amplitude = 0; isDone = true; @@ -1273,9 +1380,9 @@ float ADSR::value() // Increment internal positions a certain number of times -void ADSR::inc( int num ) +void ADSR::inc( f_cnt_t num ) { - for( int i = 0; i < num; ++i ) + for( f_cnt_t i = 0; i < num; ++i ) { value(); } diff --git a/plugins/GigPlayer/GigPlayer.h b/plugins/GigPlayer/GigPlayer.h index 9898501e7..cf3f55853 100644 --- a/plugins/GigPlayer/GigPlayer.h +++ b/plugins/GigPlayer/GigPlayer.h @@ -104,11 +104,11 @@ class ADSR bool isAttack; bool isRelease; bool isDone; - int attackPosition; - int attackLength; - int decayLength; - int releasePosition; - int releaseLength; + f_cnt_t attackPosition; + f_cnt_t attackLength; + f_cnt_t decayLength; + f_cnt_t releasePosition; + f_cnt_t releaseLength; public: ADSR(); @@ -116,7 +116,7 @@ public: void keyup(); // We will begin releasing starting now bool done(); // Is this sample done playing? float value(); // What's the current amplitude - void inc( int num ); // Increment internal positions by num + void inc( f_cnt_t num ); // Increment internal positions by num } ; @@ -138,14 +138,15 @@ public: // Needed since libsamplerate stores data internally between calls void updateSampleRate(); bool convertSampleRate( sampleFrame & oldBuf, sampleFrame & newBuf, - int oldSize, int newSize, float freq_factor, int& used ); + f_cnt_t oldSize, f_cnt_t newSize, float freq_factor, f_cnt_t& used ); gig::Sample * sample; + gig::DimensionRegion * region; float attenuation; ADSR adsr; // The position in sample - int pos; + f_cnt_t pos; // Whether to change the pitch of the samples, e.g. if there's only one // sample per octave and you want that sample pitch shifted for the rest of @@ -291,6 +292,11 @@ private: // parameters such as velocity Dimension getDimensions( gig::Region * pRegion, int velocity, bool release ); + // Load sample data from the Gig file, looping the sample where needed + void loadSample( GigSample& sample, sampleFrame* sampleData, f_cnt_t samples ); + f_cnt_t getLoopedIndex( f_cnt_t index, f_cnt_t startf, f_cnt_t endf ) const; + f_cnt_t getPingPongIndex( f_cnt_t index, f_cnt_t startf, f_cnt_t endf ) const; + // Add the desired samples to the note, either normal samples or release // samples void addSamples( GigNote & gignote, bool wantReleaseSample );