TEST( BaseAudioEvent, MixBufferLoopeableEvent )
{
    BaseAudioEvent* audioEvent = new BaseAudioEvent();

    int sourceSize            = 16;
    AudioBuffer* sourceBuffer = new AudioBuffer( 1, sourceSize );
    SAMPLE_TYPE* rawBuffer    = sourceBuffer->getBufferForChannel( 0 );
    fillAudioBuffer( sourceBuffer );

    audioEvent->setBuffer( sourceBuffer, false );
    audioEvent->setLoopeable( true );
    audioEvent->setSampleLength( 16 * 4 ); // thus will loop 4 times
    audioEvent->positionEvent ( 0, 16, 0 );

    // create an output buffer at a size smaller than the source buffer length

    int outputSize = ( int )(( double ) sourceSize * .4 );
    AudioBuffer* targetBuffer = new AudioBuffer( sourceBuffer->amountOfChannels, outputSize );

    int minBufferPos = audioEvent->getSampleStart();
    int bufferPos    = minBufferPos;
    int maxBufferPos = audioEvent->getSampleEnd();

    // test the seamless mixing over multiple iterations

    for ( ; bufferPos < maxBufferPos; bufferPos += outputSize )
    {
        // mix buffer contents

        targetBuffer->silenceBuffers();
        bool loopStarted = bufferPos + ( outputSize - 1 ) > maxBufferPos;
        int loopOffset   = ( maxBufferPos - bufferPos ) + 1;
        audioEvent->mixBuffer( targetBuffer, bufferPos, minBufferPos, maxBufferPos, loopStarted, loopOffset, false );

        // assert results

        SAMPLE_TYPE* mixedBuffer = targetBuffer->getBufferForChannel( 0 );

        for ( int i = 0; i < outputSize; ++i )
        {
            int compareOffset = ( bufferPos + i ) % sourceSize;

            EXPECT_EQ( rawBuffer[ compareOffset ], mixedBuffer[ i ] )
                << "expected mixed buffer contents to equal the source contents at mixed offset " << i << " for source offset " << compareOffset;
        }
    }

    delete targetBuffer;
    delete sourceBuffer;
    delete audioEvent;
}
void JavaUtilities::createSampleFromBuffer( jstring aKey, jint aBufferLength, jint aChannelAmount, jdoubleArray aBuffer, jdoubleArray aOptRightBuffer )
{
    AudioBuffer* sampleBuffer = new AudioBuffer( aChannelAmount, aBufferLength );

    int i = 0;

    // get a pointer to the Java array
    jdouble* c_array;
    c_array = JavaBridge::getEnvironment()->GetDoubleArrayElements( aBuffer, 0 );

    // exception checking
    if ( c_array == NULL )
        return;

    // copy buffer contents
    SAMPLE_TYPE* channelBuffer = sampleBuffer->getBufferForChannel( 0 );

    for ( i = 0; i < aBufferLength; i++ )
        channelBuffer[ i ] = ( SAMPLE_TYPE ) c_array[ i ];

    // release the memory so Java can have it again
    JavaBridge::getEnvironment()->ReleaseDoubleArrayElements( aBuffer, c_array, 0 );

    // stereo ?

    if ( aChannelAmount == 2 )
    {
        c_array = JavaBridge::getEnvironment()->GetDoubleArrayElements( aOptRightBuffer, 0 );

        // exception checking
        if ( c_array == NULL )
            return;

        // copy buffer contents
        channelBuffer = sampleBuffer->getBufferForChannel( 1 );

        for ( i = 0; i < aBufferLength; i++ )
            channelBuffer[ i ] = ( SAMPLE_TYPE ) c_array[ i ];

        // release the memory so Java can have it again
        JavaBridge::getEnvironment()->ReleaseDoubleArrayElements( aOptRightBuffer, c_array, 0 );
    }

    // convert jstring to std::string
    const char* s = JavaBridge::getEnvironment()->GetStringUTFChars( aKey, NULL );
    std::string theKey = s;
    JavaBridge::getEnvironment()->ReleaseStringUTFChars( aKey, s );

    SampleManager::setSample( theKey, sampleBuffer );
}
Beispiel #3
0
AudioBuffer* AudioBuffer::clone()
{
    AudioBuffer* output = new AudioBuffer( amountOfChannels, bufferSize );

    for ( int i = 0; i < amountOfChannels; ++i )
    {
        SAMPLE_TYPE* sourceBuffer = getBufferForChannel( i );
        SAMPLE_TYPE* targetBuffer = output->getBufferForChannel( i );

        memcpy( targetBuffer, sourceBuffer, bufferSize * sizeof( SAMPLE_TYPE ));
    }
    return output;
}
Beispiel #4
0
    /**
     * write the contents of the write buffer into
     * an output file, this will only write content
     * up until the point if was written to in case
     * the buffer wasn't full yet
     */
    void writeBufferToFile( int aSampleRate, int aNumChannels, bool broadcastUpdate )
    {
        // quick assertion
        if ( cachedBuffer == 0 )
            return;

        // copy string contents for appending of filename
        std::string outputFile = std::string( outputDirectory.c_str());

        int bufferSize = outputBufferSize;

        // recorded less than maximum available in buffer ? cut silence
        // by writing recording into temporary buffers

        if ( outputWriterIndex < bufferSize )
        {
            bufferSize = outputWriterIndex;

            AudioBuffer* tempBuffer = new AudioBuffer( aNumChannels, bufferSize );

            for ( int i = 0; i < bufferSize; ++i )
            {
                for ( int c = 0; c < aNumChannels; ++c )
                    tempBuffer->getBufferForChannel( c )[ i ] = cachedBuffer->getBufferForChannel( c )[ i ];
            }

            WaveWriter::bufferToFile( outputFile.append( SSTR( AudioEngine::recordingFileId )),
                                      tempBuffer, aSampleRate );

            // free memory allocated by temporary buffer

            delete tempBuffer;
        }
        else
        {
            WaveWriter::bufferToFile( outputFile.append( SSTR( AudioEngine::recordingFileId )),
                                      cachedBuffer, aSampleRate );
        }

        flushOutput(); // free memory

        // broadcast update, pass buffer identifier to identify last recording
        if ( broadcastUpdate )
            Notifier::broadcast( Notifications::RECORDING_STATE_UPDATED, AudioEngine::recordingFileId );
    }
