├── README.md ├── TestGuitarPhraseMono.wav ├── boss_DS1_transistor.m ├── diode_clipper.m ├── fender_blackface_tone_stack.m └── guitar_distortion.m /README.md: -------------------------------------------------------------------------------- 1 | # Virtual-Analog 2 | A collection of virtual analog models in the form of MATLAB scripts. 3 | 4 | Currently there are 4 circuit models within this repository. All of the models are implemented using a nonlinear state-space formation. 5 | 6 | 1. Fender Blackface Tone Stack - This is a full time-domain physical model of the tone stack circuit from the Fender blackface series of guitar amplifiers. It is a parametric, linear model that performs filtering of an input signal. 7 | 8 | 2. Diode Clipper - This is a full time-domain physical model of a 2 capacitor (high pass and low pass) diode clipper circuit. This script also allows the user to compare different numerical methods for discretization (trapezoid, midpoint, and BDF2). 9 | 10 | 3. Boss DS-1 Transistor Stage - This is a full time-domain physical model of the transistor stage from the Boss DS-1 circuit. 11 | 12 | 4. Marshall Guv'nor Inspired Distortion Effect - This is a guitar distortion model based on the Marshall Guv'nor circuit. It is not a full physical model, as the stages of the circuit are modeled separately. The tone stage of the circuit is a model of the Big Muff tone stage, rather than the original Marshall tone stage. 13 | -------------------------------------------------------------------------------- /TestGuitarPhraseMono.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mac787/Virtual-Analog/2f1fe32614fd1e6fbab1b4740c189e1e85328c54/TestGuitarPhraseMono.wav -------------------------------------------------------------------------------- /boss_DS1_transistor.m: -------------------------------------------------------------------------------- 1 | % Boss DS-1 Transistor Stage 2 | % Mac Porter 3 | 4 | clear all 5 | close all 6 | clc 7 | 8 | %% Parameters 9 | 10 | % General 11 | Fs = 44100; % Sample rate 12 | len = .01; % Length of simulation (s) 13 | inputType = 'audio'; % 'impulse', 'sine', or 'audio' 14 | filename = 'TestGuitarPhraseMono.wav'; % Filename for audio 15 | sinFreq = 1000; % Frequency for sine wave (Hz) 16 | 17 | % Effect Parameters 18 | inGain = 1; % Input gain (v) 19 | Is = 6.734e-15; % Transistor saturation current (A) 20 | Bf = 200; % Forward current gain 21 | Br = 0.1; % Reverse current gain 22 | battNoise = 0.05; % Amount of battery noise (0 to 1) 23 | 24 | % Plotting 25 | playSound = 'on'; 26 | plotTime = 'off'; % Plot time 27 | plotFreq = 'off'; % Plot frequency 28 | 29 | % For Newton solver 30 | tol = 10e-7; % Error tolerance 31 | maxIters = 100; % Number of allowed iterations 32 | maxSubIters = 10; % Number of allowed sub-iterations 33 | 34 | %% Derived Parameters 35 | 36 | T = 1/Fs; % Sample period 37 | 38 | % Input signal 39 | if strcmp(inputType,'impulse') 40 | N = floor(len*Fs); 41 | t = (0:T:N*T-T); 42 | in = [1e-6;zeros(N-1,1)]; 43 | elseif strcmp(inputType,'sine') 44 | N = floor(len*Fs); 45 | t = (0:T:N*T-T); 46 | in = inGain*sin(2*pi*sinFreq*t); 47 | elseif strcmp(inputType,'audio') 48 | in = inGain*audioread(filename); 49 | N = length(in); 50 | t = (0:T:N*T-T); 51 | end 52 | 53 | if strcmp(inputType,'impulse') 54 | Vcc = zeros(1,N); 55 | else 56 | Vcc = 9+battNoise*sin(120*pi*t); % Battery signal 57 | end 58 | 59 | 60 | fx = (0:N-1).*Fs/N; % Frequency vector 61 | 62 | out = zeros(N,1); % Initialize output 63 | 64 | %% Physical Parameters 65 | R1 = 100e3; 66 | R2 = 10e3; 67 | R3 = 470e3; 68 | R4 = 22; 69 | R5 = 100e3; 70 | C1 = .047e-6; 71 | C2 = 250e-12; 72 | C3 = .47e-6; 73 | 74 | % Precomputed constants 75 | invVt = 1/25.85e-3; 76 | invBf = 1/Bf; 77 | invBr = 1/Br; 78 | IsVtBf = Is*invBf*invVt; 79 | IsVtBr = Is*invBr*invVt; 80 | 81 | %% System matrices 82 | A = [-1/(R1*C1)-1/(R2*C1)-1/(R5*C1) -1/(R2*C1)-1/(R5*C1) -1/(R5*C1); 83 | -1/(R2*C2)-1/(R5*C2) -1/(R2*C2)-1/(R3*C2)-1/(R5*C2) -1/(R5*C2); 84 | -1/(R5*C3) -1/(R5*C3) -1/(R5*C3)]; 85 | B = [1/(R1*C1)+1/(R2*C1)+1/(R5*C1) -1/(R2*C1); 86 | 1/(R2*C2)+1/(R5*C2) -1/(R2*C2); 1/(R5*C3) 0]; 87 | C = [1/C1 1/C1; 0 1/C2; 0 0]; 88 | D = [-1 -1 -1]; 89 | E = [1 0]; 90 | F = [0 0]; 91 | G = [-1 0 0; 0 1 0]; 92 | H = [1 0; 0 0]; 93 | K = [-R4 -R4; 0 0]; 94 | 95 | %% DC Analysis - to find steady state values 96 | 97 | % In a real time implementation, this could be precalculated 98 | 99 | % DC system matrices 100 | Ra = R1+R2+R3; 101 | Adc = [-R1/Ra; -R3/Ra; 1-R2/Ra]; 102 | Bdc = [R1-R1*R1/Ra R1-(R1*R1+R1*R3)/Ra; -R1*R3/Ra R3-(R1*R3+R3*R3)/Ra;... 103 | -R1*R2/Ra -(R1*R2+R2*R3)/Ra]; 104 | Cdc = [R1/Ra; -R3/Ra]; 105 | Ddc = [R1*R1/Ra-R1-R4 (R1*R1+R1*R3)/Ra-R1-R4; -R1*R3/Ra R3-(R1*R3+R3*R3)/Ra]; 106 | 107 | u = 9; 108 | v = [0;0]; 109 | 110 | error = 1; 111 | iters = 0; 112 | 113 | % Damped Newton 114 | while (error > tol) && (iters < maxIters) 115 | 116 | % Precomputation 117 | VbeVt = v(1)*invVt; 118 | VbcVt = v(2)*invVt; 119 | 120 | % Vector of transistor base and collector currents 121 | i = [Is*(invBf*(exp(VbeVt)-1)+invBr*(exp(VbcVt)-1));... 122 | Is*((exp(VbeVt)-1)-(1+Br)*invBr*(exp(VbcVt)-1))]; 123 | 124 | % Partial derivatives of transistor currents 125 | iDer = [IsVtBf*exp(VbeVt) IsVtBr*exp(VbcVt);... 126 | Is*invVt*exp(VbeVt) -IsVtBr*(1+Br)*exp(VbcVt)]; 127 | 128 | M = Cdc*u+Ddc*i-v; % Function to solve 129 | J = Ddc*iDer-eye(2); % Jacobian matrix 130 | 131 | step = J\M; % Newton step 132 | 133 | vNew = v-step; % New transistor voltages 134 | VbeVtNew = vNew(1)*invVt; 135 | VbcVtNew = vNew(2)*invVt; 136 | % New transistor currents 137 | iNew = [Is*(invBf*(exp(VbeVtNew)-1)+invBr*(exp(VbcVtNew)-1));... 138 | Is*((exp(VbeVtNew)-1)-(1+Br)*invBr*(exp(VbcVtNew)-1))]; 139 | MNew = Cdc*u+Ddc*iNew-vNew; % Updated function 140 | 141 | % Apply damping if the step goes in the wrong direction, or if the 142 | % function goes to inf 143 | subStep = step; 144 | subIters = 0; 145 | while ((norm(MNew) > norm(M)) && (subIters < maxSubIters))... 146 | || (isnan(norm(MNew))) || (isinf(norm(MNew))) 147 | subStep = subStep/2; 148 | vNew = v-subStep; 149 | VbeVtNew = vNew(1)*invVt; 150 | VbcVtNew = vNew(2)*invVt; 151 | iNew = [Is*(invBf*(exp(VbeVtNew)-1)+invBr*(exp(VbcVtNew)-1));... 152 | Is*((exp(VbeVtNew)-1)-(1+Br)*invBr*(exp(VbcVtNew)-1))]; 153 | MNew = Cdc*u+Ddc*iNew-vNew; 154 | subIters = subIters+1; 155 | end 156 | error = norm(vNew-v)/norm(v); % Relative error 157 | iters = iters+1; 158 | v = vNew; % Final transistor voltages 159 | end 160 | 161 | VbeVt = v(1)*invVt; 162 | VbcVt = v(2)*invVt; 163 | % Transistor currents 164 | i = [Is*(invBf*(exp(VbeVt)-1)+invBr*(exp(VbcVt)-1));... 165 | Is*((exp(VbeVt)-1)-(1+Br)*invBr*(exp(VbcVt)-1))]; 166 | % Steady state voltages across the capacitors 167 | x = Adc*u+Bdc*i; 168 | 169 | %% Simulation 170 | 171 | if strcmp(inputType,'impulse') 172 | x = [0;0;0]; 173 | i = [0;0]; 174 | v = [0;0]; 175 | uprev = [0;0]; 176 | else 177 | uprev = [0;9]; 178 | end 179 | xprev = x; 180 | iprev = i; 181 | 182 | % Matrix inversion for trapezoid discretization 183 | Q = inv(2*Fs*eye(3)-A); 184 | 185 | % Precomputation 186 | GQCK = G*Q*C+K; 187 | 188 | % Main loop 189 | for n = 1:N 190 | 191 | u = [in(n);Vcc(n)]; % Input 192 | 193 | error = 1; 194 | iters = 0; 195 | 196 | % Constant term from discretization 197 | r = G*Q*(2*Fs*eye(3)+A)*xprev+(G*Q*B)*uprev+(G*Q*B+H)*u+(G*Q*C)*iprev; 198 | 199 | % Damped Newton 200 | while (error > tol) && (iters < maxIters) 201 | 202 | % Precomputation 203 | VbeVt = v(1)*invVt; 204 | VbcVt = v(2)*invVt; 205 | 206 | % Vector of transistor base and collector currents 207 | i = [Is*(invBf*(exp(VbeVt)-1)+invBr*(exp(VbcVt)-1));... 208 | Is*((exp(VbeVt)-1)-(1+Br)*invBr*(exp(VbcVt)-1))]; 209 | 210 | % Partial derivatives of transistor currents 211 | iDer = [IsVtBf*exp(VbeVt) IsVtBr*exp(VbcVt);... 212 | Is*invVt*exp(VbeVt) -IsVtBr*(1+Br)*exp(VbcVt)]; 213 | 214 | M = r+(GQCK)*i-v; % Function to solve 215 | J = (GQCK)*iDer-eye(2); % Jacobian matrix 216 | 217 | step = J\M; % Newton step 218 | 219 | vNew = v-step; % New transistor voltages 220 | VbeVtNew = vNew(1)*invVt; 221 | VbcVtNew = vNew(2)*invVt; 222 | % New transistor currents 223 | iNew = [Is*(invBf*(exp(VbeVtNew)-1)+invBr*(exp(VbcVtNew)-1));... 224 | Is*((exp(VbeVtNew)-1)-(1+Br)*invBr*(exp(VbcVtNew)-1))]; 225 | MNew = r+(GQCK)*iNew-vNew; % Updated function 226 | 227 | % Apply damping if the step goes in the wrong direction, or if the 228 | % function goes to inf 229 | subStep = step; 230 | subIters = 0; 231 | while ((norm(MNew) > norm(M)) && (subIters < maxSubIters))... 232 | || (isnan(norm(MNew))) || (isinf(norm(MNew))) 233 | subStep = subStep/2; 234 | vNew = v-subStep; 235 | VbeVtNew = vNew(1)*invVt; 236 | VbcVtNew = vNew(2)*invVt; 237 | iNew = [Is*(invBf*(exp(VbeVtNew)-1)+invBr*(exp(VbcVtNew)-1));... 238 | Is*((exp(VbeVtNew)-1)-(1+Br)*invBr*(exp(VbcVtNew)-1))]; 239 | MNew = r+(GQCK)*iNew-vNew; 240 | subIters = subIters+1; 241 | end 242 | error = norm(vNew-v)/norm(v); % Relative error 243 | iters = iters+1; 244 | v = vNew; % Final transistor voltages 245 | end 246 | 247 | VbeVt = v(1)*invVt; 248 | VbcVt = v(2)*invVt; 249 | % Transistor currents 250 | i = [Is*(invBf*(exp(VbeVt)-1)+invBr*(exp(VbcVt)-1));... 251 | Is*((exp(VbeVt)-1)-(1+Br)*invBr*(exp(VbcVt)-1))]; 252 | % State update 253 | x = Q*((2*Fs*eye(3)+A)*xprev+B*(u+uprev)+C*(i+iprev)); 254 | % Output 255 | y = D*x+E*u+F*i; 256 | out(n) = y; 257 | 258 | % Update previous values 259 | xprev = x; 260 | uprev = u; 261 | iprev = i; 262 | end 263 | 264 | if strcmp(playSound,'on') 265 | soundsc(out,Fs); 266 | end 267 | 268 | %% Plots 269 | if strcmp(plotTime,'on') 270 | figure(); 271 | plot(t,out); 272 | xlabel('Time (s)'); 273 | ylabel('Volts'); 274 | end 275 | 276 | if strcmp(plotFreq,'on') 277 | Y = 1e6*abs(fft(out)); 278 | figure(); 279 | semilogx(fx,20*log10(Y)); 280 | xlim([20 20000]); 281 | xlabel('Freq (Hz)'); 282 | ylabel('dB'); 283 | end 284 | -------------------------------------------------------------------------------- /diode_clipper.m: -------------------------------------------------------------------------------- 1 | % Diode Clipper 2 | % Mac Porter 3 | 4 | clear all 5 | close all 6 | clc 7 | 8 | %% Parameters 9 | 10 | % General 11 | Fs = 4*44100; % Sample rate 12 | len = .0005; % Length of simulation (s) 13 | inputType = 'sine'; % 'impulse', 'sine', or 'audio' 14 | filename = 'TestGuitarPhraseMono.wav'; % Filename for audio 15 | sinFreq = 10000; % Frequency for sine wave (Hz) 16 | discMethod = 'all'; % Discretization method: trapezoid, midpoint, 17 | % BDF2, or all (for comparing all methods) 18 | 19 | % Effect Parameters 20 | inGain = 10; % Input gain (v) 21 | Is = 2.52e-9; % Diode saturation current (A) 22 | 23 | % Plotting 24 | playSound = 'off'; 25 | plotTime = 'off'; % Plot time 26 | plotFreq = 'off'; % Plot frequency 27 | 28 | % For Newton solver 29 | tol = 10e-7; % Error tolerance 30 | maxIters = 100; % Number of allowed iterations 31 | maxSubIters = 10; % Number of allowed sub-iterations 32 | 33 | %% Derived Parameters 34 | 35 | T = 1/Fs; % Sample period 36 | 37 | % Input signal 38 | if strcmp(inputType,'impulse') 39 | N = floor(len*Fs); 40 | t = (0:T:N*T-T); 41 | in = [1e-6;zeros(N-1,1)]; 42 | elseif strcmp(inputType,'sine') 43 | N = floor(len*Fs); 44 | t = (0:T:N*T-T); 45 | in = inGain*sin(2*pi*sinFreq*t); 46 | elseif strcmp(inputType,'audio') 47 | in = inGain*audioread(filename); 48 | N = length(in); 49 | t = (0:T:N*T-T); 50 | end 51 | 52 | fx = (0:N-1).*Fs/N; % Frequency vector 53 | 54 | out = zeros(N,1); % Initialize output 55 | 56 | trap = zeros(N,1); 57 | mid = zeros(N,1); 58 | BDF2 = zeros(N,1); 59 | eul = zeros(N,1); 60 | 61 | %% Physical Parameters 62 | R1 = 2.2e3; 63 | C1 = .47e-6; 64 | C2 = .01e-6; 65 | invVt = 1/25.85e-3; % Inverse of thermal voltage 66 | 67 | %% System matrices 68 | A = [-1/(R1*C1) -1/(R1*C1); -1/(R1*C2) -1/(R1*C2)]; 69 | B = [1/(R1*C1); 1/(R1*C2)]; 70 | C = [0; -1/C1]; 71 | D = [0 1]; 72 | G = [0 -1]; 73 | 74 | %% Simulation 75 | 76 | % Initial values 77 | x = [0;0]; 78 | xprev = [0;0]; 79 | xprev2 = [0;0]; 80 | uprev = 0; 81 | iprev = 0; 82 | v = 0; 83 | vm = 0; 84 | 85 | % Trapezoid discretization sheme 86 | if strcmp(discMethod,'trapezoid') || strcmp(discMethod,'all') 87 | 88 | % Initial values 89 | x = [0;0]; 90 | xprev = [0;0]; 91 | xprev2 = [0;0]; 92 | uprev = 0; 93 | iprev = 0; 94 | v = 0; 95 | vm = 0; 96 | 97 | Q = inv(2*Fs*eye(2)-A); % Matrix inversion 98 | for n = 1:N 99 | 100 | u = in(n); % Input 101 | 102 | error = 1; % Set initial error greater than tol 103 | iters = 0; % Reset Newton iterations 104 | 105 | % Constant term from discretization 106 | r = G*Q*(2*Fs*eye(2)+A)*xprev+(G*Q*B)*uprev+(G*Q*B)*u+(G*Q*C)*iprev; 107 | 108 | % Damped Newton to solve nonlinearity 109 | while (error > tol) && (iters < maxIters) 110 | 111 | i = -2*Is*sinh(v*invVt); % Diode i-v relation 112 | iDer = -2*invVt*Is*cosh(v*invVt); % Derivative of i-v relation 113 | 114 | M = r+(G*Q*C)*i-v; % Function to solve (= 0) 115 | J = (G*Q*C)*iDer-1; % Derivative of function to solve 116 | 117 | step = J\M; % Newton step 118 | 119 | vNew = v-step; % New diode voltage 120 | iNew = -2*Is*sinh(vNew*invVt); % New diode current 121 | MNew = r+(G*Q*C)*iNew-vNew; % Updated function 122 | 123 | % Apply damping if the step goes in the wrong direction 124 | subStep = step; 125 | subIters = 0; 126 | while (norm(MNew) > norm(M)) && (subIters < maxSubIters) 127 | subStep = subStep/2; % Damping reduces step by half 128 | vNew = v-subStep; 129 | iNew = -2*Is*sinh(vNew*invVt); 130 | MNew = r+(G*Q*C)*iNew-vNew; 131 | subIters = subIters+1; 132 | end 133 | %residual = max(abs(M))+max(abs(step)); 134 | error = norm(vNew-v)/norm(v); % Relative error 135 | iters = iters+1; 136 | v = vNew; % Final diode voltage 137 | end 138 | 139 | i = -2*Is*sinh(v*invVt); %Diode current 140 | % State update 141 | x = Q*((2*Fs*eye(2)+A)*xprev+B*(u+uprev)+C*(i+iprev)); 142 | % Output 143 | y = D*x; 144 | trap(n) = y; 145 | 146 | % Update previous values for next time step 147 | xprev = x; 148 | uprev = u; 149 | iprev = i; 150 | end 151 | out = trap; 152 | end 153 | 154 | % Midpoint discretization scheme 155 | if strcmp(discMethod,'midpoint') || strcmp(discMethod,'all') 156 | 157 | % Initial values 158 | x = [0;0]; 159 | xprev = [0;0]; 160 | xprev2 = [0;0]; 161 | uprev = 0; 162 | iprev = 0; 163 | v = 0; 164 | vm = 0; 165 | 166 | Q = inv(2*Fs*eye(2)-A); % Matrix inversion 167 | for n = 1:N 168 | 169 | u = in(n); % Input 170 | um = 0.5*(u+uprev); % Midpoint input 171 | 172 | error = 1; % Set initial error greater than tol 173 | iters = 0; % Reset Newton iterations 174 | 175 | % Constant term from discretization 176 | r = G*Q*2*Fs*xprev+(G*Q*B)*um; 177 | 178 | % Damped Newton to solve nonlinearity 179 | while (error > tol) && (iters < maxIters) 180 | 181 | im = -2*Is*sinh(vm*invVt); % Diode i-v relation 182 | imDer = -2*invVt*Is*cosh(vm*invVt); % Derivative of i-v relation 183 | 184 | M = r+(G*Q*C)*im-vm; % Function to solve (= 0) 185 | J = (G*Q*C)*imDer-1; % Derivative of function to solve 186 | 187 | step = J\M; % Newton step 188 | 189 | vmNew = vm-step; % New diode voltage 190 | imNew = -2*Is*sinh(vmNew*invVt); % New diode current 191 | MNew = r+(G*Q*C)*imNew-vmNew; % Updated function 192 | 193 | % Apply damping if the step goes in the wrong direction 194 | subStep = step; 195 | subIters = 0; 196 | while (norm(MNew) > norm(M)) && (subIters < maxSubIters) 197 | subStep = subStep/2; % Damping reduces step by half 198 | vmNew = vm-subStep; 199 | imNew = -2*Is*sinh(vmNew*invVt); 200 | MNew = r+(G*Q*C)*imNew-vmNew; 201 | subIters = subIters+1; 202 | end 203 | %residual = max(abs(M))+max(abs(step)); 204 | error = norm(vmNew-vm)/norm(vm); % Relative error 205 | iters = iters+1; 206 | vm = vmNew; % Final diode voltage 207 | end 208 | 209 | im = -2*Is*sinh(vm*invVt); %Diode current 210 | % State update at midpoint 211 | xm = Q*2*Fs*xprev+Q*B*um+Q*C*im; 212 | % Output at midpoint 213 | y = D*xm; 214 | mid(n) = y; 215 | % Current state from midpoint 216 | x = 2*xm-xprev; 217 | 218 | % Update previous values for next time step 219 | xprev = x; 220 | uprev = u; 221 | end 222 | out = mid; 223 | end 224 | 225 | % BDF2 (Backward Difference Formula 2nd order) discretization scheme 226 | if strcmp(discMethod,'BDF2') || strcmp(discMethod,'all') 227 | 228 | % Initial values 229 | x = [0;0]; 230 | xprev = [0;0]; 231 | xprev2 = [0;0]; 232 | uprev = 0; 233 | iprev = 0; 234 | v = 0; 235 | vm = 0; 236 | 237 | Q = inv(1.5*Fs*eye(2)-A); % Matrix inversion 238 | for n = 1:N 239 | 240 | u = in(n); % Input 241 | 242 | error = 1; % Set initial error greater than tol 243 | iters = 0; % Reset Newton iterations 244 | 245 | % Constant term from discretization 246 | r = G*Q*2*Fs*xprev-G*Q*.5*Fs*xprev2+(G*Q*B)*u; 247 | 248 | % Damped Newton to solve nonlinearity 249 | while (error > tol) && (iters < maxIters) 250 | 251 | i = -2*Is*sinh(v*invVt); % Diode i-v relation 252 | iDer = -2*invVt*Is*cosh(v*invVt); % Derivative of i-v relation 253 | 254 | M = r+(G*Q*C)*i-v; % Function to solve (= 0) 255 | J = (G*Q*C)*iDer-1; % Derivative of function to solve 256 | 257 | step = J\M; % Newton step 258 | 259 | vNew = v-step; % New diode voltage 260 | iNew = -2*Is*sinh(vNew*invVt); % New diode current 261 | MNew = r+(G*Q*C)*iNew-vNew; % Updated function 262 | 263 | % Apply damping if the step goes in the wrong direction 264 | subStep = step; 265 | subIters = 0; 266 | while (norm(MNew) > norm(M)) && (subIters < maxSubIters) 267 | subStep = subStep/2; % Damping reduces step by half 268 | vNew = v-subStep; 269 | iNew = -2*Is*sinh(vNew*invVt); 270 | MNew = r+(G*Q*C)*iNew-vNew; 271 | subIters = subIters+1; 272 | end 273 | %residual = max(abs(M))+max(abs(step)); 274 | error = norm(vNew-v)/norm(v); % Relative error 275 | iters = iters+1; 276 | v = vNew; % Final diode voltage 277 | end 278 | 279 | i = -2*Is*sinh(v*invVt); %Diode current 280 | % State update 281 | x = Q*(2*Fs*xprev-.5*Fs*xprev2+B*u+C*i); 282 | % Output 283 | y = D*x; 284 | BDF2(n) = y; 285 | 286 | % Update previous values for next time step 287 | xprev2 = xprev; 288 | xprev = x; 289 | uprev = u; 290 | iprev = i; 291 | end 292 | out = BDF2; 293 | end 294 | 295 | if strcmp(playSound,'on') 296 | soundsc(out,Fs); 297 | end 298 | 299 | %% Plots 300 | if strcmp(plotTime,'on') 301 | figure(); 302 | plot(t,out); 303 | xlabel('Time (s)'); 304 | ylabel('Volts'); 305 | end 306 | 307 | if strcmp(plotFreq,'on') 308 | Y = 1e6*abs(fft(out)); 309 | figure(); 310 | semilogx(fx,20*log10(Y)); 311 | xlim([20 20000]); 312 | xlabel('Freq (Hz)'); 313 | ylabel('dB'); 314 | end 315 | 316 | if strcmp(discMethod,'all') 317 | figure(); 318 | plot(t,trap,t,mid,t,BDF2); 319 | legend('Trapezoid','Midpoint','BDF2'); 320 | xlabel('Time (s)'); 321 | ylabel('Volts'); 322 | end 323 | 324 | 325 | 326 | 327 | -------------------------------------------------------------------------------- /fender_blackface_tone_stack.m: -------------------------------------------------------------------------------- 1 | % Fender Blackface Tone Stack 2 | % Mac Porter 3 | 4 | clear all 5 | close all 6 | clc 7 | 8 | %% Parameters 9 | 10 | % General 11 | Fs = 44100; % Sample rate 12 | len = 1; % Length of simulation (s) 13 | inputType = 'impulse'; % 'impulse', 'sine', or 'audio' 14 | filename = 'TestGuitarPhraseMono.wav'; % Filename for audio 15 | sinFreq = 1000; % Frequency for sine wave (Hz) 16 | 17 | % Effect Parameters 18 | treb = 0.5; % Treble knob 19 | mid = 0.5; % Middle knob 20 | bass = 0.5; % Bass knob 21 | 22 | % Plotting 23 | playSound = 'off'; 24 | plotTime = 'off'; % Plot time 25 | plotFreq = 'on'; % Plot frequency 26 | 27 | %% Derived Parameters 28 | 29 | T = 1/Fs; % Sample period 30 | 31 | % Input signal 32 | if strcmp(inputType,'impulse') 33 | N = floor(len*Fs); 34 | t = (0:T:N*T-T); 35 | in = [1;zeros(N-1,1)]; 36 | elseif strcmp(inputType,'sine') 37 | N = floor(len*Fs); 38 | t = (0:T:N*T-T); 39 | in = sin(2*pi*sinFreq*t); 40 | elseif strcmp(inputType,'audio') 41 | in = audioread(filename); 42 | N = length(in); 43 | t = (0:T:N*T-T); 44 | end 45 | 46 | fx = (0:N-1).*Fs/N; % Frequency vector 47 | 48 | out = zeros(N,1); % Initialize output 49 | 50 | %% Physical Parameters 51 | 52 | R1 = 100e3; 53 | Rt = 250e3; 54 | Rm = 10e3; 55 | Rb = 250e3; 56 | R2 = (1-treb)*Rt; 57 | R3 = treb*Rt; 58 | R4 = bass*Rb; 59 | R5 = mid*Rm; 60 | C1 = 250e-12; 61 | C2 = 100e-9; 62 | C3 = 47e-9; 63 | bass = 1.0125^bass-.0125; % Bass pot is logarithmic 64 | 65 | %% Simulation 66 | 67 | % Initial values 68 | x = [0;0;0]; 69 | xprev = [0;0;0]; 70 | uprev = 0; 71 | 72 | for n = 1:N 73 | % System matrices 74 | % Matrices are updated in time loop in case of parameter changes 75 | Rp = R2*R5+R3*R5; 76 | Rq = R1*R2+R1*R3+R1*R4+R1*R5; 77 | Rx = -1/(C1*(R1*R2+R1*R3+R1*R5+R2*R5+R3*R5)); 78 | Ry = -1/(R4*C2*(R1*R2+R1*R3+R1*R5+R2*R5+R3*R5)); 79 | Rz = 1/(R4*C3*(R1*R2+R1*R3+R1*R5+R2*R5+R3*R5)); 80 | 81 | A = [Rx*(R1+R5) -Rx*(R1+R5) Rx*R1;... 82 | -Ry*(R1*R4+R4*R5) Ry*(Rp+Rq+R4*R5) -Ry*(Rq+R3*R5);... 83 | -Rz*R1*R4 Rz*(Rp+Rq) -Rz*(Rp+Rq+R2*R4+R3*R4)]; 84 | B = [-Rx*R1; Ry*R1*R4; Rz*(R1*R4+R2*R4+R3*R4)]; 85 | C = [-1-R2*C1*Rx*(R1+R5) R2*C1*Rx*(R1+R5) -R2*C1*Rx*R1]; 86 | D = 1+R2*C1*Rx*R1; 87 | 88 | u = in(n); % Input sample 89 | % State update (trapezoid discretization) 90 | x = (2*Fs*eye(3)-A)\(2*Fs*eye(3)+A)*xprev+(2*Fs*eye(3)-A)\(B*(u+uprev)); 91 | % Output equation 92 | y = C*x+D*u; 93 | out(n) = y; 94 | 95 | % Update previous values for next time step 96 | xprev = x; 97 | uprev = u; 98 | end 99 | 100 | if strcmp(playSound,'on') 101 | soundsc(out,Fs) 102 | end 103 | 104 | %% Plotting 105 | 106 | if strcmp(plotTime,'on') 107 | figure(); 108 | plot(t,out); 109 | xlabel('Time (s)') 110 | ylabel('Volts') 111 | end 112 | 113 | if strcmp(plotFreq,'on') 114 | figure(); 115 | semilogx(fx,20*log10(abs(fft(out)))); 116 | xlim([20 20000]); 117 | ylim([-35 0]); 118 | xlabel('Freq (Hz)'); 119 | ylabel('dB'); 120 | end 121 | -------------------------------------------------------------------------------- /guitar_distortion.m: -------------------------------------------------------------------------------- 1 | % Distortion Effect Inspired by Marshall Guv'nor 2 | % Mac Porter 3 | 4 | clear all 5 | close all 6 | clc 7 | 8 | %% Parameters 9 | 10 | % General 11 | Fs = 44100; % Sample rate 12 | len = 1; % Length of simulation (s) 13 | inputType = 'audio'; % 'impulse', 'sine', or 'audio' 14 | filename = 'TestGuitarPhraseMono.wav'; % Filename for audio 15 | sinFreq = 1000; % Frequency for sine wave (Hz) 16 | 17 | % Effect Parameters 18 | inGain = 0.1; % Input gain (around 0.1V) 19 | gain = 0.7; % Gain knob (0 to 1) 20 | tone = 0.4; % Tone knob (0 to 1) 21 | Is = 2.52e-6; % Diode saturation current (A) 22 | 23 | % Plotting 24 | playSound = 'on'; 25 | plotTime = 'off'; % Plot time 26 | plotFreq = 'off'; % Plot frequency 27 | plotStageOuts = 'off'; % Plot time/frequency of individual stages 28 | 29 | % For Newton solver 30 | tol = 10e-7; % Error tolerance 31 | maxIters = 100; % Number of allowed iterations 32 | maxSubIters = 10; % Number of allowed sub-iterations 33 | 34 | %% Derived Parameters 35 | 36 | T = 1/Fs; % Sample period 37 | 38 | % Input signal 39 | if strcmp(inputType,'impulse') 40 | N = floor(len*Fs); 41 | t = (0:T:N*T-T); 42 | in = [1e-6;zeros(N-1,1)]; 43 | elseif strcmp(inputType,'sine') 44 | N = floor(len*Fs); 45 | t = (0:T:N*T-T); 46 | in = inGain*sin(2*pi*sinFreq*t); 47 | elseif strcmp(inputType,'audio') 48 | in = inGain*audioread(filename); 49 | N = length(in); 50 | t = (0:T:N*T-T); 51 | end 52 | 53 | fx = (0:N-1).*Fs/N; % Frequency vector 54 | 55 | out = zeros(N,1); % Initialize output 56 | 57 | if strcmp(plotStageOuts,'on') 58 | Vop1 = zeros(N,1); 59 | Vop2 = zeros(N,1); 60 | Vd = zeros(N,1); 61 | end 62 | 63 | %% Component Values 64 | 65 | % R's are resistors in ohms, C's are capacitors in farads 66 | 67 | % Input stage 68 | R1in = 1e6; 69 | R2in = 2.2e3; 70 | Rvin = gain*100e3; 71 | C1in = 9.6e-9; 72 | C2in = 120e-12; 73 | C3in = 100e-9; 74 | 75 | % Op amp gain stage 76 | R1op = 10e3; 77 | R2op = 680e3; 78 | C1op = 220e-9; 79 | C2op = 220e-12; 80 | 81 | % Diode clipping stage 82 | C3d = 220e-9; 83 | R3d = 1e3; 84 | invVt = 1/25.85e-3; % Inverse of thermal voltage 85 | 86 | % Tone stage 87 | R1t = 1e3; 88 | R2t = 39e3; 89 | R3t = 22e3; 90 | Rm1 = (1-tone)*100e3; 91 | Rm2 = tone*100e3; 92 | R4t = 1e6; 93 | C1t = 4e-9; 94 | C2t = 10e-9; 95 | 96 | %% Constant System Matrices 97 | 98 | % These matrices to not depend on parameters, so they only need to be 99 | % calculated once 100 | 101 | % Input stage 102 | Bin = [1/(R1in*C1in); -1/(R2in*C2in); -1/(R2in*C3in)]; 103 | Cin = [-1 -1 0]; 104 | Din = 1; 105 | 106 | % Op amp gain stage 107 | Aop = [-1/(R1op*C1op) 0; 1/(R1op*C2op) -1/(R2op*C2op)]; 108 | Bop = [1/(R1op*C1op); -1/(R1op*C2op)]; 109 | Cop = [0 1]; 110 | Dop = 0; 111 | 112 | % Diode clipping stage 113 | Cd = 1/C3d; 114 | Gd = -1; 115 | Hd = 1; 116 | Kd = -R3d; 117 | 118 | %% Simulation 119 | 120 | % Initial values 121 | xInPrev = [0;0;0]; 122 | xOpPrev = [0;0]; 123 | xdPrev = 0; 124 | uInPrev = 0; 125 | uOpPrev = 0; 126 | iprev = 0; 127 | v = 0; 128 | xtPrev = [0;0]; 129 | utPrev = 0; 130 | 131 | Qop = inv(2*Fs*eye(2)-Aop); % Matrix inversion for op amp stage 132 | Qd = T/2; % "Matrix" inversion for diode stage 133 | 134 | % Main time loop 135 | for n = 1:N 136 | 137 | % Non-constant system matrices 138 | % These need to be re-calculated at each time step in case of parameter 139 | % changes 140 | 141 | % Input stage 142 | Ain = [-1/(R1in*C1in) 0 0; 1/(R2in*C2in) -1/(Rvin*C2in) -1/(R2in*C2in);... 143 | 1/(R2in*C3in) 0 -1/(R2in*C3in)]; 144 | 145 | % Tone stage 146 | Amat11 = ((-R2t-R1t)*(R3t+R4t+Rm1)*Rm2+((-R3t-Rm1)*R2t-(R3t+Rm1)*R1t)*R4t)... 147 | /(C1t*((((R1t+R3t)*R4t+(R3t+Rm1)*R1t+Rm1*R3t)*R2t+R1t*R3t*(R4t+Rm1))*Rm2... 148 | +(((R3t+Rm1)*R1t+Rm1*R3t)*R2t+R1t*R3t*Rm1)*R4t)); 149 | Amat12 = (R1t*(R3t+R4t+Rm1)*Rm2+(R1t*Rm1-R2t*R3t)*R4t)... 150 | /(C1t*((((R1t+R3t)*R4t+(R3t+Rm1)*R1t+Rm1*R3t)*R2t+R1t*R3t*(R4t+Rm1))*Rm2... 151 | +(((R3t+Rm1)*R1t+Rm1*R3t)*R2t+R1t*R3t*Rm1)*R4t)); 152 | Amat21 = ((R1t*Rm2-R2t*R4t)*R3t-R1t*((-Rm1-Rm2)*R4t-Rm1*Rm2))... 153 | /(C2t*((((R2t+Rm1+Rm2)*R4t+Rm2*(R2t+Rm1))*R1t+R2t*((Rm1+Rm2)*R4t+Rm1*Rm2))*R3t... 154 | +R1t*R2t*((Rm1+Rm2)*R4t+Rm1*Rm2))); 155 | Amat22 = (((-R2t-Rm1-Rm2)*R1t+(-R2t-Rm1-Rm2)*R4t+(-R2t-Rm2)*Rm1)*R3t-R1t... 156 | *((R2t+Rm1+Rm2)*R4t+(R2t+Rm2)*Rm1))... 157 | /(C2t*((((R2t+Rm1+Rm2)*R4t+Rm2*(R2t+Rm1))*R1t+R2t*((Rm1+Rm2)*R4t+Rm1*Rm2))... 158 | *R3t+R1t*R2t*((Rm1+Rm2)*R4t+Rm1*Rm2))); 159 | Bvec1 = (R2t*(R3t+R4t+Rm1)*Rm2+(R3t+Rm1)*R2t*R4t)... 160 | /(C1t*((((R1t+R3t)*R4t+(R3t+Rm1)*R1t+Rm1*R3t)*R2t+R1t*R3t*(R4t+Rm1))... 161 | *Rm2+(((R3t+Rm1)*R1t+Rm1*R3t)*R2t+R1t*R3t*Rm1)*R4t)); 162 | Bvec2 = ((R2t+Rm1+Rm2)*R4t+Rm1*Rm2)*R3t... 163 | /(C2t*((((R2t+Rm1+Rm2)*R4t+Rm2*(R2t+Rm1))*R1t+R2t*((Rm1+Rm2)*R4t+Rm1*Rm2))... 164 | *R3t+R1t*R2t*((Rm1+Rm2)*R4t+Rm1*Rm2))); 165 | Cvec1 = R4t*(-R1t*Rm2-R2t*Rm2)*R3t... 166 | /((((R4t+Rm2)*R2t+(R4t+Rm1)*Rm2+R4t*Rm1)*R1t+R2t*((R4t+Rm1)*Rm2+R4t*Rm1))... 167 | *R3t+R2t*((R4t+Rm1)*Rm2+R4t*Rm1)*R1t); 168 | Cvec2 = R4t*(((R2t+Rm1+Rm2)*R1t+R2t*Rm1)*R3t+R1t*R2t*Rm1)... 169 | /((((R4t+Rm2)*R2t+(R4t+Rm1)*Rm2+R4t*Rm1)*R1t+R2t*((R4t+Rm1)*Rm2+R4t*Rm1))... 170 | *R3t+R2t*((R4t+Rm1)*Rm2+R4t*Rm1)*R1t); 171 | Dt = R2t*R3t*R4t*Rm2... 172 | /((((R4t+Rm2)*R2t+(R4t+Rm1)*Rm2+R4t*Rm1)*R1t+R2t*((R4t+Rm1)*Rm2+R4t*Rm1))... 173 | *R3t+R2t*((R4t+Rm1)*Rm2+R4t*Rm1)*R1t); 174 | 175 | At = [Amat11 Amat12; Amat21 Amat22]; 176 | Bt = [Bvec1; Bvec2]; 177 | Ct = [Cvec1 Cvec2]; 178 | 179 | % Input stage 180 | %-------------------------------------- 181 | uIn = in(n); 182 | % State update (trapezoid discretization) 183 | xIn = (2*Fs*eye(3)-Ain)\(2*Fs*eye(3)+Ain)*xInPrev+(2/T*eye(3)-Ain)\(Bin*(uIn+uInPrev)); 184 | % Output equation 185 | y = .1111*(Cin*xIn+Din*uIn); 186 | % Static nonlinearity for op amp clipping 187 | if y <= -1 188 | opOut = -2/3; 189 | elseif y >= 1 190 | opOut = 2/3; 191 | else 192 | opOut = y-(y^3)/3; 193 | end 194 | % Output as input for next stage 195 | uOp = 9*opOut; 196 | 197 | % Op amp stage 198 | %-------------------------------------- 199 | % State update (trapezoid discretization) 200 | xOp = Qop*(2*Fs*eye(2)+Aop)*xOpPrev+Qop*Bop*(uOp+uOpPrev); 201 | % Output equation 202 | y = .1111*(Cop*xOp+Dop*uOp); 203 | % Static nonlinearity for op amp clipping 204 | if y <= -1 205 | opOut = -2/3; 206 | elseif y >= 1 207 | opOut = 2/3; 208 | else 209 | opOut = y-(y^3)/3; 210 | end 211 | % Output as input for next stage 212 | ud = 9*opOut; 213 | 214 | % Diode stage 215 | %-------------------------------------- 216 | error = 1; % Set initial error (just needs to be above tol) 217 | iters = 0; % Reset Newton iterations 218 | 219 | % Constant term from trapezoid discretization 220 | r = Gd*xdPrev+ud+(Gd*Qd*Cd)*iprev; 221 | 222 | % Damped Newton to solve nonlinearity 223 | while (error > tol) && (iters < maxIters) 224 | 225 | i = 2*Is*sinh(v*invVt); % Diode i-v relation 226 | iDer = 2*invVt*Is*cosh(v*invVt); % Derivative of i-v relation 227 | 228 | M = r+(Gd*Qd*Cd+Kd)*i-v; % Function to solve (= 0) 229 | J = (Gd*Qd*Cd+Kd)*iDer-1; % Derivative of function to solve 230 | 231 | step = J\M; % Newton step 232 | 233 | vNew = v-step; % New diode voltage 234 | iNew = 2*Is*sinh(vNew*invVt); % New diode current 235 | MNew = r+(Gd*Qd*Cd+Kd)*iNew-vNew; % Updated function 236 | 237 | % Apply damping if the step goes in the wrong direction 238 | subStep = step; 239 | subIters = 0; 240 | while (norm(MNew) > norm(M)) && (subIters < maxSubIters) 241 | subStep = subStep/2; % Damping reduces step by half 242 | vNew = v-subStep; 243 | iNew = 2*Is*sinh(vNew*invVt); 244 | MNew = r+(Gd*Qd*Cd+Kd)*iNew-vNew; 245 | subIters = subIters+1; 246 | end 247 | error = norm(vNew-v)/norm(v); % Relative error 248 | iters = iters+1; 249 | v = vNew; % Final diode voltage 250 | end 251 | 252 | i = 2*Is*sinh(vNew*invVt); % Diode current 253 | xd = xdPrev+Qd*Cd*(i+iprev); % State update 254 | ut = v; 255 | 256 | % Tone stage 257 | % State update 258 | xt = (2*Fs*eye(2)-At)\(2*Fs*eye(2)+At)*xtPrev+(2*Fs*eye(2)-At)\(Bt*(ut+utPrev)); 259 | % Output equation 260 | y = Ct*xt+Dt*ut; 261 | out(n) = y; 262 | 263 | % Individual stage outputs 264 | if strcmp(plotStageOuts,'on') 265 | Vop1(n) = uOp; 266 | Vop2(n) = ud; 267 | Vd(n) = ut; 268 | end 269 | 270 | % Update previous values for next time step 271 | uInPrev = uIn; 272 | xInPrev = xIn; 273 | uOpPrev = uOp; 274 | xOpPrev = xOp; 275 | xdPrev = xd; 276 | iprev = i; 277 | utPrev = ut; 278 | xtPrev = xt; 279 | end 280 | 281 | if strcmp(playSound,'on') 282 | soundsc(out,Fs) 283 | end 284 | 285 | %% Plots 286 | if strcmp(plotTime,'on') 287 | figure(); 288 | plot(t,out); 289 | xlabel('Time (s)'); 290 | ylabel('Volts'); 291 | end 292 | 293 | if strcmp(plotFreq,'on') 294 | Y = 1e6*abs(fft(out)); 295 | figure(); 296 | semilogx(fx,20*log10(Y)); 297 | xlim([20 20000]); 298 | xlabel('Freq (Hz)'); 299 | ylabel('dB'); 300 | end 301 | 302 | % Individual stage plots 303 | if strcmp(plotStageOuts,'on') 304 | Yop1 = 1e6*abs(fft(Vop1)); 305 | Yop2 = 1e6*abs(fft(Vop2)); 306 | Yd = 1e6*abs(fft(Vd)); 307 | Y = 1e6*abs(fft(out)); 308 | 309 | figure(); 310 | 311 | subplot(4,2,1); 312 | plot(t,Vop1); 313 | title('Input Stage'); 314 | xlabel('Time (s)'); 315 | ylabel('Volts'); 316 | 317 | subplot(4,2,2); 318 | semilogx(fx,20*log10(Yop1)); 319 | xlim([20 20000]); 320 | title('Input Stage'); 321 | xlabel('Freq (Hz)'); 322 | ylabel('dB'); 323 | 324 | subplot(4,2,3); 325 | plot(t,Vop2); 326 | title('Op Amp Stage'); 327 | xlabel('Time (s)'); 328 | ylabel('Volts'); 329 | 330 | subplot(4,2,4); 331 | semilogx(fx,20*log10(Yop2)); 332 | xlim([20 20000]); 333 | title('Op Amp Stage'); 334 | xlabel('Freq (Hz)'); 335 | ylabel('dB'); 336 | 337 | subplot(4,2,5); 338 | plot(t,Vd); 339 | title('Diode Stage'); 340 | xlabel('Time (s)'); 341 | ylabel('Volts'); 342 | 343 | subplot(4,2,6); 344 | semilogx(fx,20*log10(Yd)); 345 | xlim([20 20000]); 346 | title('Diode Stage'); 347 | xlabel('Freq (Hz)'); 348 | ylabel('dB'); 349 | 350 | subplot(4,2,7); 351 | plot(t,out); 352 | title('Tone Stage'); 353 | xlabel('Time (s)'); 354 | ylabel('Volts'); 355 | 356 | subplot(4,2,8); 357 | semilogx(fx,20*log10(Y)); 358 | xlim([20 20000]); 359 | title('Tone Stage'); 360 | xlabel('Freq (Hz)'); 361 | ylabel('dB'); 362 | end 363 | --------------------------------------------------------------------------------