Beispiel #1
0
        /// <summary>
        /// Creates a new <see cref="Waveform"/> containing a specific number of data points by selecting the average value of each sampled group.
        /// </summary>
        /// <param name="pointCount">The number of points the resulting <see cref="Waveform"/> should contain.</param>
        /// <param name="cancellationToken">The token to cancel the task.</param>
        /// <returns>An async task for the generation of the <see cref="Waveform"/>.</returns>
        public async Task <Waveform> GenerateResampledAsync(int pointCount, CancellationToken cancellationToken = default)
        {
            if (pointCount < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(pointCount));
            }

            if (readTask == null)
            {
                return(new Waveform());
            }

            await readTask;

            return(await Task.Run(() =>
            {
                var generatedPoints = new List <WaveformPoint>();
                float pointsPerGeneratedPoint = (float)points.Count / pointCount;

                for (float i = 0; i < points.Count; i += pointsPerGeneratedPoint)
                {
                    if (cancellationToken.IsCancellationRequested)
                    {
                        break;
                    }

                    int startIndex = (int)i;
                    int endIndex = (int)Math.Min(points.Count, Math.Ceiling(i + pointsPerGeneratedPoint));

                    var point = new WaveformPoint(channels);
                    for (int j = startIndex; j < endIndex; j++)
                    {
                        for (int c = 0; c < channels; c++)
                        {
                            point.Amplitude[c] += points[j].Amplitude[c];
                        }
                        point.LowIntensity += points[j].LowIntensity;
                        point.MidIntensity += points[j].MidIntensity;
                        point.HighIntensity += points[j].HighIntensity;
                    }

                    // Means
                    for (int c = 0; c < channels; c++)
                    {
                        point.Amplitude[c] /= endIndex - startIndex;
                    }
                    point.LowIntensity /= endIndex - startIndex;
                    point.MidIntensity /= endIndex - startIndex;
                    point.HighIntensity /= endIndex - startIndex;

                    generatedPoints.Add(point);
                }

                return new Waveform
                {
                    points = generatedPoints,
                    channels = channels
                };
            }, cancellationToken));
        }
Beispiel #2
0
        /// <summary>
        /// Constructs a new <see cref="Waveform"/> from provided audio data.
        /// </summary>
        /// <param name="data">The sample data stream. If null, an empty waveform is constructed.</param>
        public Waveform(Stream data = null)
        {
            if (data == null)
            {
                return;
            }

            readTask = Task.Run(() =>
            {
                var procs = new DataStreamFileProcedures(data);

                int decodeStream = Bass.CreateStream(StreamSystem.NoBuffer, BassFlags.Decode | BassFlags.Float, procs.BassProcedures, IntPtr.Zero);

                ChannelInfo info;
                Bass.ChannelGetInfo(decodeStream, out info);

                long length = Bass.ChannelGetLength(decodeStream);

                // Each "point" is generated from a number of samples, each sample contains a number of channels
                int sampleDataPerPoint = (int)(info.Frequency * resolution * info.Channels);
                points.Capacity        = (int)(length / sampleDataPerPoint);

                int bytesPerIteration = sampleDataPerPoint * points_per_iteration;
                var dataBuffer        = new float[bytesPerIteration / bytes_per_sample];

                while (length > 0)
                {
                    length          = Bass.ChannelGetData(decodeStream, dataBuffer, bytesPerIteration);
                    int samplesRead = (int)(length / bytes_per_sample);

                    // Process a sequence of samples for each point
                    for (int i = 0; i < samplesRead; i += sampleDataPerPoint)
                    {
                        // Process each sample in the sequence
                        var point = new WaveformPoint(info.Channels);
                        for (int j = i; j < i + sampleDataPerPoint; j += info.Channels)
                        {
                            // Process each channel in the sample
                            for (int c = 0; c < info.Channels; c++)
                            {
                                point.Amplitude[c] = Math.Max(point.Amplitude[c], Math.Abs(dataBuffer[j + c]));
                            }
                        }

                        for (int c = 0; c < info.Channels; c++)
                        {
                            point.Amplitude[c] = Math.Min(1, point.Amplitude[c]);
                        }

                        points.Add(point);
                    }
                }

                channels = info.Channels;
            }, cancelSource.Token);
        }
