/** 
 * Initializes PortAudio with a callback that fills output buffers mixed by the Kowalski Engine.
 * @param engine
 * @param sampleRate
 * @param numChannels
 * @param bufferSize
 * @return A Kowalski error code.
 */
kwlError kwlEngine_hostSpecificInitialize(kwlEngine* engine, int sampleRate, int numOutChannels, int numInChannels, int bufferSize)
{
    PaError err = Pa_Initialize();
    KWL_ASSERT(err == paNoError && "error initializing portaudio");
    
    /* Open an audio I/O stream. */
    err = Pa_OpenDefaultStream(&stream,
                               numInChannels,
                               numOutChannels,
                               paFloat32,   /* 32 bit floating point output. */
                               sampleRate,
                               bufferSize, /* frames per buffer, i.e. the number
                                            of sample frames that PortAudio will
                                            request from the callback. Many apps
                                            may want to use
                                            paFramesPerBufferUnspecified, which
                                            tells PortAudio to pick the best,
                                            possibly changing, buffer size.*/
                               (PaStreamCallback*)&paCallback,
                               engine->mixer);
    //printf("PortAudio error: %s\n", Pa_GetErrorText(err));
    KWL_ASSERT(err == paNoError);
    
    err = Pa_StartStream(stream);
    //printf("PortAudio error: %s\n", Pa_GetErrorText(err));
    KWL_ASSERT(err == paNoError);

	const PaStreamInfo* si = Pa_GetStreamInfo(stream);

    return KWL_NO_ERROR;
}
void kwlThreadJoin(kwlThread* thread)
{
    void* valuePtr;
    int rc = pthread_join(*thread, &valuePtr);
    switch (rc) {
        case EINVAL:
            //The implementation has detected that the value specified by thread does 
            //not refer to a joinable thread.
            KWL_ASSERT(0);
            break;
        case ESRCH:
            //No thread could be found corresponding to that specified by the given thread ID.
            KWL_ASSERT(0);
            break;
        case EDEADLK:
            //A deadlock was detected or the value of thread specifies the calling thread.    
            KWL_ASSERT(0);
            break;
        default:
            break;
    }
    
    KWL_ASSERT(rc == 0);
    
    debugThreadCount--;
}
int kwlEventInstance_getNumRemainingOutFrames(kwlEventInstance* event, float pitch)
{
    KWL_ASSERT(pitch > 0);
    if (isUnitPitch(pitch) != 0)
    {
        return event->currentPCMBufferSize - event->currentPCMFrameIndex;
    }
    else
    {
        int numRemaining = (int)((event->currentPCMBufferSize - event->currentPCMFrameIndex - event->pitchAccumulator) / pitch); 
        KWL_ASSERT(numRemaining >= 0 && "kwlEventInstance_getNumRemainingOutFrames: negative num frames");
        return numRemaining;
    }
}
kwlError kwlEventInstance_createFreeformEventFromFile(kwlEventInstance** event, const char* const audioFilePath, 
                                              kwlEventType type, int streamFromDisk)
{
    
    KWL_ASSERT(streamFromDisk == 0 && "stream flag not supported yet");
    
    /*try to load the audio file data*/
    kwlAudioData* audioData = (kwlAudioData*)KWL_MALLOC(sizeof(kwlAudioData), 
                                                        "freeform event audio data struct");
    kwlMemset(audioData, 0, sizeof(kwlAudioData));
    
    kwlError error = kwlLoadAudioFile(audioFilePath, audioData, KWL_CONVERT_TO_INT16_OR_FAIL);
    if (error != KWL_NO_ERROR)
    {
        KWL_FREE(audioData);
        return error;
    }
    
    if (type == KWL_POSITIONAL &&
        audioData->numChannels != 1)
    {
        kwlAudioData_free(audioData);
        KWL_FREE(audioData);
        return KWL_POSITIONAL_EVENT_MUST_BE_MONO;
    }
    
    return kwlEventInstance_createFreeformEventFromAudioData(event, audioData, type, "freeform event");
}
void kwlEventInstance_releaseFreeformEvent(kwlEventInstance* event)
{
    /*Free all data associated with the freeform event.*/
    kwlEventDefinition* eventDefinition = event->definition_engine;
    if (eventDefinition->streamAudioData != NULL)
    {
        KWL_ASSERT(0); /* double check this*/
        kwlAudioData_free(eventDefinition->streamAudioData);
        KWL_FREE(eventDefinition->streamAudioData);
    }
    else if (eventDefinition->sound != NULL)
    {
        /*TODO: this check could be more robust. it will cause a memory
         leak for freeform events created from files with the name
         "freeform buffer event"*/
        if (strcmp(eventDefinition->id, "freeform buffer event") == 0)
        {
            /*don't release audio data buffer for freeform buffer events.*/
            eventDefinition->sound->audioDataEntries[0]->bytes = NULL;
        }
        /* Free loaded audio data */
        kwlAudioData_free(eventDefinition->sound->audioDataEntries[0]);
        /* Free allocated audio data and sound structs */
        KWL_FREE(eventDefinition->sound->audioDataEntries[0]);
        KWL_FREE(eventDefinition->sound->audioDataEntries);
        KWL_FREE(eventDefinition->sound);
    }
    
    /* Finally, free the event instance and the event definition. */
    KWL_FREE(eventDefinition);
    KWL_FREE(event);
}
/**
 * Shuts down PortAudio.
 * @param engine
 */
