Beispiel #1
0
        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);
        }