TEST( AudioEngine, Output )
{
    AudioEngine::test_program    = 2;   // help mocked OpenSL IO identify which test is running
    AudioEngine::test_successful = false;

    // prepare engine environment

    SequencerController* controller = new SequencerController();
    controller->prepare( 16, 48000, 130.0f, 4, 4 ); // 130 BPM in 4/4 time at 48 kHz sample rate w/buffer size of 16 samples
    controller->setTempoNow( 130.0f, 4, 4 );
    controller->rewind();

    AudioEngine::volume = 1;    // QQQ : later on we test mix volume ;)

    // create an AudioEvent that holds a simple waveform
    // the resulting 16 sample mono buffer contains the following samples:
    //
    // -1,-1,-1,-1,0,0,0,0,1,1,1,1,0,0,0,0
    //
    // the event will last for an entire measure in duration

    BaseInstrument* instrument = new BaseInstrument();
    BaseAudioEvent* event      = new BaseAudioEvent( instrument );

    AudioBuffer* buffer    = new AudioBuffer( 1, 16 );
    SAMPLE_TYPE* rawBuffer = buffer->getBufferForChannel( 0 );

    for ( int i = 0; i < 4; ++i )
        rawBuffer[ i ] = ( SAMPLE_TYPE ) -MAX_PHASE;

    for ( int i = 4; i < 8; ++i )
        rawBuffer[ i ] = ( SAMPLE_TYPE ) 0;

    for ( int i = 8; i < 12; ++i )
        rawBuffer[ i ] = ( SAMPLE_TYPE ) MAX_PHASE;

    for ( int i = 12; i < 16; ++i )
        rawBuffer[ i ] = ( SAMPLE_TYPE ) 0;

    event->setBuffer( buffer, false );
    event->setLoopeable( true );
    event->setSampleLength( AudioEngine::samples_per_bar );
    event->positionEvent( 0, 16, 0 );
    event->addToSequencer();

    // start the engine

    controller->setPlaying( true );
    AudioEngine::start();

    // evaluate results (assertions are made in mock_opensl_io.cpp)

    ASSERT_TRUE( AudioEngine::test_successful )
        << "expected test to be successful";

    EXPECT_EQ( 3, AudioEngine::test_program )
        << "expected test program to have incremented";

    // clean up

    controller->setPlaying( false );
    AudioEngine::render_iterations = 0;

    delete controller;
    delete instrument;
    delete event;
    delete buffer;
}
Beispiel #6
0
    /**
     * starts the render thread
     * NOTE: the render thread is always active, even when the
     * sequencer is paused
     */
    void start()
    {
        OPENSL_STREAM *p;

        p = android_OpenAudioDevice( AudioEngineProps::SAMPLE_RATE,     AudioEngineProps::INPUT_CHANNELS,
                                     AudioEngineProps::OUTPUT_CHANNELS, AudioEngineProps::BUFFER_SIZE );

        // hardware unavailable ? halt thread, trigger JNI callback for error handler
        if ( p == NULL )
        {
            Observer::handleHardwareUnavailable();
            return;
        }
        // audio hardware available, start render thread

        int buffer_size, i, c, ci;
        buffer_size        = AudioEngineProps::BUFFER_SIZE;
        int outputChannels = AudioEngineProps::OUTPUT_CHANNELS;
        bool isMono        = outputChannels == 1;
        std::vector<AudioChannel*> channels;
        std::vector<AudioChannel*> channels2; // used when loop starts for gathering events at the start range

        bool loopStarted = false;   // whether the current buffer will exceed the end offset of the loop (read remaining samples from the start)
        int loopOffset = 0;         // the offset within the current buffer where we start reading from the current loops start offset
        int loopAmount = 0;         // amount of samples we must read from the current loops start offset

        float recbufferIn   [ buffer_size ];                  // used for recording from device input
        float outbuffer     [ buffer_size * outputChannels ]; // the output buffer rendered by the hardware

        // generate buffers for temporary channel buffer writes
        AudioBuffer* channelBuffer = new AudioBuffer( outputChannels, buffer_size );
        AudioBuffer* inbuffer      = new AudioBuffer( outputChannels, buffer_size ); // accumulates all channels ("master strip")
        AudioBuffer* recbuffer     = new AudioBuffer( AudioEngineProps::INPUT_CHANNELS, buffer_size );

        thread = 1;

        // signal processors
        Finalizer* limiter = new Finalizer  ( 2, 500,  AudioEngineProps::SAMPLE_RATE, outputChannels );
        LPFHPFilter* hpf   = new LPFHPFilter(( float ) AudioEngineProps::SAMPLE_RATE, 55, outputChannels );

        while ( thread )
        {
            // erase previous buffer contents
            inbuffer->silenceBuffers();

            // gather the audio events by the buffer range currently being processed
            int endPosition = bufferPosition + buffer_size;
            channels        = sequencer::getAudioEvents( channels, bufferPosition, endPosition, true );

            // read pointer exceeds maximum allowed offset ? => sequencer has started its loop
            // we must now also gather extra events at the start position of the seq. range
            loopStarted = endPosition > max_buffer_position;
            loopOffset  = (( max_buffer_position + 1 ) - bufferPosition );
            loopAmount  = buffer_size - loopOffset;

            if ( loopStarted )
            {
                // were we bouncing the audio ? save file and stop rendering
                if ( bouncing )
                {
                    DiskWriter::writeBufferToFile( AudioEngineProps::SAMPLE_RATE, AudioEngineProps::OUTPUT_CHANNELS, false );

                    // broadcast update via JNI, pass buffer identifier name to identify last recording
                    Observer::handleBounceComplete( 1 );
                    thread = 0; // stop thread, halts rendering
                    break;
                }
                else
                {
                    endPosition -= max_buffer_position;
                    channels2 = sequencer::getAudioEvents( channels2, min_buffer_position, min_buffer_position + buffer_size, false );

                    // er? the channels are magically merged by above invocation..., performing the insert below adds the same events TWICE*POP*!?!?
                    //channels.insert( channels.end(), channels2.begin(), channels2.end() ); // merge the channels into one

                    channels2.clear();  // would clear on next "getAudioEvents"-query... but why wait ?
                }
            }

            // record audio from Android device ?
            if ( recordFromDevice && AudioEngineProps::INPUT_CHANNELS > 0 )
            {
                int recSamps                  = android_AudioIn( p, recbufferIn, AudioEngineProps::BUFFER_SIZE );
                SAMPLE_TYPE* recBufferChannel = recbuffer->getBufferForChannel( 0 );

                for ( int j = 0; j < recSamps; ++j )
                {
                    recBufferChannel[ j ] = recbufferIn[ j ];//static_cast<float>( recbufferIn[ j ] );

                    // merge recording into current input buffer for instant monitoring
                    if ( monitorRecording )
                    {
                        for ( int k = 0; k < outputChannels; ++k )
                            inbuffer->getBufferForChannel( k )[ j ] = recBufferChannel[ j ];
                    }
                }
            }

            // channel loop
            int j = 0;
            int channelAmount = channels.size();

            for ( j; j < channelAmount; ++j )
            {
                AudioChannel* channel = channels[ j ];
                bool isCached         = channel->hasCache;                // whether this channel has a fully cached buffer
                bool mustCache        = AudioEngineProps::CHANNEL_CACHING && channel->canCache() && !isCached; // whether to cache this channels output
                bool gotBuffer        = false;
                int cacheReadPos      = 0;  // the offset we start ready from the channel buffer (when writing to cache)

                SAMPLE_TYPE channelVolume                = ( SAMPLE_TYPE ) channel->mixVolume;
                std::vector<BaseAudioEvent*> audioEvents = channel->audioEvents;
                int amount                               = audioEvents.size();

                // clear previous channel buffer content
                channelBuffer->silenceBuffers();

                bool useChannelRange  = channel->maxBufferPosition != 0; // channel has its own buffer range (i.e. drummachine)
                int maxBufferPosition = useChannelRange ? channel->maxBufferPosition : max_buffer_position;

                // we make a copy of the current buffer position indicator
                int bufferPos = bufferPosition;

                // ...in case the AudioChannels maxBufferPosition differs from the sequencer loop range
                // note that these buffer positions are always a full bar in length (as we loop measures)
                while ( bufferPos > maxBufferPosition )
                    bufferPos -= bytes_per_bar;

                // only render sequenced events when the sequencer isn't in the paused state
                // and the channel volume is actually at an audible level! ( > 0 )

                if ( playing && amount > 0 && channelVolume > 0.0 )
                {
                    if ( !isCached )
                    {
                        // write the audioEvent buffers into the main output buffer
                        for ( int k = 0; k < amount; ++k )
                        {
                            BaseAudioEvent* audioEvent = audioEvents[ k ];

                            if ( !audioEvent->isLocked())   // make sure we are allowed to query the contents
                            {
                                audioEvent->lock();         // prevent buffer mutations during this read cycle
                                audioEvent->mixBuffer( channelBuffer, bufferPos, min_buffer_position,
                                                       maxBufferPosition, loopStarted, loopOffset, useChannelRange );

                                audioEvent->unlock();   // release lock
                            }
                        }
                    }
                    else
                    {
                        channel->readCachedBuffer( channelBuffer, bufferPos );
                    }
                }

                // perform live rendering for this instrument
                if ( channel->hasLiveEvents )
                {
                    int lAmount = channel->liveEvents.size();

                    // the volume of the live events is divided by the channel mix as a live event
                    // is played on the same instrument, but just as a different voice (note the
                    // events can have their own mix level)

                    float lAmp = channel->mixVolume > 0.0 ? MAX_PHASE / channel->mixVolume : MAX_PHASE;

                    for ( int k = 0; k < lAmount; ++k )
                    {
                        BaseAudioEvent* vo = channel->liveEvents[ k ];
                        channelBuffer->mergeBuffers( vo->synthesize( buffer_size ), 0, 0, lAmp );
                    }
                }

                // apply the processing chains processors / modulators
                ProcessingChain* chain = channel->processingChain;
                std::vector<BaseProcessor*> processors = chain->getActiveProcessors();

                for ( int k = 0; k < processors.size(); k++ )
                {
                    BaseProcessor* processor = processors[ k ];
                    bool canCacheProcessor   = processor->isCacheable();

                    // only apply processor when we're not caching or cannot cache its output
                    if ( !isCached || !canCacheProcessor )
                    {
                        // cannot cache this processor and we're caching ? write all contents
                        // of the channelBuffer into the channels cache
                        if ( mustCache && !canCacheProcessor )
                            mustCache = !writeChannelCache( channel, channelBuffer, cacheReadPos );

                        processors[ k ]->process( channelBuffer, channel->isMono );
                    }
                }

                // write cache if it didn't happen yet ;) (bus processors are (currently) non-cacheable)
                if ( mustCache )
                    mustCache = !writeChannelCache( channel, channelBuffer, cacheReadPos );

                // write the channel buffer into the combined output buffer, apply channel volume
                // note live events are always audible as their volume is relative to the instrument
                if ( channel->hasLiveEvents && channelVolume == 0.0 ) channelVolume = MAX_PHASE;
                inbuffer->mergeBuffers( channelBuffer, 0, 0, channelVolume );
            }

            // TODO: create bus processors for these ?

            // apply high pass filtering to prevent extreme low rumbling and nasty filter offsets
            hpf->process( inbuffer, buffer_size );

            // limit the audio to prevent clipping
            limiter->process( inbuffer, isMono );

            // write the accumulated buffers into the output buffer
            for ( i = 0, c = 0; i < buffer_size; i++, c += outputChannels )
            {
                for ( ci = 0; ci < outputChannels; ci++ )
                {
                    float sample = ( float ) inbuffer->getBufferForChannel( ci )[ i ] * volume; // apply master volume

                    // extreme limiting (still above the thresholds?)
                    if ( sample < -MAX_PHASE )
                        sample = -MAX_PHASE;

                    else if ( sample > +MAX_PHASE )
                        sample = +MAX_PHASE;

                    outbuffer[ c + ci ] = sample;
                }

                // update the buffer pointers and sequencer position
                if ( playing )
                {
                    if ( ++bufferPosition % bytes_per_tick == 0 )
                       handleSequencerPositionUpdate( android_GetTimestamp( p ));

                    if ( bufferPosition > max_buffer_position )
                        bufferPosition = min_buffer_position;
               }
            }
            // render the buffer in the audio hardware (unless we're bouncing as writing the output
            // makes it both unnecessarily audible and stalls this thread's execution
            if ( !bouncing )
                android_AudioOut( p, outbuffer, buffer_size * AudioEngineProps::OUTPUT_CHANNELS );

            // record the output if recording state is active
            if ( playing && ( recordOutput || recordFromDevice ))
            {
                if ( recordFromDevice ) // recording from device input ? > write the record buffer
                    DiskWriter::appendBuffer( recbuffer );
                else                    // recording global output ? > write the combined buffer
                    DiskWriter::appendBuffer( inbuffer );

                // exceeded maximum recording buffer amount ? > write current recording
                if ( DiskWriter::bufferFull() || haltRecording )
                {
                    int amountOfChannels = recordFromDevice ? AudioEngineProps::INPUT_CHANNELS : outputChannels;
                    DiskWriter::writeBufferToFile( AudioEngineProps::SAMPLE_RATE, amountOfChannels, true );

                    if ( !haltRecording )
                    {
                        DiskWriter::generateOutputBuffer(); // allocate new buffer for next iteration
                        ++recordingFileId;
                    }
                    else {
                        haltRecording = false;
                    }
                }
            }

            // tempo update queued ?
            if ( queuedTempo != tempo )
                handleTempoUpdate( queuedTempo, true );
        }
        android_CloseAudioDevice( p );

        // clear heap memory allocated before thread loop
        delete inbuffer;
        delete channelBuffer;
        delete limiter;
        delete hpf;
    }
