/
AudioReader.cs
137 lines (123 loc) · 5.51 KB
/
AudioReader.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
using OpenCV.Net;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Bonsai.Audio
{
/// <summary>
/// Represents an operator that generates a sequence of buffered audio samples from an
/// uncompressed RIFF/WAV file.
/// </summary>
[DefaultProperty(nameof(FileName))]
[Description("Generates a sequence of buffered audio samples from an uncompressed RIFF/WAV file.")]
public class AudioReader : Source<Mat>
{
/// <summary>
/// Gets or sets the name of the WAV file.
/// </summary>
[Description("The name of the WAV file.")]
[FileNameFilter("WAV Files (*.wav;*.wave)|*.wav;*.wave|All Files|*.*")]
[Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)]
public string FileName { get; set; }
/// <summary>
/// Gets or sets the length of the sample buffer, in milliseconds.
/// </summary>
[Description("The length of the sample buffer, in milliseconds.")]
public double BufferLength { get; set; } = 10;
/// <summary>
/// Gets or sets the sample rate, in Hz, used to playback the sample buffers.
/// If it is zero, samples will be played at the rate specified in the
/// RIFF/WAV file header.
/// </summary>
[Description("The sample rate, in Hz, used to playback the sample buffers.")]
public int SampleRate { get; set; }
IEnumerable<Mat> CreateReader(double bufferLength)
{
using (var reader = new BinaryReader(new FileStream(FileName, FileMode.Open, FileAccess.Read)))
{
RiffHeader header;
RiffReader.ReadHeader(reader, out header);
var sampleRate = SampleRate;
if (sampleRate <= 0) sampleRate = (int)header.SampleRate;
var sampleCount = header.DataLength / header.BlockAlign;
var depth = header.BitsPerSample == 8 ? Depth.U8 : Depth.S16;
var bufferSize = (int)Math.Ceiling(sampleRate * bufferLength / 1000);
bufferSize = bufferSize <= 0 ? (int)sampleCount : bufferSize;
var sampleData = new byte[bufferSize * header.BlockAlign];
for (int i = 0; i < sampleCount / bufferSize; i++)
{
var bytesRead = reader.Read(sampleData, 0, sampleData.Length);
if (bytesRead < sampleData.Length) break;
var output = new Mat(header.Channels, bufferSize, depth, 1);
using (var bufferHeader = Mat.CreateMatHeader(sampleData, bufferSize, header.Channels, depth, 1))
{
CV.Transpose(bufferHeader, output);
}
yield return output;
}
}
}
/// <summary>
/// Generates a sequence of buffered audio samples from the specified WAV file.
/// </summary>
/// <returns>
/// A sequence of <see cref="Mat"/> objects representing audio sample
/// buffers of a fixed length. See <see cref="BufferLength"/>.
/// </returns>
public override IObservable<Mat> Generate()
{
return Observable.Create<Mat>((observer, cancellationToken) =>
{
return Task.Factory.StartNew(() =>
{
var i = 1L;
var bufferLength = BufferLength;
using (var reader = CreateReader(bufferLength).GetEnumerator())
using (var sampleSignal = new ManualResetEvent(false))
{
var stopwatch = new Stopwatch();
stopwatch.Start();
while (!cancellationToken.IsCancellationRequested)
{
if (!reader.MoveNext()) break;
observer.OnNext(reader.Current);
var sampleInterval = (int)(bufferLength * i - stopwatch.ElapsedMilliseconds);
if (sampleInterval > 0)
{
sampleSignal.WaitOne(sampleInterval);
}
i++;
}
observer.OnCompleted();
}
},
cancellationToken,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
});
}
/// <summary>
/// Generates a sequence of buffered audio samples from the specified WAV file, where
/// each new buffer is emitted only when an observable sequence emits a notification.
/// </summary>
/// <typeparam name="TSource">
/// The type of the elements in the <paramref name="source"/> sequence.
/// </typeparam>
/// <param name="source">
/// The sequence containing the notifications used for emitting audio buffers.
/// </param>
/// <returns>
/// A sequence of <see cref="Mat"/> objects representing audio sample
/// buffers of a fixed length. See <see cref="BufferLength"/>.
/// </returns>
public IObservable<Mat> Generate<TSource>(IObservable<TSource> source)
{
return source.Zip(CreateReader(BufferLength), (x, output) => output);
}
}
}