void InitializePipeline(int trackCount) { Contract.Ensures(_analyzeAlbumPeaksBlock != null); Contract.Ensures(_broadcastAlbumResultsBlock != null); var propagateLinkOptions = new DataflowLinkOptions { PropagateCompletion = true }; // Calculate the album peak: var peakDetector = new PeakDetector(); _analyzeAlbumPeaksBlock = new TransformBlock <Tuple <float, float>, Tuple <float, float> >(input => { // Only need to submit the peak once per track: if (!float.IsNaN(input.Item2)) { return(Tuple.Create(input.Item1, input.Item2)); } peakDetector.Submit(input.Item1); return(Tuple.Create(peakDetector.Peak, input.Item2)); }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = trackCount }); // Calculate the album gain: var windowSelector = new WindowSelector(); var analyzeAlbumGainBlock = new TransformBlock <Tuple <float, float>, Tuple <float, float> >(input => { if (float.IsNaN(input.Item2)) { if (Interlocked.Decrement(ref _tracksStillProcessing) == 0) { return(Tuple.Create(input.Item1, windowSelector.GetResult())); } } else { windowSelector.Submit(input.Item2); } return(Tuple.Create(float.NaN, float.NaN)); }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = trackCount, SingleProducerConstrained = true }); _analyzeAlbumPeaksBlock.LinkTo(analyzeAlbumGainBlock, propagateLinkOptions); // Broadcast the results: _broadcastAlbumResultsBlock = new BroadcastBlock <Tuple <float, float> >(input => Tuple.Create(input.Item1, input.Item2)); analyzeAlbumGainBlock.LinkTo(DataflowBlock.NullTarget <Tuple <float, float> >(), result => float.IsNaN(result.Item2)); analyzeAlbumGainBlock.LinkTo(_broadcastAlbumResultsBlock, propagateLinkOptions, result => !float.IsNaN(result.Item2)); }
void InitializePipeline(int channels, int sampleRate) { Contract.Requires(channels > 0); Contract.Requires(sampleRate > 0); var boundedExecutionBlockOptions = new ExecutionDataflowBlockOptions { BoundedCapacity = _boundedCapacity, SingleProducerConstrained = true }; var unboundedExecutionBlockOptions = new ExecutionDataflowBlockOptions { SingleProducerConstrained = true }; var propogateLinkOptions = new DataflowLinkOptions { PropagateCompletion = true }; // First, resize the sample count collections to the desired window size: var sampleCountFilter = new SampleCountFilter(channels, (int)Math.Round(sampleRate * _rmsWindowTime)); _filterSampleCountBlock = new TransformManyBlock <SampleCollection, SampleCollection>(input => sampleCountFilter.Process(input), boundedExecutionBlockOptions); // Calculate the track peaks: var peakDetector = new PeakDetector(); var analyzeTrackPeaksBlock = new TransformBlock <SampleCollection, Tuple <SampleCollection, float> >(input => { peakDetector.Submit(input); return(Tuple.Create(input, peakDetector.Peak)); }, boundedExecutionBlockOptions); _filterSampleCountBlock.LinkTo(analyzeTrackPeaksBlock, propogateLinkOptions); // Down-convert certain samples rates (easy multiples) that aren't directly supported by ReplayGain: var sampleRateConverter = new SampleRateConverter(sampleRate); var convertSampleRateBlock = new TransformBlock <Tuple <SampleCollection, float>, Tuple <SampleCollection, float> >(input => { SampleCollection result = sampleRateConverter.Convert(input.Item1); return(Tuple.Create(result, input.Item2)); }, boundedExecutionBlockOptions); analyzeTrackPeaksBlock.LinkTo(convertSampleRateBlock, propogateLinkOptions); // Filter the samples: var butterworthFilter = new ButterworthFilter(sampleRate); var butterworthFilterBlock = new TransformBlock <Tuple <SampleCollection, float>, Tuple <SampleCollection, float> >(input => { butterworthFilter.Process(input.Item1); return(input); }, boundedExecutionBlockOptions); convertSampleRateBlock.LinkTo(butterworthFilterBlock, propogateLinkOptions); var yuleWalkFilter = new YuleWalkFilter(sampleRate); var yuleWalkFilterBlock = new TransformBlock <Tuple <SampleCollection, float>, Tuple <SampleCollection, float> >(input => { yuleWalkFilter.Process(input.Item1); return(input); }, boundedExecutionBlockOptions); butterworthFilterBlock.LinkTo(yuleWalkFilterBlock, propogateLinkOptions); // Calculate the root mean square for each filtered window: var calculateRmsBlock = new TransformBlock <Tuple <SampleCollection, float>, Tuple <SampleCollection, float, float> >(input => Tuple.Create(input.Item1, input.Item2, input.Item1.IsLast ? float.NaN : CalculateRms(input.Item1)), boundedExecutionBlockOptions); yuleWalkFilterBlock.LinkTo(calculateRmsBlock, propogateLinkOptions); // Free the sample collections once they are no longer needed: var freeSampleCollectionsBlock = new TransformBlock <Tuple <SampleCollection, float, float>, Tuple <float, float> >(input => { SampleCollectionFactory.Instance.Free(input.Item1); return(Tuple.Create(input.Item2, input.Item3)); }, boundedExecutionBlockOptions); calculateRmsBlock.LinkTo(freeSampleCollectionsBlock, propogateLinkOptions); // Broadcast the RMS values: var broadcastRmsBlock = new BroadcastBlock <Tuple <float, float> >(input => Tuple.Create(input.Item1, input.Item2)); freeSampleCollectionsBlock.LinkTo(broadcastRmsBlock, propogateLinkOptions); // Calculate the album gain: broadcastRmsBlock.LinkTo(_albumComponent.InputBlock); // Calculate the track gain: var windowSelector = new WindowSelector(); var analyzeTrackGainBlock = new TransformBlock <Tuple <float, float>, Tuple <float, float> >(input => { if (float.IsNaN(input.Item2)) { return(Tuple.Create(input.Item1, windowSelector.GetResult())); } windowSelector.Submit(input.Item2); return(Tuple.Create(input.Item1, float.NaN)); }, unboundedExecutionBlockOptions); broadcastRmsBlock.LinkTo(analyzeTrackGainBlock, propogateLinkOptions); // Join the track and album peak and gain values all together: var joinResultsBlock = new JoinBlock <Tuple <float, float>, Tuple <float, float> >(); analyzeTrackGainBlock.LinkTo(DataflowBlock.NullTarget <Tuple <float, float> >(), result => float.IsNaN(result.Item2)); analyzeTrackGainBlock.LinkTo(joinResultsBlock.Target1, propogateLinkOptions, result => !float.IsNaN(result.Item2)); _albumComponent.OutputBlock.LinkTo(joinResultsBlock.Target2, propogateLinkOptions); // Convert the results: var convertToMetadataBlock = new TransformBlock <Tuple <Tuple <float, float>, Tuple <float, float> >, MetadataDictionary>(input => { var result = new MetadataDictionary { ["TrackPeak"] = ConvertPeakToString(input.Item1.Item1), ["TrackGain"] = ConvertGainToString(input.Item1.Item2), ["AlbumPeak"] = ConvertPeakToString(input.Item2.Item1), ["AlbumGain"] = ConvertGainToString(input.Item2.Item2) }; return(result); }, unboundedExecutionBlockOptions); joinResultsBlock.LinkTo(convertToMetadataBlock, propogateLinkOptions); // Buffer the results: _bufferResultsBlock = new BufferBlock <MetadataDictionary>(); convertToMetadataBlock.LinkTo(_bufferResultsBlock, propogateLinkOptions); }