%% AMBIENTE  --  the calm soundscape, from four systems you already know
%
% This is the sound that plays on the exam clock of Sistemas e Sinais
% Mecatronicos, and on the day of reflection of the Lista M vote. There is no
% recording anywhere: every sample below is computed from difference equations
% and one convolution. Four ideas, all of them from this course:
%
%   1. a plucked string      -> a comb filter with a lossy loop (Karplus-Strong)
%   2. a drone               -> two sinusoids a hair apart, so they beat slowly
%   3. long chords           -> second-order-ish envelopes on a pentatonic scale
%   4. a room                -> convolution with a decaying-noise impulse response
%
% Run it and it plays. Change the four numbers in PARAMETERS and it changes
% character. Nothing here needs a toolbox.
%
% Carlos Cardeira, IST, 2026.

clear; close all; clc

%% -------- PARAMETERS ----------------------------------------------------
fs      = 44100;      % sampling rate [Hz]
T       = 90;         % how long to render [s]
f0      = 110;        % A2, the root of everything [Hz]
damp    = 0.998;      % string loop gain: closer to 1 = the string sings longer
                      % (>1 would be an unstable pole and it would howl)

graus   = [0 3 5 7 10 12];   % minor pentatonic, in semitones
rng('shuffle')

N  = round(fs*T);
y  = zeros(N,2);             % stereo canvas

nota = @(g,oct) f0 * 2^(oct-1) * 2^(g/12);
sorteia = @(v) v(randi(numel(v)));

%% -------- 1. THE PLUCKED STRING  (Karplus-Strong) -----------------------
% A delay line of L samples fed back through a two-tap average. The average is
% a first-order low-pass, so each mode is attenuated by a different amount per
% turn: the high modes die first. That is exactly what a plucked string does,
% and it is why this two-line loop sounds like a string and not like a buzzer.
%
%   y[n] = damp * 0.5 * ( y[n-L] + y[n-L-1] )
%
% Pole picture: the loop puts L poles near the unit circle, at the harmonics of
% fs/L. The loop filter pushes the high ones further in, so sigma_n grows with
% n. Same story as the guitar synthesizer curiosity, in one line.
function s = corda(f, dur, damp, fs)
    L = max(2, round(fs/f));
    n = round(fs*dur);
    anel = 2*rand(L,1) - 1;          % the pluck: noise = all modes at once
    s = zeros(n,1);
    k = 1;
    for i = 1:n
        s(i) = anel(k);
        k2 = mod(k, L) + 1;
        anel(k) = damp * 0.5 * (anel(k) + anel(k2));
        k = k2;
    end
    s = s .* min(1, linspace(1, 0, n)'*3);    % fade the tail, no click
end

%% -------- 2. THE DRONE --------------------------------------------------
% Two sinusoids detuned by 0.3%: their sum is an amplitude-modulated tone with
% beat frequency |f1-f2|, about a third of a hertz here. Slow beating is what
% makes a held note sound alive instead of electronic.
t = (0:N-1)'/fs;
bordao = 0.11*sin(2*pi*f0*t) + 0.11*sin(2*pi*f0*1.003*t) ...
       + 0.08*sin(2*pi*f0*1.5*t);
bordao = bordao .* (1 + 0.05*sin(2*pi*0.04*t));      % very slow tremolo
y = y + [bordao bordao];

%% -------- 3. THE LONG CHORDS -------------------------------------------
% Notes drawn from the pentatonic scale, with attack/sustain/decay of seconds.
% Any two notes of this scale sound well together, so a random draw never
% produces a wrong chord: that is the whole trick behind "ambient" music.
tempo = 0;
while tempo < T-15
    f  = nota(sorteia(graus), sorteia([1 2 3]));
    at = 2.5 + 2.5*rand;  sus = 3 + 3*rand;  qu = 4 + 3*rand;
    d  = at + sus + qu;
    tt = (0:round(fs*d)-1)'/fs;
    env = interp1([0 at at+sus d], [0 1 1 0], tt, 'pchip');   % slow envelope
    voz = env .* (0.20*sin(2*pi*f*tt) + 0.14*sawlike(f*1.004, tt));
    pan = 0.5 + 0.4*(rand-0.5);                                % where in the room
    i0  = round(fs*tempo) + 1;
    i1  = i0 + numel(voz) - 1;
    if i1 <= N
        y(i0:i1,1) = y(i0:i1,1) + voz*(1-pan);
        y(i0:i1,2) = y(i0:i1,2) + voz*pan;
    end
    tempo = tempo + 3.5 + 4.5*rand;
end

%% -------- and the strings on top ---------------------------------------
tempo = 2.5;
while tempo < T-6
    s   = corda(nota(sorteia(graus), sorteia([2 3])), 4.5, damp, fs);
    pan = 0.5 + 0.4*(rand-0.5);
    i0  = round(fs*tempo) + 1;
    i1  = i0 + numel(s) - 1;
    if i1 <= N
        y(i0:i1,1) = y(i0:i1,1) + 0.38*s*(1-pan);
        y(i0:i1,2) = y(i0:i1,2) + 0.38*s*pan;
    end
    tempo = tempo + 5 + 7*rand;
end

%% -------- 4. THE ROOM  (reverb = convolution with an LTI system) --------
% A room is a linear time-invariant system: what comes out is the convolution
% of what went in with the room's impulse response. We do not have a room, so
% we build a plausible h[n]: noise with an exponential decay. Convolve, mix a
% little of it back in, and the sound sits in a hall instead of in your face.
dur_h = 3.6;
n_h   = round(fs*dur_h);
h     = (2*rand(n_h,2)-1) .* (linspace(1,0,n_h)'.^2.4);
wet   = [conv(y(:,1), h(:,1), 'same')  conv(y(:,2), h(:,2), 'same')];
y     = 0.75*y + 0.55*wet/max(abs(wet(:)));

%% -------- out ------------------------------------------------------------
y = 0.9 * y / max(abs(y(:)));          % normalise, leave headroom
soundsc(y, fs)
% audiowrite('ambiente.wav', y, fs);

%% ------------------------------------------------------------------------
function s = sawlike(f, t)
% A few harmonics with 1/n amplitudes: a band-limited sawtooth, which is what
% the Fourier series of a sawtooth gives you if you stop after five terms.
    s = zeros(size(t));
    for n = 1:5
        s = s + sin(2*pi*f*n*t)/n;
    end
    s = s/2;
end
