/** * will only occur for a sequenced BaseSynthEvent */ void BaseSynthEvent::mixBuffer( AudioBuffer* outputBuffer, int bufferPos, int minBufferPosition, int maxBufferPosition, bool loopStarted, int loopOffset, bool useChannelRange ) { lock(); // over the max position ? read from the start ( implies that sequence has started loop ) if ( bufferPos > maxBufferPosition ) { if ( useChannelRange ) bufferPos -= maxBufferPosition; else if ( !loopStarted ) return; } int bufferEndPos = bufferPos + AudioEngineProps::BUFFER_SIZE; if (( bufferPos >= _sampleStart || bufferEndPos > _sampleStart ) && bufferPos < _sampleEnd ) { lastWriteIndex = _sampleStart > bufferPos ? 0 : bufferPos - _sampleStart; int writeOffset = _sampleStart > bufferPos ? _sampleStart - bufferPos : 0; // render the snippet _synthInstrument->synthesizer->render( _buffer, this ); // note we merge using MAX_PHASE as mix volume (event volume was applied during synthesis) outputBuffer->mergeBuffers( _buffer, 0, writeOffset, MAX_PHASE ); // reset of event properties at end of write if ( lastWriteIndex >= _sampleLength ) calculateBuffers(); } if ( loopStarted && bufferPos >= loopOffset ) { bufferPos = minBufferPosition + loopOffset; if ( bufferPos >= _sampleStart && bufferPos <= _sampleEnd ) { lastWriteIndex = 0; // render the snippet from the start // TODO: specify range in ::render method ? this would avoid unnecessary buffer merging ;) _synthInstrument->synthesizer->render( _buffer, this ); // overwrites old buffer contents // note we merge using MAX_PHASE as mix volume (event volume was applied during synthesis) outputBuffer->mergeBuffers( _buffer, 0, loopOffset, MAX_PHASE ); // reset of event properties at end of write if ( lastWriteIndex >= _sampleLength ) calculateBuffers(); } } unlock(); }
/** * @param aInstrument pointer to the SynthInstrument containing the rendering properties for the BaseSynthEvent * @param aFrequency frequency in Hz for the note to be rendered * @param aPosition offset in the sequencer where this event starts playing / becomes audible * @param aLength length of the event (in sequencer steps) * @param aIsSequenced whether this event is sequenced and only audible in a specific sequence range */ void BaseSynthEvent::init( SynthInstrument *aInstrument, float aFrequency, int aPosition, int aLength, bool aIsSequenced ) { _destroyableBuffer = true; // synth event buffer is always unique and managed by this instance ! _instrument = aInstrument; _adsr = aInstrument->adsr != 0 ? aInstrument->adsr->clone() : new ADSR(); // when instrument has no fixed length and the decay is short // we deactivate the decay envelope completely (for now) if ( !aIsSequenced && _adsr->getDecay() < .75 ) _adsr->setDecay( 0 ); _buffer = 0; _locked = false; position = aPosition; length = aLength; isSequenced = aIsSequenced; _queuedForDeletion = false; _deleteMe = false; _update = false; _cancel = false; // whether we should cancel caching _hasMinLength = isSequenced; // a sequenced event has no early cancel _caching = false; _cachingCompleted = false; // whether we're done caching _autoCache = false; // we'll cache sequentially instead _rendering = false; _volume = aInstrument->volume; _sampleLength = 0; _cacheWriteIndex = 0; setFrequency( aFrequency ); calculateBuffers(); addToSequencer(); }
/** * @param aInstrument pointer to the SynthInstrument containing the rendering properties for the BaseSynthEvent * @param aFrequency frequency in Hz for the note to be rendered * @param aPosition offset in the sequencer where this event starts playing / becomes audible * @param aLength length of the event (in sequencer steps) * @param isSequenced whether this event is sequenced and only audible in a specific sequence range */ void BaseSynthEvent::init( SynthInstrument* aInstrument, float aFrequency, int aPosition, float aLength, bool isSequenced ) { instanceId = ++INSTANCE_COUNT; _destroyableBuffer = true; // synth event buffer is always unique and managed by this instance ! _instrument = aInstrument; _synthInstrument = aInstrument; // convenience reference (typecast to SynthInstrument) position = aPosition; length = aLength; cachedProps.ADSRenvelope = 0.0; cachedProps.arpeggioPosition = 0; cachedProps.arpeggioStep = 0; int maxOscillatorAmount = 8; // let's assume a max amount of oscillators of 8 here cachedProps.oscillatorPhases.reserve( maxOscillatorAmount ); for ( int i = 0; i < maxOscillatorAmount; ++i ) cachedProps.oscillatorPhases.push_back( 0.0 ); this->isSequenced = isSequenced; _queuedForDeletion = false; _deleteMe = false; _hasMinLength = isSequenced; // a sequenced event has no early cancel _sampleLength = 0; lastWriteIndex = 0; setFrequency( aFrequency ); calculateBuffers(); addToSequencer(); }
void BaseSynthEvent::unlock() { _locked = false; if ( _updateAfterUnlock ) calculateBuffers(); _updateAfterUnlock = false; }
void ScopeGUI::resizedGUI() { initBuffers(); if(audioBuffer.size() > 0) { calculateBuffers(); } }
/** * actual updating of the properties, requested by invalidateProperties * this operation might potentially delete objects that could be in * use during a rendering operation, as such it is invoked from the render */ void BaseSynthEvent::updateProperties() { // ADSR envelopes _adsr->cloneEnvelopes( _instrument->adsr ); calculateBuffers(); _update = false; // we're in sync now :) _cancel = false; }
/** * @param aPosition position in the sequencer where this event starts playing * @param aLength length (in sequencer steps) of this event * @param aInstrument the SynthInstrument whose properties will be used for synthesizing this event * @param aState which oscillator(s) to update 0 = all, 1 = oscillator 1, 2 = oscillator 2 * this is currently rudimentary as both oscillators are rendered and merged into one * this is here for either legacy purposes or when performance improvements can be gained */ void SynthEvent::updateProperties( int aPosition, float aLength, SynthInstrument *aInstrument, int aState ) { bool updateLFO1 = true;//( aState == 0 || aState == 1 ); bool updateOSC2 = true;//( aState == 0 || aState == 2 ); _type = aInstrument->waveform; position = aPosition; length = aLength; _adsr->cloneEnvelopes( aInstrument->adsr ); // secondary oscillator if ( updateOSC2 && aInstrument->osc2active ) createOSC2( aPosition, aLength, aInstrument ); else destroyOSC2(); // modules applyModules( aInstrument ); if ( updateLFO1 ) { if ( _caching /*&& !_cachingCompleted */) { if ( _osc2 != 0 && _osc2->_caching ) _osc2->_cancel = true; _cancel = true; } else { calculateBuffers(); } } }
/** * will only occur for a sequenced BaseSynthEvent */ void BaseSynthEvent::mixBuffer( AudioBuffer* outputBuffer, int bufferPos, int minBufferPosition, int maxBufferPosition, bool loopStarted, int loopOffset, bool useChannelRange ) { // is EVENT_CACHING is enabled, read from cached buffer if ( AudioEngineProps::EVENT_CACHING ) { BaseAudioEvent::mixBuffer( outputBuffer, bufferPos, minBufferPosition, maxBufferPosition, loopStarted, loopOffset, useChannelRange ); } else { // EVENT_CACHING is disabled, synthesize on the fly lock(); // over the max position ? read from the start ( implies that sequence has started loop ) if ( bufferPos >= maxBufferPosition ) { if ( useChannelRange ) bufferPos -= maxBufferPosition; else if ( !loopStarted ) bufferPos -= ( maxBufferPosition - minBufferPosition ); } int bufferEndPos = bufferPos + AudioEngineProps::BUFFER_SIZE; if (( bufferPos >= _sampleStart || bufferEndPos > _sampleStart ) && bufferPos < _sampleEnd ) { // render the snippet _cacheWriteIndex = _sampleStart > bufferPos ? 0 : bufferPos - _sampleStart; int writeOffset = _sampleStart > bufferPos ? _sampleStart - bufferPos : 0; render( _buffer ); // overwrites old buffer contents outputBuffer->mergeBuffers( _buffer, 0, writeOffset, MAX_PHASE ); // reset of properties at end of write if ( _cacheWriteIndex >= _sampleLength ) calculateBuffers(); } if ( loopStarted && bufferPos >= loopOffset ) { bufferPos = minBufferPosition + loopOffset; if ( bufferPos >= _sampleStart && bufferPos <= _sampleEnd ) { _cacheWriteIndex = 0; // render the snippet from the start render( _buffer ); // overwrites old buffer contents outputBuffer->mergeBuffers( _buffer, 0, loopOffset, MAX_PHASE ); // reset of properties at end of write if ( _cacheWriteIndex >= _sampleLength ) calculateBuffers(); } } unlock(); } }
/** * @param aInstrument pointer to the SynthInstrument containing the rendering properties for the SynthEvent * @param aFrequency frequency in Hz for the note to be rendered * @param aPosition offset in the sequencer where this event starts playing / becomes audible * @param aLength length of the event (in sequencer steps) * @param aHasParent when true, this SynthEvent will be merged into the buffer of its parent instead of being * added to the Sequencer as an individual event, this makes rendering of its parent event * draw more CPU as its rendering multiple buffers, but after merging it consumes less memory * than two individual buffers would, it also omits the need of having float SynthEvents * to be mixed by the Sequencer * @param aIsSequenced whether this event is sequenced and only audible in a specific sequence range */ void SynthEvent::init( SynthInstrument *aInstrument, float aFrequency, int aPosition, int aLength, bool aHasParent, bool aIsSequenced ) { _destroyableBuffer = true; // always unique and managed by this instance ! _instrument = aInstrument; _adsr = _instrument->adsr->clone(); // when instrument has no fixed length and the decay is short // we deactivate the decay envelope completely (for now) if ( !aIsSequenced && _adsr->getDecay() < .75 ) _adsr->setDecay( 0 ); _buffer = 0; _ringBuffer = 0; _ringBufferSize = 0; _locked = false; _frequency = aFrequency; _baseFrequency = aFrequency; position = aPosition; length = aLength; hasParent = aHasParent; isSequenced = aIsSequenced; _queuedForDeletion = false; _deleteMe = false; _cancel = false; // whether we should cancel caching _caching = false; _cachingCompleted = false; // whether we're done caching _autoCache = false; // we'll cache sequentially instead _type = aInstrument->waveform; _osc2 = 0; _volume = aInstrument->volume; _sampleLength = 0; _cacheWriteIndex = 0; // constants used by waveform generators TWO_PI_OVER_SR = TWO_PI / AudioEngineProps::SAMPLE_RATE; pwr = PI / 1.05; pwAmp = 0.075; EnergyDecayFactor = 0.990f; // TODO make this settable ? _pwmValue = 0.0; _phase = 0.0; // secondary oscillator, note different constructor // to omit going into recursion! if ( !hasParent && aInstrument->osc2active ) createOSC2( position, length, aInstrument ); setFrequency( aFrequency ); // modules _arpeggiator = 0; applyModules( aInstrument ); // buffer _hasMinLength = isSequenced; // a sequenced event has no early cancel calculateBuffers(); // add the event to the sequencer so it can be heard // note that OSC2 contents aren't added to the sequencer // individually as their render is invoked by their parent, // writing directly into their parent buffer (saves memory overhead) if ( isSequenced ) { if ( !hasParent ) aInstrument->audioEvents->push_back( this ); } else { if ( !hasParent ) aInstrument->liveEvents->push_back( this ); } }
/** * the actual synthesizing of the audio * * @param aOutputBuffer {AudioBuffer*} the buffer to write into */ void SynthEvent::render( AudioBuffer* aOutputBuffer ) { int i; int bufferLength = aOutputBuffer->bufferSize; SAMPLE_TYPE amp = 0.0; SAMPLE_TYPE tmp, am, dpw, pmv; bool hasOSC2 = _osc2 != 0; int renderStartOffset = AudioEngineProps::EVENT_CACHING && isSequenced ? _cacheWriteIndex : 0; int maxSampleIndex = _sampleLength - 1; // max index possible for this events length int renderEndOffset = renderStartOffset + bufferLength; // max buffer index to be written to in this cycle // keep in bounds of event duration if ( renderEndOffset > maxSampleIndex ) { renderEndOffset = maxSampleIndex; aOutputBuffer->silenceBuffers(); // as we tend to overwrite the incoming buffer } for ( i = renderStartOffset; i < renderEndOffset; ++i ) { switch ( _type ) { case WaveForms::SINE_WAVE: // ---- Sine wave if ( _phase < .5 ) { tmp = ( _phase * 4.0 - 1.0 ); amp = ( 1.0 - tmp * tmp ); } else { tmp = ( _phase * 4.0 - 3.0 ); amp = ( tmp * tmp - 1.0 ); } amp *= .7; // sines tend to distort easily when overlapping multi timbral parts break; case WaveForms::SAWTOOTH: // ---- Sawtooth amp = ( _phase < 0 ) ? _phase - ( int )( _phase - 1 ) : _phase - ( int )( _phase ); //amp *= ( !isSequenced ? .7 : 1.0; ); break; case WaveForms::SQUARE_WAVE: // ---- Square wave if ( _phase < .5 ) { tmp = TWO_PI * ( _phase * 4.0 - 1.0 ); amp = ( 1.0 - tmp * tmp ); } else { tmp = TWO_PI * ( _phase * 4.0 - 3.0 ); amp = ( tmp * tmp - 1.0 ); } amp *= .01; // these get loud ! 0.005 break; case WaveForms::TRIANGLE: // ---- triangle if ( _phase < .5 ) { tmp = ( _phase * 4.0 - 1.0 ); amp = ( 1.0 - tmp * tmp ) * .75; } else { tmp = ( _phase * 4.0 - 3.0 ); amp = ( tmp * tmp - 1.0 ) * .75; } // the actual triangulation function amp = amp < 0 ? -amp : amp; break; case WaveForms::PWM: // --- pulse width modulation pmv = i + ( ++_pwmValue ); // i + event position dpw = sin( pmv / 0x4800 ) * pwr; // LFO -> PW amp = _phase < PI - dpw ? pwAmp : -pwAmp; // PWM has its own phase update operation _phase = _phase + ( TWO_PI_OVER_SR * _frequency ); _phase = _phase > TWO_PI ? _phase - TWO_PI : _phase; // we multiply the amplitude as PWM results in a "quieter" wave amp *= 4; /* // OLD: oscillation modulating the PW wave am = sin( pmv / 0x1000 ); // LFO -> AM amp *= am; */ break; case WaveForms::NOISE: // --- noise if ( _phase < .5 ) { tmp = ( _phase * 4.0 - 1.0 ); amp = ( 1.0 - tmp * tmp ); } else { tmp = ( _phase * 4.0 - 3.0 ); amp = ( tmp * tmp - 1.0 ); } // above we calculated pitch, now we add some // randomization to the signal for the actual noise amp *= randomFloat(); break; case WaveForms::KARPLUS_STRONG: // --- Karplus-Strong algorithm for plucked string-sound _ringBuffer->enqueue(( EnergyDecayFactor * (( _ringBuffer->dequeue() + _ringBuffer->peek()) / 2 ))); amp = _ringBuffer->peek(); // * .7; gets the level down a bit, 'tis loud break; } // --- _phase update operations if ( _type != WaveForms::PWM ) { _phase += _phaseIncr; // restore _phase, max range is 0 - 1 ( float ) if ( _phase > MAX_PHASE ) _phase -= MAX_PHASE; } if ( !isSequenced ) amp *= .5; // anticipating multi-timbral fun // update modules if ( _arpeggiator != 0 ) { // step the arpeggiator to the next position if ( _arpeggiator->peek()) setFrequency( _arpeggiator->getPitchForStep( _arpeggiator->getStep(), _baseFrequency ), true, false ); } // -- write the output into the buffers channels for ( int c = 0, ca = aOutputBuffer->amountOfChannels; c < ca; ++c ) aOutputBuffer->getBufferForChannel( c )[ i ] = amp * _volume * VOLUME_CORRECTION; // stop caching/rendering loop when cancel was requested if ( _cancel ) break; } // secondary oscillator ? render its contents into this (parent) buffer if ( hasOSC2 && !_cancel ) { // create a temporary buffer (this prevents writing to deleted buffers // when the parent event changes its _buffer properties (f.i. tempo change) int tempLength = ( AudioEngineProps::EVENT_CACHING && isSequenced ) ? ( renderEndOffset - _cacheWriteIndex ) : bufferLength; AudioBuffer* tempBuffer = new AudioBuffer( aOutputBuffer->amountOfChannels, tempLength ); _osc2->render( tempBuffer ); aOutputBuffer->mergeBuffers( tempBuffer, 0, renderStartOffset, MAX_PHASE ); delete tempBuffer; // free allocated memory } // apply envelopes and update cacheWriteIndex for next render cycle if ( !hasParent ) { _adsr->apply( aOutputBuffer, _cacheWriteIndex ); _cacheWriteIndex += i; } if ( AudioEngineProps::EVENT_CACHING ) { if ( isSequenced ) { _caching = false; // was a cancel requested ? re-cache to // match the new properties (cancel may only // be requested when changing properties!) if ( _cancel ) { _cancel = false; calculateBuffers(); } else { if ( i == maxSampleIndex ) _cachingCompleted = true; if ( _bulkCacheable ) _autoCache = true; } } } }
/** * actual updating of the properties, requested by invalidateProperties * this operation might potentially delete objects that could be in * use during a rendering operation, as such it is invoked from the render */ void BaseSynthEvent::updateProperties() { calculateBuffers(); }