0e92d35Pizzicato standup bass: coupled string-body modal model, numbl MATLAB reference, and interactive web appJeremy Magland 1function [y, peakPa] = bass_output(P, p, oversample)
2% BASS_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 .* (bo_i0(beta * sqrt(max(0, 1 - r.^2))) / bo_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 = max(1, round(P.note_count));
33beat = 60 / max(20, P.tempo_bpm);
34duration = (K - 1) * beat + P.tail_s;
35total = floor(duration * P.sample_rate);
36if padN > 0
37 y = [zeros(1, padN) y];
38end
39y = y(1:min(length(y), total));
41% Squared fade on the tail
42fade = min(2048, floor(length(y) / 8));
43if fade > 1
44 y(end - fade + 1:end) = y(end - fade + 1:end) .* linspace(1, 0, fade).^2;
45end
47if P.normalize ~= 0
48 pk = max(abs(y));
49 if pk > 0
50 y = y / pk * 10^(P.peak_dbfs / 20);
51 end
52else
53 y = y / P.full_scale_pa;
54end
55end
57function v = bo_i0(x)
58% Modified Bessel function of the first kind, order zero (series expansion).
59v = ones(size(x));
60term = ones(size(x));
61hx = x / 2;
62for k = 1:40
63 term = term .* (hx / k).^2;
64 v = v + term;
65end
66end