void OnAudioFilterRead(float [] data, int numChannels) { #if GAT_DEBUG if (_FiltersHandler.NbOfFilteredChannels != numChannels) { Debug.LogError("This player was setup for " + GATInfo.NbOfChannels + " channels output, current is " + numChannels + ". Disabling player."); _shouldDisable = true; return; } #endif BufferedSample sample; bool shouldRemove; int i; int dataLength; bool noData = false; dataLength = data.Length; //********************************************************************************************* //First, we check if any samples in the scheduled queue need to be moved to the playing queue shouldRemove = false; sample = _scheduledSamples.head.next; // caching the first item of the linked queue: thread safe iteration if (sample != null) { double nextBufferDSPTime = AudioSettings.dspTime + GATInfo.AudioBufferDuration; while (sample != null) { if (nextBufferDSPTime > sample.scheduledDspTime) //flag samples which need to be moved { sample.shouldBeRemoved = true; shouldRemove = true; sample.OffsetInBuffer = ( int )((sample.scheduledDspTime - AudioSettings.dspTime) * GATInfo.OutputSampleRate); } sample = sample.next; } if (shouldRemove) //move to playing queue { lock ( _scheduledSamples ) // make sure no sample is added from the main thread whilst removing { _scheduledSamples.TrimAndKeepDiscarded(_discardedSamples); } _playingSamples.Enqueue(_discardedSamples); // no need to lock on the playing queue: it is only accessed by the audio thread } } //************************************************************************* //Second, we check the PlayImmediate queue lock ( _samplesToEnqueue ) //make sure no play immediate sample gets added by the main thread whilst we concatenate the 2 queues { if (_samplesToEnqueue.head.next != null) { _playingSamples.Enqueue(_samplesToEnqueue); _samplesToEnqueue.Clear(); } } //*********************************************************** //Third, we mix the samples of the Playing queue sample = _playingSamples.head.next; if (onPlayerWillMix != null) { onPlayerWillMix(); } //Even if there is no samples to play, filters might add to the mix: if (sample == null) { noData = true; //Check tracks for (i = 0; i < _tracks.Count; i++) { if (ReferenceEquals(_tracks[i], null) == false) { if (_tracks[i].FXAndMixTo(data)) { noData = false; } } } //Check Master Filters if (_FiltersHandler.HasFilters) { if (_FiltersHandler.ApplyFilters(data, 0, dataLength, noData)) { noData = false; } } //Broadcast stream _audioThreadStreamProxy.BroadcastStream(data, 0, noData); //Stop there. if (onPlayerDidMix != null) { onPlayerDidMix(); } return; } shouldRemove = false; while (sample != null) { if (sample.MixNow(data) == true) { shouldRemove = true; } sample = sample.next; } //**************************************************** //Then, we remove samples which ended and clip the mix if needed if (shouldRemove) { _playingSamples.TrimAndReleaseDiscarded(); } //**************************************************** //Now, we check and mix tracks for (i = 0; i < _tracks.Count; i++) { if (ReferenceEquals(_tracks[i], null) == false) { _tracks[i].FXAndMixTo(data); } } //Filter the mix, includes a default gain filter which will clip if set so if (_FiltersHandler.HasFilters) { _FiltersHandler.ApplyFilters(data, 0, dataLength, false); } //**************************************************** //Finally, we fire the last callback _audioThreadStreamProxy.BroadcastStream(data, 0, false); if (onPlayerDidMix != null) { onPlayerDidMix(); } if (_releasePlaying) { _playingSamples.ReleaseAllAndPool(this); _releasePlaying = false; float deltaGain = 1f / (data.Length / numChannels); float gain = 1f; for (i = 0; i < data.Length; i += numChannels) { data[i] *= gain; data[i + 1] *= gain; gain -= deltaGain; } } }
// Called by GATPlayer. // Applies effects, and pan-mixes // to the audio buffer. public bool FXAndMixTo(float[] audioBuffer) { if (!_active) { return(false); } int i; bool isEmptyData = false; if (_hasData == false) //no sample overwrote the buffer, let's clear it { if (_bufferIsDirty) { _trackBuffer.Clear(); _bufferIsDirty = false; } isEmptyData = true; } if (_contributor != null) { isEmptyData = !(_contributor.MixToTrack(_trackBuffer, _trackNb)); } if (_filtersHandler.HasFilters) { if (_filtersHandler.ApplyFilters(_trackBuffer.ParentArray, _trackBuffer.MemOffset, GATInfo.AudioBufferSizePerChannel, isEmptyData)) { isEmptyData = false; } } if (isEmptyData) { _audioThreadStreamProxy.BroadcastStream(_trackBuffer.ParentArray, _trackBuffer.MemOffset, isEmptyData); return(false); } if (_shouldToggleMute) { if (_nextMute) { _trackBuffer.FadeOut(GATInfo.AudioBufferSizePerChannel); } else { _trackBuffer.FadeIn(GATInfo.AudioBufferSizePerChannel); _mute = false; } _shouldToggleMute = false; } _bufferIsDirty = true; _audioThreadStreamProxy.BroadcastStream(_trackBuffer.ParentArray, _trackBuffer.MemOffset, false); if (_mute) { return(false); } GATDynamicChannelGain channelGain; for (i = 0; i < _panInfo.channelGains.Count; i++) { channelGain = _panInfo.channelGains[i]; if (channelGain.ShouldInterpolate) { _trackBuffer.SmoothedGainMixToInterlaced(audioBuffer, 0, 0, GATInfo.AudioBufferSizePerChannel, channelGain); continue; } if (channelGain.Gain != 0f) { _trackBuffer.GainMixToInterlaced(audioBuffer, 0, 0, GATInfo.AudioBufferSizePerChannel, channelGain); } } if (_nextMute) //only set mute here to allow for mixing of faded data : elegant stop { _mute = true; } return(!isEmptyData); }