forked from albertp007/FSound
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Plot.fs
265 lines (243 loc) · 10.5 KB
/
Plot.fs
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
//
// FSound - F# Sound Processing Library
// Copyright (c) 2016 by Albert Pang <[email protected]>
// All rights reserved.
//
// This file is a part of FSound
//
// FSound is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// FSound is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
namespace FSound
module Plot =
open XPlot.GoogleCharts
open XPlot.GoogleCharts.WpfExtensions
open XPlot.Plotly
open FSound.Utilities
open FSound.Filter
open System.Numerics
let Show(chart : GoogleChart) = chart.Show()
/// <summary>
/// Calculate the data points for the spectrum of the samples up to the
/// specified frequency
/// </summary>
/// <param name="toFreq">Spectrum data up to toFreq in Hz</param>
/// <param name="samples">Sequence of samples</param>
/// <returns>A pair of sequences representing the x and y data points
/// </returns>
let calcSpectrum (toFreq: float) samples =
let toFreq' = int toFreq
({ 0.0..(float toFreq' + 1.0) },
samples
|> fft
|> magnitudes
|> Seq.take (toFreq' + 1))
/// <summary>
/// Calculate the data points for the frequency response of a filter given
/// its feed-forward and feedback coefficients
/// </summary>
/// <param name="fs">Sampling frequency in Hz</param>
/// <param name="ffcoeff">Feed-forward coefficients</param>
/// <param name="fbcoeff">Feedback coefficients</param>
/// <param name="func">A conversion function from complex to float, the
/// result of which will be the y axis data points</param>
/// <param name="toFreq">Calculate up to toFreq</param>
/// <returns>A pair of sequences representing the x and y data points
/// </returns>
let calcFreqRes fs ffcoeff fbcoeff func (fromFreq: float) (toFreq: float) =
let H = transfer fs ffcoeff fbcoeff
{ fromFreq..toFreq }, Seq.map (H >> func) { fromFreq..toFreq }
/// <summary>
/// Calculate the data points for the magnitude response of a filter given
/// its feed-forward and feedback coefficients
/// </summary>
/// <param name="fs">Sampling frequency in Hz</param>
/// <param name="ffcoeff">Feed-forward coefficients</param>
/// <param name="fbcoeff">Feedback coefficients</param>
/// result of which will be the y axis data points</param>
/// <param name="toFreq">Calculate up to toFreq</param>
/// <returns>A pair of sequences representing the x and y data points
/// </returns>
let calcMagnitude fs ffcoeff fbcoeff toFreq =
calcFreqRes fs ffcoeff fbcoeff (Complex.Abs
>> log10
>> ((*) 20.0)) toFreq
/// <summary>
/// Calculate the data points for the phase response of a filter given
/// its feed-forward and feedback coefficients
/// </summary>
/// <param name="fs">Sampling frequency in Hz</param>
/// <param name="ffcoeff">Feed-forward coefficients</param>
/// <param name="fbcoeff">Feedback coefficients</param>
/// result of which will be the y axis data points</param>
/// <param name="toFreq">Calculate up to toFreq</param>
/// <returns>A pair of sequences representing the x and y data points
/// </returns>
let calcPhase fs ffcoeff fbcoeff toFreq =
calcFreqRes fs ffcoeff fbcoeff
(fun c -> 180.0 * (atan2 c.Imaginary c.Real) / System.Math.PI) toFreq
/// <summary>
/// Calculate the data points for the group delay of a filter
/// </summary>
/// <param name="fs">Sampling frequency in Hz</param>
/// <param name="ffcoeff">Feed-forward coefficients</param>
/// <param name="fbcoeff">Feedback coefficients</param>
/// <param name="toFreq">Calculate up to toFreq</param>
let calcGroupDelay fs ffcoeff fbcoeff toFreq =
let phase fromFreq toFreq = calcPhase fs ffcoeff fbcoeff fromFreq toFreq
let (x0, y0) = phase 0.0 toFreq
let windowed = Seq.windowed 2 y0
let y' = windowed |> Seq.map (fun a -> (a.[0] - a.[1]) * toFreq / 180.0)
(x0, 0.0::(Seq.toList y'))
/// <summary>
/// Calculate the data points for the impulse response of a filter
/// </summary>
/// <param name="n"></param>
/// <param name="filter"></param>
/// <returns>A pair of sequences representing the x and y data points
/// </returns>
let calcImpulse n filter = { 0..(n - 1) }, impulseResponse n filter
type PlotContainer =
| Window
| Browser
/// <summary>
/// Make a 2D line plot and show it in a window using Google Chart
/// </summary>
/// <param name="title">Title of the plot</param>
/// <param name="titleX">Title of the x axis</param>
/// <param name="titleY">Title of the y axis</param>
/// <param name="x">Sequence of data points for the x-axis</param>
/// <param name="y">Sequence of data points for the y-axis</param>
let plot2dwpf title titleX titleY (x, y) =
let xaxis = Axis()
let yaxis = Axis()
xaxis.title <- titleX
yaxis.title <- titleY
let options =
Options
(curveType = "function", title = title, hAxis = xaxis, vAxis = yaxis)
Seq.zip x y
|> Chart.Line
|> Chart.WithOptions options
|> Chart.Show
|> ignore
/// <summary>
/// Make a 2D line plot and show it in the browser using PlotLy
/// </summary>
/// <param name="title">Title of the plot</param>
/// <param name="titleX">Title of the x axis</param>
/// <param name="titleY">Title of the y axis</param>
/// <param name="x">Sequence of data points for the x-axis</param>
/// <param name="y">Sequence of data points for the y-axis</param>
let plotly2d title titleX titleY (x, y) =
let layout = Layout()
layout.title <- title
let yaxis = Yaxis()
yaxis.title <- titleY
layout.yaxis <- yaxis
[ Scatter(x = x, y = y, mode = "lines") ]
|> fun data -> Plotly.Plot(data, layout)
|> Plotly.Show
/// <summary>
/// Convenient function to make a 2D plot and show either in a window or in a
/// browser depending on the container type being passed in
/// </summary>
/// <param name="container">Either window or browser</param>
/// <param name="title">Title of the plot</param>
/// <param name="titleX">Title of the x axis</param>
/// <param name="titleY">Title of the y axis</param>
/// <param name="data">Pair of sequences</param>
let plot2d container title titleX titleY data =
match container with
| Window -> plot2dwpf title titleX titleY data
| Browser -> plotly2d title titleX titleY data
/// <summary>
/// Plot the spectrum of a sequence of samples
/// </summary>
/// <param name="container">Either window or browser</param>
/// <param name="toFreq"></param>
/// <param name="samples"></param>
let plotSpectrum' container toFreq samples =
calcSpectrum toFreq samples
|> plot2d container "Frequency spectrum" "Frequency (Hz)" ""
/// <summary>
/// Plot the spectrum of a sequence of samples and show the plot in a window
/// </summary>
/// <param name="toFreq"></param>
/// <param name="samples"></param>
let plotSpectrum toFreq samples = plotSpectrum' Window toFreq samples
/// <summary>
/// Plot the impulse response of a filter
/// </summary>
/// <param name="container">Either window or browser</param>
/// <param name="n"></param>
/// <param name="filter"></param>
let plotImpulse' container n filter =
calcImpulse n filter |> plot2d container "Impulse response" "" ""
/// <summary>
/// Plot the impulse response of a filter in a window
/// </summary>
let plotImpulse = plotImpulse' Window
/// <summary>
/// Plot the magnitude response of a filter given its feedforward and
/// feedback coefficients. Note that 1.0 is appended to the list of
/// feedback coefficients
/// </summary>
/// <param name="container">Either window or browser</param>
/// <param name="fs">Sampling frequency in Hz</param>
/// <param name="ffcoeff">Feedforward coefficients</param>
/// <param name="fbcoeff">Feedback coefficients</param>
/// <param name="toFreq">Frequcncy to plot up to</param>
let plotMagnitude' container fs toFreq (ffcoeff, fbcoeff) =
let title =
sprintf "Frequency response\nff=%A\nfb=%A\nfs=%f" ffcoeff fbcoeff fs
calcMagnitude fs ffcoeff fbcoeff 0.0 toFreq
|> plot2d container title "Frequency (Hz)" "Magnitude (dB)"
/// <summary>
/// Plot the magnitude response of a filter and show it in a window
/// </summary>
let plotMagnitude = plotMagnitude' Window
/// <summary>
/// Plot the phase response of a filter given its feedforward and
/// feedback coefficients. Note that 1.0 is appended to the list of
/// feedback coefficients
/// </summary>
/// <param name="container">Either window or browser</param>
/// <param name="fs">Sampling frequency in Hz</param>
/// <param name="ffcoeff">Feedforward coefficients</param>
/// <param name="fbcoeff">Feedback coefficients</param>
/// <param name="toFreq">Frequcncy to plot up to</param>
let plotPhase' container fs toFreq (ffcoeff, fbcoeff) =
let title =
sprintf "Frequency response\nff=%A\nfb=%A\nfs=%f" ffcoeff fbcoeff fs
calcPhase fs ffcoeff fbcoeff 0.0 toFreq
|> plot2d container title "Frequency (Hz)" "Angle (degrees)"
/// <summary>
/// Plot the phase response of a filter and show it in a window
/// </summary>
let plotPhase = plotPhase' Window
/// <summary>
/// Plot the group delay of a filter
/// </summary>
/// <param name="container">Either window or browser</param>
/// <param name="fs">Sampling frequency in Hz</param>
/// <param name="ffcoeff">Feed-forward coefficients</param>
/// <param name="fbcoeff">Feedback coefficients</param>
/// <param name="toFreq">Frequency to plot up to</param>
let plotGroupDelay' container fs toFreq (ffcoeff, fbcoeff) =
calcGroupDelay fs ffcoeff fbcoeff toFreq
|> plot2d container "Group Delay" "Frequency (Hz)" "Group Delay (samples)"
/// <summary>
/// Plot the group delay of a filter and show it in a window
/// </summary>
let plotGroupDelay = plotGroupDelay' Window