5623b11Recorder simulator: MATLAB jet-drive model and interactive web appJeremy Magland 1function [y, peakPa] = recorder_output(P, p, oversample)
2% RECORDER_OUTPUT Anti-aliased decimation and output conditioning.
3% Kaiser-windowed-sinc FIR lowpass at 0.45 * fs_lo, decimate by oversample,
4% pre-roll, tail fade, then either normalize to peak_dbfs or divide by
5% full_scale_pa. Identical in structure to the sibling instruments' output
6% stages.
8peakPa = max(abs(p));
9R = oversample;
10T = length(p);
12if R > 1
13 taps = 12 * R + 1;
14 M2 = (taps - 1) / 2;
15 fcNorm = 0.45 / R; % cycles per input sample
16 m = (0:taps - 1) - M2;
17 h = sin(2 * pi * fcNorm * m) ./ (pi * m);
18 h(M2 + 1) = 2 * fcNorm; % sinc value at m = 0
19 r = m / M2;
20 beta = 8;
21 h = h .* (rc_i0(beta * sqrt(max(0, 1 - r.^2))) / rc_i0(beta));
22 h = h / sum(h); % unity DC gain
23 pf = conv(p, h);
24 outLen = floor(T / R);
25 y = reshape(pf((0:outLen - 1) * R + M2 + 1), 1, outLen); % centered pick
26else
27 y = p;
28end
30% Pre-roll, then truncate to the nominal duration
31padN = floor(P.pre_delay_ms * 1e-3 * P.sample_rate);
32K = min(3, max(1, round(P.note_count)));
33duration = K * P.note_dur_s + (K - 1) * max(0, P.gap_ms) * 1e-3 + P.tail_s;
34total = floor(duration * P.sample_rate);
35if padN > 0
36 y = [zeros(1, padN) y];
37end
38y = y(1:min(length(y), total));
40% Squared fade on the tail
41fade = min(2048, floor(length(y) / 8));
42if fade > 1
43 y(end - fade + 1:end) = y(end - fade + 1:end) .* linspace(1, 0, fade).^2;
44end
46if P.normalize ~= 0
47 pk = max(abs(y));
48 if pk > 0
49 y = y / pk * 10^(P.peak_dbfs / 20);
50 end
51else
52 y = y / P.full_scale_pa;
53end
54end
56function v = rc_i0(x)
57% Modified Bessel function of the first kind, order zero (series expansion).
58v = ones(size(x));
59term = ones(size(x));
60hx = x / 2;
61for k = 1:40
62 term = term .* (hx / k).^2;
63 v = v + term;
64end
65end