Beispiel #3
0
        /// <summary>
        /// Constructs a new <see cref="Waveform"/> from provided audio data.
        /// </summary>
        /// <param name="data">The sample data stream. If null, an empty waveform is constructed.</param>
        public Waveform(Stream data)
        {
            if (data == null)
            {
                return;
            }

            readTask = Task.Run(() =>
            {
                // for the time being, this code cannot run if there is no bass device available.
                if (Bass.CurrentDevice <= 0)
                {
                    return;
                }

                procedures = CreateDataStreamFileProcedures(data);

                if (!RuntimeInfo.SupportsIL)
                {
                    pinnedProcedures = GCHandle.Alloc(procedures, GCHandleType.Pinned);
                }

                int decodeStream = Bass.CreateStream(StreamSystem.NoBuffer, BassFlags.Decode | BassFlags.Float, procedures.BassProcedures,
                                                     RuntimeInfo.SupportsIL ? IntPtr.Zero : GCHandle.ToIntPtr(pinnedProcedures));

                Bass.ChannelGetInfo(decodeStream, out ChannelInfo info);

                long length = Bass.ChannelGetLength(decodeStream);

                // Each "point" is generated from a number of samples, each sample contains a number of channels
                int samplesPerPoint = (int)(info.Frequency * resolution * info.Channels);

                int bytesPerPoint = samplesPerPoint * bytes_per_sample;

                points.Capacity = (int)(length / bytesPerPoint);

                // Each iteration pulls in several samples
                int bytesPerIteration = bytesPerPoint * points_per_iteration;
                var sampleBuffer      = new float[bytesPerIteration / bytes_per_sample];

                // Read sample data
                while (length > 0)
                {
                    length          = Bass.ChannelGetData(decodeStream, sampleBuffer, bytesPerIteration);
                    int samplesRead = (int)(length / bytes_per_sample);

                    // Each point is composed of multiple samples
                    for (int i = 0; i < samplesRead; i += samplesPerPoint)
                    {
                        // Channels are interleaved in the sample data (data[0] -> channel0, data[1] -> channel1, data[2] -> channel0, etc)
                        // samplesPerPoint assumes this interleaving behaviour
                        var point = new WaveformPoint(info.Channels);
                        for (int j = i; j < i + samplesPerPoint; j += info.Channels)
                        {
                            // Find the maximum amplitude for each channel in the point
                            for (int c = 0; c < info.Channels; c++)
                            {
                                point.Amplitude[c] = Math.Max(point.Amplitude[c], Math.Abs(sampleBuffer[j + c]));
                            }
                        }

                        // BASS may provide unclipped samples, so clip them ourselves
                        for (int c = 0; c < info.Channels; c++)
                        {
                            point.Amplitude[c] = Math.Min(1, point.Amplitude[c]);
                        }

                        points.Add(point);
                    }
                }

                Bass.ChannelSetPosition(decodeStream, 0);
                length = Bass.ChannelGetLength(decodeStream);

                // Read FFT data
                float[] bins     = new float[fft_bins];
                int currentPoint = 0;
                long currentByte = 0;
                while (length > 0)
                {
                    length       = Bass.ChannelGetData(decodeStream, bins, (int)fft_samples);
                    currentByte += length;

                    double lowIntensity  = computeIntensity(info, bins, low_min, mid_min);
                    double midIntensity  = computeIntensity(info, bins, mid_min, high_min);
                    double highIntensity = computeIntensity(info, bins, high_min, high_max);

                    // In general, the FFT function will read more data than the amount of data we have in one point
                    // so we'll be setting intensities for all points whose data fits into the amount read by the FFT
                    // We know that each data point required sampleDataPerPoint amount of data
                    for (; currentPoint < points.Count && currentPoint * bytesPerPoint < currentByte; currentPoint++)
                    {
                        points[currentPoint].LowIntensity  = lowIntensity;
                        points[currentPoint].MidIntensity  = midIntensity;
                        points[currentPoint].HighIntensity = highIntensity;
                    }
                }

                channels = info.Channels;
            }, cancelSource.Token);
        }
Beispiel #4
0
        /// <summary>
        /// Creates a new <see cref="Waveform"/> containing a specific number of data points by selecting the average value of each sampled group.
        /// </summary>
        /// <param name="pointCount">The number of points the resulting <see cref="Waveform"/> should contain.</param>
        /// <param name="cancellationToken">The token to cancel the task.</param>
        /// <returns>An async task for the generation of the <see cref="Waveform"/>.</returns>
        public async Task <Waveform> GenerateResampledAsync(int pointCount, CancellationToken cancellationToken = default)
        {
            if (pointCount < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(pointCount));
            }

            if (readTask == null)
            {
                return(new Waveform(null));
            }

            await readTask;

            return(await Task.Run(() =>
            {
                var generatedPoints = new List <WaveformPoint>();
                float pointsPerGeneratedPoint = (float)points.Count / pointCount;

                // Determines at which width (relative to the resolution) our smoothing filter is truncated.
                // Should not effect overall appearance much, except when the value is too small.
                // A gaussian contains almost all its mass within its first 3 standard deviations,
                // so a factor of 3 is a very good choice here.
                const int kernel_width_factor = 3;

                int kernelWidth = (int)(pointsPerGeneratedPoint *kernel_width_factor) + 1;

                float[] filter = new float[kernelWidth + 1];
                for (int i = 0; i < filter.Length; ++i)
                {
                    filter[i] = (float)Blur.EvalGaussian(i, pointsPerGeneratedPoint);
                }

                for (float i = 0; i < points.Count; i += pointsPerGeneratedPoint)
                {
                    if (cancellationToken.IsCancellationRequested)
                    {
                        break;
                    }

                    int startIndex = (int)i - kernelWidth;
                    int endIndex = (int)i + kernelWidth;

                    var point = new WaveformPoint(channels);
                    float totalWeight = 0;
                    for (int j = startIndex; j < endIndex; j++)
                    {
                        if (j < 0 || j >= points.Count)
                        {
                            continue;
                        }

                        float weight = filter[Math.Abs(j - startIndex - kernelWidth)];
                        totalWeight += weight;

                        for (int c = 0; c < channels; c++)
                        {
                            point.Amplitude[c] += weight *points[j].Amplitude[c];
                        }
                        point.LowIntensity += weight *points[j].LowIntensity;
                        point.MidIntensity += weight *points[j].MidIntensity;
                        point.HighIntensity += weight *points[j].HighIntensity;
                    }

                    // Means
                    for (int c = 0; c < channels; c++)
                    {
                        point.Amplitude[c] /= totalWeight;
                    }
                    point.LowIntensity /= totalWeight;
                    point.MidIntensity /= totalWeight;
                    point.HighIntensity /= totalWeight;

                    generatedPoints.Add(point);
                }

                return new Waveform(null)
                {
                    points = generatedPoints,
                    channels = channels
                };
            }, cancellationToken));
        }