TEST( BaseAudioEvent, MixBuffer )
{
    BaseAudioEvent* audioEvent = new BaseAudioEvent();

    int sampleLength = randomInt( 8, 24 );
    int sampleStart  = randomInt( 0, ( int )( sampleLength / 2 ));

    audioEvent->setSampleStart ( sampleStart );
    audioEvent->setSampleLength( sampleLength );

    int sampleEnd = audioEvent->getSampleEnd();

    AudioBuffer* buffer = fillAudioBuffer( new AudioBuffer( randomInt( 1, 4 ), sampleLength ));
    audioEvent->setBuffer( buffer, true );

    float volume = randomFloat();
    audioEvent->setVolume( volume );

    //std::cout << " ss: " << sampleStart << " se: " << sampleEnd << " sl: " << sampleLength << " ch: " << buffer->amountOfChannels;

    // create a temporary buffer to write output in, ensure it is smaller than the event buffer
    AudioBuffer* targetBuffer = new AudioBuffer( buffer->amountOfChannels, randomInt( 2, 4 ));
    int buffersToWrite        = targetBuffer->bufferSize;

    ASSERT_FALSE( bufferHasContent( targetBuffer ))
        << "expected target buffer to be silent after creation, but it has content";

    // test 1. mix without loopable range

    int maxBufferPos = sampleLength * 2; // use a "loop range" larger than the size of the events length
    int minBufferPos = randomInt( 0, maxBufferPos / 2 );
    int bufferPos    = randomInt( minBufferPos, maxBufferPos - 1 );
    bool loopStarted = false;
    int loopOffset   = 0;

    // if the random bufferPosition wasn't within the events sampleStart and sampleEnd range, we expect no content

    bool expectContent = ( bufferPos >= sampleStart && bufferPos <= sampleEnd ) ||
                         (( bufferPos + buffersToWrite ) >= sampleStart && ( bufferPos + buffersToWrite ) <= sampleEnd );

    //std::cout << " expected content: " << expectContent << " for buffer size: " << buffersToWrite;
    //std::cout << " min: " << minBufferPos << " max: " << maxBufferPos << " cur: " << bufferPos;

    audioEvent->mixBuffer( targetBuffer, bufferPos, minBufferPos, maxBufferPos, loopStarted, loopOffset, false );

    // validate buffer contents after mixing

    if ( expectContent )
    {
        for ( int c = 0, ca = targetBuffer->amountOfChannels; c < ca; ++c )
        {
            SAMPLE_TYPE* buffer       = targetBuffer->getBufferForChannel( c );
            SAMPLE_TYPE* sourceBuffer = audioEvent->getBuffer()->getBufferForChannel( c );
            SAMPLE_TYPE expectedSample;

            for ( int i = 0; i < buffersToWrite; ++i )
            {
                int r = i + bufferPos; // read pointer for the source buffer

                if ( r >= maxBufferPos && !loopStarted )
                    r -= ( maxBufferPos - minBufferPos );

                if ( r >= sampleStart && r <= sampleEnd )
                {
                    r -= sampleStart; // substract audioEvent start position
                    expectedSample = sourceBuffer[ r ] * volume;
                }
                else {
                    expectedSample = 0.0;
                }
                SAMPLE_TYPE sample = buffer[ i ];

                EXPECT_EQ( expectedSample, sample )
                    << "expected mixed sample at " << i << " to be equal to the calculated expected sample at read offset " << r;
            }
        }

    }
    else {
        ASSERT_FALSE( bufferHasContent( targetBuffer ))
            << "expected target buffer to contain no content after mixing for an out-of-range buffer position";
    }

    // test 2. mixing within a loopable range (implying sequencer is starting a loop)

    targetBuffer->silenceBuffers();

    ASSERT_FALSE( bufferHasContent( targetBuffer ))
        << "expected target buffer to be silent after silencing, but it still has content";

    bufferPos     = randomInt( minBufferPos, maxBufferPos - 1 );
    loopStarted   = true;
    loopOffset    = ( maxBufferPos - bufferPos ) + 1;

    // pre calculate at which buffer iterator the looping will commence
    // loopStartIteratorPosition describes at which sequencer position the loop starts
    // loopStartWritePointer describes at which position in the targetBuffer the loop is written to
    // amountOfLoopedWrites is the amount of samples written in the loop
    // loopStartReadPointer describes at which position the samples from the source audioEvent will be read when loop starts
    // loopStartReadPointerEnd describes the last position the samples from the source audioEvent will be read for the amount of loop writes

    int loopStartIteratorPosition = maxBufferPos + 1;
    int loopStartWritePointer     = loopOffset;
    int loopStartReadPointer      = minBufferPos;
    int amountOfLoopedWrites      = ( bufferPos + buffersToWrite ) - loopStartIteratorPosition;
    int loopStartReadPointerEnd   = ( loopStartReadPointer + amountOfLoopedWrites ) - 1;

    expectContent = ( bufferPos >= sampleStart && bufferPos <= sampleEnd ) ||
                    (( bufferPos + buffersToWrite ) >= sampleStart && ( bufferPos + buffersToWrite ) <= sampleEnd ) ||
                    ( loopStartIteratorPosition > maxBufferPos && (
                        ( loopStartReadPointer >= sampleStart && loopStartReadPointer <= sampleEnd ) ||
                        ( loopStartReadPointerEnd >= sampleStart && loopStartReadPointerEnd <= sampleEnd )));

    audioEvent->mixBuffer( targetBuffer, bufferPos, minBufferPos, maxBufferPos, loopStarted, loopOffset, false );

    //std::cout << " expected content: " << expectContent << " for buffer size: " << buffersToWrite;
    //std::cout << " min: " << minBufferPos << " max: " << maxBufferPos << " cur: " << bufferPos << " loop offset: " << loopOffset;

    if ( expectContent )
    {
        for ( int c = 0, ca = targetBuffer->amountOfChannels; c < ca; ++c )
        {
            SAMPLE_TYPE* buffer       = targetBuffer->getBufferForChannel( c );
            SAMPLE_TYPE* sourceBuffer = audioEvent->getBuffer()->getBufferForChannel( c );

            for ( int i = 0; i < buffersToWrite; ++i )
            {
                SAMPLE_TYPE expectedSample = 0.0;

                int r = i + bufferPos; // read pointer for the source buffer

                if ( i >= loopOffset )
                    r = minBufferPos + ( i - loopOffset );

                if ( r >= sampleStart && r <= sampleEnd )
                {
                    r -= sampleStart; // substract audioEvent start position
                    expectedSample = sourceBuffer[ r ] * volume;
                }
                SAMPLE_TYPE sample = buffer[ i ];

                EXPECT_EQ( expectedSample, sample )
                    << "expected mixed sample at " << i << " to be equal to the calculated expected sample at read "
                    << "offset " << r << " ( sanitized from " << ( i + bufferPos ) << " )";
            }
        }
    }
    else {
        ASSERT_FALSE( bufferHasContent( targetBuffer ))
            << "expected output buffer to contain no content after mixing for an out-of-range buffer position";
    }
    delete audioEvent;
    delete targetBuffer;
    delete buffer;
}