kwlError kwlEngine_hostSpecificDeinitialize(kwlEngine* engine)
{
    
    PaError err = Pa_StopStream(stream);
    //printf("PortAudio error: %s\n", Pa_GetErrorText(err));
    KWL_ASSERT(err == paNoError);
    
    err = Pa_CloseStream(stream);
    //printf("PortAudio error: %s\n", Pa_GetErrorText(err));
    KWL_ASSERT(err == paNoError);
    
    err = Pa_Terminate();
    //printf("PortAudio error: %s\n", Pa_GetErrorText(err));
    KWL_ASSERT(err == paNoError);
    return KWL_NO_ERROR;
}
kwlSemaphore* kwlSemaphoreOpen(const char* const name)
{
    errno = 0;
    kwlSemaphore* ret = NULL;
    /*
     * Passing O_CREAT | O_EXCL generates an error
     * if a semaphore with the given name alread exits.
     */
    ret = sem_open(name, O_CREAT | O_EXCL, 0777, 0);
    
    if (ret == SEM_FAILED && errno == EEXIST)
    {
        sem_unlink(name);
        ret = sem_open(name, O_CREAT | O_EXCL, 0777, 0);
    }
    
    if (ret == SEM_FAILED)
    {
        switch (errno) {
            case EACCES:
                KWL_ASSERT(0);
                break;
            case EEXIST:
                KWL_ASSERT(0);
                break;
            case EINVAL:
                KWL_ASSERT(0);
                break;
            case EMFILE:
                KWL_ASSERT(0);
                break;
            case ENAMETOOLONG:
                KWL_ASSERT(0);
                break;
            case ENFILE:
                KWL_ASSERT(0);
                break;
            case ENOENT:
                KWL_ASSERT(0);
                break;
            case ENOMEM:
                KWL_ASSERT(0);
                break;
            default:
                break;
        }
    }
    
    KWL_ASSERT(ret != SEM_FAILED);
    debugSemaphoreCount++;
    return ret;
}
void kwlSemaphoreWait(kwlSemaphore* semaphore)
{
    int rc = sem_wait(semaphore);
    //printf("errno %d\n", errno);
    switch (errno) {
        case EBADF:
            KWL_ASSERT(0);
            break;
        case EAGAIN:
            KWL_ASSERT(0);
            break;
        case EINVAL:
            KWL_ASSERT(0);
            break;
        case ENOSYS:
            KWL_ASSERT(0);
            break;
        case EDEADLK:
            KWL_ASSERT(0);
            break;
        case EINTR:
            KWL_ASSERT(0);
            break;
        default:
            break;
    }        
    KWL_ASSERT(rc == 0);
}
void kwlSemaphorePost(kwlSemaphore* semaphore)
{
    int rc = sem_post(semaphore);
    
    if (rc != 0)
    {
        switch (errno) {
            case EINVAL:
                KWL_ASSERT(0);
                break;
            case ENOSYS:
                KWL_ASSERT(0);
                break;
            case EBADF:
                KWL_ASSERT(0 && "bad file descriptor/invalid semaphore");
            default:
                break;
        }            
    }
    KWL_ASSERT(rc == 0);
}
示例#10
0
int kwlDecoder_decodeNewBufferForEvent(kwlDecoder* decoder, kwlEventInstance* event)
{
    if (decoder->isDecoding)
    {
        printf("still decoding, missed buffer!\n");
        return 0;
    }

    KWL_ASSERT(decoder->numChannels > 0);
    
    event->currentPCMBuffer = decoder->currentDecodedBufferFront;
    event->currentPCMFrameIndex = event->currentPCMFrameIndex - event->currentPCMBufferSize;
    KWL_ASSERT(event->currentPCMFrameIndex >= 0); /*Could be greater than zero for events with non-unit pitch*/
    
    event->currentPCMBufferSize = decoder->currentDecodedBufferSizeInBytes / (2 * decoder->numChannels);
    event->currentNumChannels = decoder->numChannels;
    
    kwlSemaphorePost(decoder->semaphore);
    //printf("assigned front buffer %d\n", (int)decoder->currentDecodedBufferFront);
    return decoder->threadJoinRequested; //TODO: proper return value
}
void kwlSemaphoreDestroy(kwlSemaphore* semaphore, const char* const name)
{
    int rc = sem_unlink(name);
    
    KWL_ASSERT(rc == 0);
    
    rc = sem_close(semaphore);

    if (rc < 0)
    {
        switch (errno) {
            case EBUSY:
                KWL_ASSERT(0 && "EBUSY");
                break;
            default:
                break;
        }
    }
    
    KWL_ASSERT(rc == 0);
    debugSemaphoreCount--;

}
示例#12
0
void* kwlDecoder_decodingLoop(void* data)
{
    kwlDecoder* decoder = (kwlDecoder*)data;
    KWL_ASSERT(decoder->numChannels > 0);
    
    while (1)
    {   
        kwlSemaphoreWait(decoder->semaphore);

        if (decoder->threadJoinRequested != 0)
        {
            /*if stop is requested from outside the thread*/
            return NULL;
        }
        decoder->isDecoding = 1;
        int endOfData = decoder->decodeBuffer(decoder);
        
        kwlDecoder_swapBuffers(decoder);
        
        decoder->isDecoding = 0;
        
        if (endOfData != 0)
        {
            if (decoder->loop == 0)
            {
                decoder->threadJoinRequested = 1;
            }
            else
            {
                /*This is a looping decoder. Try to rewind the stream*/
                int rewindResult = decoder->rewind(decoder);
                if (rewindResult == 0)
                {
                    /*rewind failed, stop playing*/
                    decoder->threadJoinRequested = 1;
                }
            }
        }
        
        //printf("decoded buffer, result %d. waiting for semaphore...\n", decoder->threadJoinRequested);
        if (decoder->threadJoinRequested != 0)
        {
            /*if the end of the audio data was reached*/
            return NULL;
        }
    }
    
    return NULL;
}
示例#13
0
void kwlMixBus_addEvent(kwlMixBus* bus, kwlEventInstance* event)
{
    /*printf("adding event %d to bus %s\n", (int)event, bus->id);
      printf("    event list before:\n");
    kwlEventInstance* tempEvent = bus->eventList;
    while (tempEvent != NULL)
    {
        if (event == tempEvent)
        {
            printf("kwlMixBus_addEvent: event %s is already in mix bus %s\n", event->definition_mixer->id, bus->id);
        }
        tempEvent = tempEvent->nextEvent_mixer;
    }
    */
    
    KWL_ASSERT(event->nextEvent_mixer == NULL && "event to add already has event(s) attached to it");
        
    kwlEventInstance* eventi = bus->eventList;
    if (eventi == NULL)
    {
        /*The list is empty. Make the incoming event the first item.*/
        bus->eventList = event;
    }
    else
    {
        /*Find the last event in the list...*/
        while (eventi->nextEvent_mixer != NULL)
        {
            eventi = eventi->nextEvent_mixer;
        }
        /*...and attach the new event to it*/
        eventi->nextEvent_mixer = event;
    }
    
    /*printf("    event list after:\n");
    tempEvent = bus->eventList;
    while (tempEvent != NULL)
    {
        printf("        %d\n", (int)tempEvent);
        tempEvent = tempEvent->nextEvent_mixer;
    }*/

}
void kwlMutexLockRelease(kwlMutexLock* lock)
{
    int rc = pthread_mutex_unlock(lock);
    KWL_ASSERT(rc == 0);
}
示例#15
0
kwlError kwlWaveBank_loadAudioDataItems(kwlWaveBank* waveBank, kwlInputStream* stream)
{
    /*The input stream is assumed to be valid, so move the
      read position to the first audio data entry.*/
    kwlInputStream_reset(stream);
    kwlInputStream_skip(stream, KWL_WAVE_BANK_BINARY_FILE_IDENTIFIER_LENGTH);
    const int strLen = kwlInputStream_readIntBE(stream);
    kwlInputStream_skip(stream, strLen); 
    /*int numEntries = */kwlInputStream_readIntBE(stream);
    
    const int waveBankToLoadnumAudioDataEntries = waveBank->numAudioDataEntries;
    
    for (int i = 0; i < waveBankToLoadnumAudioDataEntries; i++)
    {
        char* const waveEntryIdi = kwlInputStream_readASCIIString(stream);
        
        kwlAudioData* matchingAudioData = NULL;
        int j;
        for (int j = 0; j < waveBankToLoadnumAudioDataEntries; j++)
        {
            kwlAudioData* entryj = &waveBank->audioDataItems[j];
            if (strcmp(entryj->filePath, waveEntryIdi) == 0)
            {
                matchingAudioData = entryj;
                break;
            }
        }
        //printf("    loading %s\n", waveEntryIdi);
        KWL_FREE(waveEntryIdi);
        
        const kwlAudioEncoding encoding = (kwlAudioEncoding)kwlInputStream_readIntBE(stream);
        const int streamFromDisk = kwlInputStream_readIntBE(stream);
        const int numChannels = kwlInputStream_readIntBE(stream);
        const int numBytes = kwlInputStream_readIntBE(stream);
        const int numFrames = numBytes / 2 * numChannels;
        
        /* Check that the audio meta data makes sense */
        if (numBytes <= 0)
        {
            KWL_ASSERT(0 && "the number of audio data bytes must be positive");
            return KWL_CORRUPT_BINARY_DATA;
        }
        if (matchingAudioData == NULL)
        {
            KWL_ASSERT(0 && "no matching wave bank entry");
            return KWL_CORRUPT_BINARY_DATA;
        }
        if (numChannels != 0 && numChannels != 1 && numChannels != 2)
        {
            KWL_ASSERT(0 && "invalid number of channels");
            return KWL_CORRUPT_BINARY_DATA;
        }
        
        /*free any old data*/
        kwlAudioData_free(matchingAudioData);
        
        /*Store audio meta data.*/
        matchingAudioData->numFrames = numFrames;
        matchingAudioData->numChannels = numChannels;
        matchingAudioData->numBytes = numBytes;
        matchingAudioData->encoding = (kwlAudioEncoding)encoding;
        matchingAudioData->streamFromDisk = streamFromDisk;
        matchingAudioData->isLoaded = 1;
        matchingAudioData->bytes = NULL;
        
        if (streamFromDisk == 0)
        {
            /*This entry should not be streamed, so allocate audio data up front.*/
            matchingAudioData->bytes = KWL_MALLOC(numBytes, "kwlEngine_loadWaveBank");
            
            int bytesRead = kwlInputStream_read(stream, 
                                                (signed char*)matchingAudioData->bytes, 
                                                numBytes);
            if (bytesRead != numBytes)
            {
                KWL_ASSERT(0 && "error reading wave bank audio data bytes");
                return KWL_CORRUPT_BINARY_DATA;
            }
        }
        else
        {
            /*Store the offset into the wave bank binary files for streaming entries.*/
            matchingAudioData->fileOffset = kwlInputStream_tell(stream);
            kwlInputStream_skip(stream, numBytes);
        }
    }
    
    waveBank->isLoaded = 1;
    return KWL_NO_ERROR;
}
示例#16
0
kwlError kwlWaveBank_verifyWaveBankBinary(kwlEngine* engine, 
                                          const char* const waveBankPath,
                                          kwlWaveBank** waveBank)
{
    /*Open the file...*/
    kwlInputStream stream;
    kwlError result = kwlInputStream_initWithFile(&stream, waveBankPath);
    if (result != KWL_NO_ERROR)
    {
        kwlInputStream_close(&stream);
        return result;
    }
    /*... and check the wave bank file identifier.*/
    int i;
    for (i = 0; i < KWL_WAVE_BANK_BINARY_FILE_IDENTIFIER_LENGTH; i++)
    {
        const char identifierChari = kwlInputStream_readChar(&stream);
        if (identifierChari != KWL_WAVE_BANK_BINARY_FILE_IDENTIFIER[i])
        {
            /* Not the file identifier we expected. */
            kwlInputStream_close(&stream);
            return KWL_UNKNOWN_FILE_FORMAT;
        }
    }
    
    /*Read the ID from the wave bank binary file and find a matching wave bank struct.*/
    const char* waveBankToLoadId = kwlInputStream_readASCIIString(&stream);
    const int waveBankToLoadnumAudioDataEntries = kwlInputStream_readIntBE(&stream);
    const int numWaveBanks = engine->engineData.numWaveBanks;
    kwlWaveBank* matchingWaveBank = NULL;
    for (i = 0; i < numWaveBanks; i++)
    {
        if (strcmp(waveBankToLoadId, engine->engineData.waveBanks[i].id) == 0)
        {
            matchingWaveBank = &engine->engineData.waveBanks[i];
        }
    }
    
    KWL_FREE((void*)waveBankToLoadId);
    if (matchingWaveBank == NULL)
    {
        /*No matching bank was found. Close the file stream and return an error.*/
        kwlInputStream_close(&stream);
        return KWL_NO_MATCHING_WAVE_BANK;
    }
    else if (waveBankToLoadnumAudioDataEntries != matchingWaveBank->numAudioDataEntries)
    {
        /*A matching wave bank was found but the number of audio data entries
         does not match the binary wave bank data.*/
        kwlInputStream_close(&stream);
        return KWL_WAVE_BANK_ENTRY_MISMATCH;
    }
    else if (matchingWaveBank->isLoaded != 0)
    {
        /*The wave bank is already loaded, just set the handle and do nothing.*/
        *waveBank = matchingWaveBank;
        kwlInputStream_close(&stream);
        return KWL_NO_ERROR;
    }
    
    /*Store the path the wave bank was loaded from (used when streaming from disk).*/
    const int pathLen = strlen(waveBankPath);
    matchingWaveBank->waveBankFilePath = (char*)KWL_MALLOC((pathLen + 1) * sizeof(char), "wave bank path string");
    strcpy(matchingWaveBank->waveBankFilePath, waveBankPath);
    
    /*Make sure that the entries of the wave bank to load and the wave bank struct line up.*/
    for (i = 0; i < waveBankToLoadnumAudioDataEntries; i++)
    {
        const char* filePathi = kwlInputStream_readASCIIString(&stream);
        
        int matchingEntryIndex = -1;
        int j = 0;
        for (j = 0; j < waveBankToLoadnumAudioDataEntries; j++)
        {
            if (strcmp(matchingWaveBank->audioDataItems[j].filePath, filePathi) == 0)
            {
                matchingEntryIndex = j;
                break;
            }
        }
        
        KWL_FREE((void*)filePathi);
        
        if (matchingEntryIndex < 0)
        {
            /* This wave bank entry has no corresponding waveform slot. Abort loading.*/
            kwlInputStream_close(&stream);
            return KWL_WAVE_BANK_ENTRY_MISMATCH;
        }
        
        /*skip to the next wave data entry*/
        /*const int encoding = */kwlInputStream_readIntBE(&stream);
        /*const int streamFromDisk = */kwlInputStream_readIntBE(&stream);
        const int numChannels = kwlInputStream_readIntBE(&stream);
        KWL_ASSERT((numChannels == 0 || numChannels == 1 || numChannels == 2) && "invalid num channels");
        const int numBytes = kwlInputStream_readIntBE(&stream);
        KWL_ASSERT(numBytes > 0);
        kwlInputStream_skip(&stream, numBytes);
    }
    
    /* Reading went well. */
    kwlInputStream_close(&stream);
    *waveBank = matchingWaveBank;
    return KWL_NO_ERROR;
}
示例#17
0
kwlError kwlDecoder_init(kwlDecoder* decoder, kwlEventInstance* event)
{
    kwlAudioData* audioData = event->definition_engine->streamAudioData;
    /*reset the decoder struct.*/
    kwlMemset(decoder, 0, sizeof(kwlDecoder));
    
    decoder->loop = event->definition_engine->loopIfStreaming;
    
    /*
     * Hook up audio data, that could either be from a file or from an already loaded buffer
     */
    if (audioData->streamFromDisk != 0)
    {
        KWL_ASSERT(audioData->fileOffset >= 0);
        kwlError result = kwlInputStream_initWithFileRegion(&decoder->audioDataStream,
                                                            audioData->waveBank->waveBankFilePath,
                                                            audioData->fileOffset,
                                                            audioData->numBytes);
        KWL_ASSERT(result == KWL_NO_ERROR);
    }
    else
    {   
        kwlInputStream_initWithBuffer(&decoder->audioDataStream,
                                      audioData->bytes,
                                      0, 
                                      audioData->numBytes);
    }
    
    /*
     * do codec specific initialization.
     */
    kwlError result = KWL_UNSUPPORTED_ENCODING;
    
    if (audioData->encoding == KWL_ENCODING_IMA_ADPCM)
    {
        result = kwlInitDecoderIMAADPCM(decoder);
    }
    else if (audioData->encoding == KWL_ENCODING_VORBIS)
    {
        result = kwlInitDecoderOggVorbis(decoder);
    }
    else if (kwlAudioData_isLinearPCM(audioData))
    {
        result = kwlInitDecoderPCM(decoder);
    }
    #ifdef KWL_IPHONE
    else if (audioData->encoding == KWL_ENCODING_UNKNOWN)
    {
        /*try the iphone decoder*/
        result = kwlInitDecoderIPhone(decoder);
    }
    #endif /*KWL_IPHONE*/
    
    if (result != KWL_NO_ERROR)
    {
        decoder->deinit(decoder);
        return result;
    }
    
    KWL_ASSERT(decoder->numChannels > 0);
    
    decoder->currentDecodedBuffer = 
        (short*)KWL_MALLOC(sizeof(short) * decoder->maxDecodedBufferSize, "decoder back buffer");
    decoder->currentDecodedBufferFront = 
        (short*)KWL_MALLOC(sizeof(short) * decoder->maxDecodedBufferSize, "decoder front buffer");
    decoder->currentDecodedBufferSizeInBytes = 0;
    
    /*
     * Before starting the decoding thread, call the decode function 
     * synchronously to get the first buffer of decoded samples.
     */
    int decodingResult = decoder->decodeBuffer(decoder);
    
    kwlDecoder_swapBuffers(decoder);
    
    /*TODO: check the decoding result. the event could be done playing here.*/
    event->currentPCMFrameIndex = 0;
    event->currentPCMBuffer = decoder->currentDecodedBufferFront;
    event->currentPCMBufferSize = decoder->currentDecodedBufferSizeInBytes / (2 * decoder->numChannels);
    
    event->currentNumChannels = decoder->numChannels;
    
    
    /*Create a semaphore with a unique name based on the addess of the decoder*/
    sprintf(decoder->semaphoreName, "decoder%d", (int)decoder);
    decoder->semaphore = kwlSemaphoreOpen(decoder->semaphoreName);
    kwlSemaphorePost(decoder->semaphore);
    
    /*Fire up the decoding thread.*/
    kwlThreadCreate(&decoder->decodingThread, kwlDecoder_decodingLoop, decoder);
    
    return result;
}
int kwlEventInstance_render(kwlEventInstance* event, 
                    float* outBuffer,
                    const int numOutChannels,
                    const int numFrames,
                    const float accumulatedBusPitch)
{
    /* initial playback logic checks */
    {
        if (event->playbackState == KWL_STOP_AND_UNLOAD_REQUESTED)
        {
            return 1;
        }
        else if (event->playbackState == KWL_STOP_REQUESTED)
        {
            /*if the event has been requested to stop and if the
              sound (if any) permits stopping mid-buffer, return 1 to indicate
              that the event should be removed from the mixer.*/
            int allowsImmediateStop = 
                event->definition_mixer->sound != NULL ? 
                event->definition_mixer->sound->deferStop == 0 : 1;
            if (allowsImmediateStop != 0)
            {
                return 1;
            }
        }
        else if (event->playbackState == KWL_PLAY_LAST_BUFFER_AND_STOP_REQUESTED)
        {
            int allowsImmediateStop = 
                event->definition_mixer->sound != NULL ? 
                event->definition_mixer->sound->deferStop == 0 : 1;
            if (allowsImmediateStop != 0)
            {
                kwlSound_pickNextBufferForEvent(event->definition_mixer->sound, 
                                                event, 0);
            }
        }        
        else if (event->isPaused != 0)
        {
            kwlClearFloatBuffer(outBuffer, numFrames * numOutChannels);
            return 0;
        }
        else if (event->pitch.valueMixer < PITCH_EPSILON)
        {
            /*Don't allow too low pitch values.*/
            event->pitch.valueMixer = PITCH_EPSILON;
        }
    }
    
    /*Update fade progress*/
    {
        event->fadeGain += event->fadeGainIncrPerFrame * numFrames;
        if (event->fadeGain > 1.0f)
        {
            event->fadeGain = 1.0f;
        }
        else if (event->fadeGain < 0.0f)
        {
            event->fadeGain = 0.0f;
            /** The fade out just finished, signal that the event should be stopped.*/
            return 1;
        }
    }
    
    /*gets set to a non-zero value when the out buffer has been completely filled*/
    int endOfOutBufferReached = 0;
    /*the index of the current frame in the out buffer*/
    int outFrameIdx = 0;
    /*gets set to a non-zero value when the end of the current source buffer is reached*/
    int endOfSourceBufferReached = 0;
    
    /* 
       During this loop, the output buffer is filled with samples from 
       either a sound or a decoder.     
     */
    int donePlaying = 0;
    while (!endOfOutBufferReached)
    {
        /*if the event pitch is close enough to 1, pitch shifting is not applied.*/
        float effectivePitch = event->pitch.valueMixer * event->soundPitch * accumulatedBusPitch;
        if (effectivePitch < PITCH_EPSILON)
        {
            effectivePitch = PITCH_EPSILON;
        }
        
        int unitPitch = isUnitPitch(effectivePitch);
        
        /*Check if we have enough source frames to fill the output buffer. */
        int numOutFramesLeft = kwlEventInstance_getNumRemainingOutFrames(event, effectivePitch);
        int maxOutFrameIdx = numFrames;
        if (numOutFramesLeft < numFrames - outFrameIdx) 
        {
            maxOutFrameIdx = outFrameIdx + numOutFramesLeft + (unitPitch == 0 ? 1 : 0);/*TODO: ugly*/
            endOfSourceBufferReached = 1;
        }
        
        int outSampleIdx = 0;
        int srcSampleIdx = 0;
        float pitchAccumulator = 0;
        
        const float soundGain = event->definition_mixer->sound != NULL ? 
                                event->definition_mixer->sound->gain : 1.0f;
        
        /*This loop is where the actual mixing takes place.*/
        //printf("about to mix event buffer, event->currentPCMFrameIndex %d, ep %f\n", event->currentPCMFrameIndex, effectivePitch);
        int ch;
        for (ch = 0; ch < numOutChannels; ch++)
        { 
            outSampleIdx = outFrameIdx * numOutChannels + ch;
            const int maxOutSampleIdx = maxOutFrameIdx * numOutChannels + ch;
            srcSampleIdx = event->currentPCMFrameIndex * event->currentNumChannels + ch;
            pitchAccumulator = event->pitchAccumulator;
            
            if (unitPitch)
            {
                /*a simplified mix loop without pitch shifting*/
                kwlInt16ToFloatWithGain(event->currentPCMBuffer, 
                                        outBuffer,
                                        maxOutSampleIdx,                    
                                        &srcSampleIdx,
                                        event->currentNumChannels,
                                        &outSampleIdx, 
                                        numOutChannels, 
                                        soundGain);
                KWL_ASSERT(srcSampleIdx >= 0);
            }
            else
            {
                kwlInt16ToFloatWithGainAndPitch(event->currentPCMBuffer, 
                                                outBuffer,
                                                maxOutSampleIdx,                    
                                                &srcSampleIdx,
                                                event->currentNumChannels,
                                                &outSampleIdx, 
                                                numOutChannels, 
                                                soundGain,
                                                effectivePitch,
                                                &pitchAccumulator);
            }
            
            /*There are 4 possible combinations of input and output channel counts to consider:*/

            /*1. mono in, stereo out: copy left out to right out and break the loop after the first of two channels*/
            if (event->currentNumChannels == 1 && numOutChannels == 2)
            {
                int i;
                for (i = outFrameIdx * numOutChannels + ch; i < maxOutSampleIdx;)
                {
                    outBuffer[i + 1] = outBuffer[i];
                    i += 2;
                }
                break;
            }
                        
            /*2. stereo in, mono out*/
            /*If the input is stereo, its right channel gets ignored.*/
            
            /*3. mono in, mono out*/
            /*Requires no special handling.*/
            
            /*4. stereo in, stereo out*/
            /*Requires no special handling.*/
        }
        
        KWL_ASSERT(srcSampleIdx >= 0);
        outFrameIdx = outSampleIdx / numOutChannels;
        event->pitchAccumulator = pitchAccumulator;
        event->currentPCMFrameIndex = srcSampleIdx / event->currentNumChannels;
        
        /* Perform playback logic checks if the end of the current source buffer was reached.*/ 
        if (endOfSourceBufferReached != 0)
        {
            event->numBuffersPlayed++;
            donePlaying = 0;
            if (event->definition_mixer == NULL && event->decoder == NULL)
            {
                /*this is a PCM event created in code. we're done playing.*/
                donePlaying = 1;
            }
            else if (event->decoder != NULL)
            {
                /*decode the next buffer*/
                donePlaying = kwlDecoder_decodeNewBufferForEvent(event->decoder, event);
            }
            else
            {
                /*get another pcm buffer from the event's sound*/
                donePlaying = kwlSound_pickNextBufferForEvent(event->definition_mixer->sound, event, 0);
            }
            
            if (donePlaying != 0)
            {
                /*the event finished playing, fill the remainder of the out buffer with zeros*/
                kwlClearFloatBuffer(&outBuffer[outFrameIdx * numOutChannels], 
                                    (numFrames - outFrameIdx) * numOutChannels);
                
                break;
            }
            else
            {
                /*A new src buffer was just picked.*/
                endOfSourceBufferReached = 0;
            }
        }
        else
        {
            /* if we made it here the end of the out buffer must have been reached. */
            KWL_ASSERT(outFrameIdx == numFrames);
            endOfOutBufferReached = 1;
        }
    }
    
    /*Feed final event output through the event DSP unit, if any.*/
    kwlDSPUnit* dspUnit = (kwlDSPUnit*)event->dspUnit.valueMixer;
    if (dspUnit != NULL)
    {
        (*dspUnit->dspCallback)(outBuffer,
                                numOutChannels,
                                numFrames, 
                                dspUnit->data);
    }
    
    /* Apply per buffer gain with ramps if necessary*/
    {
        float effectiveGain[2] = 
        {
            event->fadeGain * event->gainLeft.valueMixer,
            event->fadeGain * event->gainRight.valueMixer
        };
        
        if (event->prevEffectiveGain[0] < 0.0f)
        {
            event->prevEffectiveGain[0] = effectiveGain[0];
            event->prevEffectiveGain[1] = effectiveGain[1];
        }
        
        kwlApplyGainRamp(outBuffer, 
                         numOutChannels, 
                         numFrames, 
                         event->prevEffectiveGain, 
                         effectiveGain);
        
        event->prevEffectiveGain[0] = effectiveGain[0];
        event->prevEffectiveGain[1] = effectiveGain[1];
    }
    
    return donePlaying;
}
void kwlMutexLockInit(kwlMutexLock* lock)
{
    int rc = pthread_mutex_init(lock, NULL);
    KWL_ASSERT(rc == 0);
}
kwlError kwlEventInstance_createFreeformEventFromAudioData(kwlEventInstance** event, kwlAudioData* audioData, kwlEventType type, const char* eventId)
{
    /*create the event. as opposed to a data driven event, a freeform event does
     not reference sounds and event definitions in the engine, but own its local data
     that is freed when the event is released.*/
    kwlEventInstance* createdEvent = (kwlEventInstance*)KWL_MALLOC(sizeof(kwlEventInstance), "freeform event instance");
    kwlEventInstance_init(createdEvent);
    
    kwlSound* sound = NULL;
    kwlAudioData* streamAudioData = NULL;
    
    /*create a sound if we loaded a PCM file.*/
    if (audioData->encoding == KWL_ENCODING_SIGNED_16BIT_PCM)
    {
        sound = (kwlSound*)KWL_MALLOC(sizeof(kwlSound), "freeform event: sound");
        kwlSound_init(sound);
        sound->audioDataEntries = (kwlAudioData**)KWL_MALLOC(sizeof(kwlAudioData*), 
                                                             "freeform event: sound audio data array list");
        sound->audioDataEntries[0] = audioData;
        sound->numAudioDataEntries = 1;
        sound->playbackMode = KWL_SEQUENTIAL;
        sound->playbackCount = 1;
        sound->deferStop = 0;
        sound->gain = 1.0f;
        sound->pitch = 1.0f;
        sound->pitchVariation = 0.0f;
        sound->gainVariation = 0.0f;
    }
    else
    {
        KWL_ASSERT(0 && "TODO: support creating non-pcm events");
    }
    
    /*create an event definition*/
    kwlEventDefinition* eventDefinition = 
    (kwlEventDefinition*)KWL_MALLOC(sizeof(kwlEventDefinition), 
                                    "freeform event definition");
    kwlEventDefinition_init(eventDefinition);
    
    eventDefinition->id = eventId;
    eventDefinition->instanceCount = 1;
    eventDefinition->isPositional = type == KWL_POSITIONAL ? 1 : 0;
    eventDefinition->gain = 1.0f;
    eventDefinition->pitch = 1.0f;
    eventDefinition->innerConeCosAngle = 1.0f;
    eventDefinition->outerConeCosAngle = -1.0f;
    eventDefinition->outerConeGain = 1.0f;
    eventDefinition->retriggerMode = KWL_RETRIGGER;
    eventDefinition->stealingMode = KWL_DONT_STEAL;
    eventDefinition->streamAudioData = streamAudioData;
    eventDefinition->sound = sound;
    eventDefinition->numReferencedWaveBanks = 0;
    eventDefinition->referencedWaveBanks = NULL;
    /*Set the mix bus to NULL. This is how the mixer knows this is a freeform event.
     TODO: solve this in some better way?*/
    eventDefinition->mixBus = NULL;
    
    createdEvent->definition_mixer = eventDefinition;
    createdEvent->definition_engine = eventDefinition;
    
    *event = createdEvent;
    
    return KWL_NO_ERROR;
}
kwlMutexLockAcquisitionResult kwlMutexLockTryAcquire(kwlMutexLock* lock)
{
    int rc = pthread_mutex_trylock(lock);
    KWL_ASSERT(rc == 0 || rc == EBUSY);
    return rc == EBUSY ? KWL_LOCK_BUSY : KWL_LOCK_ACQUIRED;
}
void kwlMutexLockAcquire(kwlMutexLock* lock)
{
    int rc = pthread_mutex_trylock(lock);
    KWL_ASSERT(rc == 0 || rc == EBUSY);
}
void kwlThreadCreate(kwlThread* thread, kwlThreadEntryPoint entryPoint, void* data)
{
    int rc = pthread_create(thread, NULL, entryPoint, data);
    KWL_ASSERT(rc == 0);
    debugThreadCount++;
}