void kwlMixBus_render(kwlMixBus* mixBus, void* mixerVoid, //TODO: made this a void* to get things to compile. should be kwlMixer* int numOutChannels, int numFrames, float* busScratchBuffer, float* eventScratchBuffer, float* outBuffer, float accumulatedPitch, float accumulatedGainLeft, float accumulatedGainRight) { kwlMixer* mixer = (kwlMixer*)mixerVoid; const int numSubBuses = mixBus->numSubBuses; /* Render sub buses recursively. */ for (int i = 0; i < numSubBuses; i++) { kwlMixBus* busi = mixBus->subBuses[i]; kwlMixBus_render(busi, mixer, numOutChannels, numFrames, busScratchBuffer, eventScratchBuffer, outBuffer, busi->totalPitch.valueMixer * accumulatedPitch, busi->totalGainLeft.valueMixer * accumulatedGainLeft, busi->totalGainRight.valueMixer *accumulatedGainRight); } /* Mix the events of this bus into the out buffer. */ kwlClearFloatBuffer(busScratchBuffer, numOutChannels * numFrames); kwlEventInstance* event = mixBus->eventList; int numEventsInBus = 0; while (event != NULL) { int eventFinishedPlaying = kwlEventInstance_render(event, eventScratchBuffer, numOutChannels, numFrames, accumulatedPitch); /*mix event temp buffer into mixbus temp buffer*/ kwlMixFloatBuffer(eventScratchBuffer, busScratchBuffer, numOutChannels * numFrames); numEventsInBus++; /*Get the next event in the linked list and rearrange the list if the current event just stopped playing*/ if (eventFinishedPlaying) { kwlMixer_sendEventStoppedMessage(mixer, event); /* Risky business: we're manipulating the linked list we're iterating over. Cache the next event in the bus because nextEvent_mixer gets reset when removing the event from the bus. */ kwlEventInstance* nextEvent = event->nextEvent_mixer; kwlMixBus_removeEvent(mixBus, event); event = nextEvent; } else { event = event->nextEvent_mixer; } } /*Feed the bus output through the DSP unit if any.*/ if (mixBus->dspUnit.valueMixer) { kwlDSPUnit* dspUnit = (kwlDSPUnit*)mixBus->dspUnit.valueMixer; /*process and replace mixbus temp buffer*/ (*dspUnit->dspCallback)(busScratchBuffer, numOutChannels, numFrames, dspUnit->data); } /*if we have mixed any events for this bus, mix the result into the output buffer*/ if (numEventsInBus > 0) { /*... and then mix the bus buffer into the out buffer, applying the mix bus gain.*/ for (int ch = 0; ch < numOutChannels; ch++) { kwlMixFloatBufferWithGain(busScratchBuffer, outBuffer, numOutChannels * numFrames, ch, numOutChannels, //TODO: propagate gain values ch == 0 ? accumulatedGainLeft : accumulatedGainRight); } } }
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; }