so i tried to follow it with something very simple which i understand DSP-wise, a RC lowpass.
i did it by hand and it seemed to work... so i rewatched the video a pile of times (i kinda couldn't follow once the diodes got into the picture) ... then i went to read elsewhere about NA and MNA, but basically if there are too many components (and by that i mean like more than 3) i better NOT do it by hand because i'm slow and i'll mess it up and then i will only waste time and not get anywhere
so instead, i tried to make a tool that automates the hand-work.
i wrote a CLI program that reads a netlist and spews out the node equations... then i formatted the equations so they can be directly (pretty much) pasted into wxMaxima, which outputs voltage equations
and after a lot of butchering, it worked - at least resistors and capacitors..
unfortunately, in the beginning, i made the prediction that this probably won't work at all and i kept it simple and assumed that components have 2 pins.
i had to leave it and a pile of time passed, i kinda forgot about it and in what state it was.
some months later, i "discovered" it again and wanted to ask here but i had forgotten about it so hard that i couldn't even formulate a question.. i had to re-learn the stuff i forgot and see where i had left it
in the process i fixed some things, broke some other things, i really wanted to add a proper potentiometer to it but that required 3 pins.
so i butchered it heavy and now it can do multi-pin components
i went back to the "circuit to code" video and tried hard to figure out the scary diode stuff
after a long time of head<->wall "physical interaction", i think i got the "hpf-lpf-diode-clipper" circuit from the video working!
and now that i could have more than 2 pins, i really really wanted to have transistors, because there is this one pesky circuit involving a PNP transistor that has kept me up for long days and nights...
i went to the qucs pages which have technical explanations of the transistor... yeah... no way i can do this. just the mere list of parameters is 1 page long o_O
i tried other places, there was nothing simple anywhere.
it's 2026, so i gave up and asked the chatbot - it gladly and very happily synthesized a transistor for me!
and of course, it didn't work (or i didn't know how to make it work).
i spent a pile of times trying different things in that direction (asking again, but more clearly, etc..) every time the result was different code and often it looked obviously broken.
i gave up with that.. but at least in the process, i learned some useful keywords and terminology about what i'm trying to do (and what i'm NOT doing).
well, i hated that i couldn't get anything working out of this, and looked in a few other places and saw some familiar chunks and stuff, so i went back to the qucs page, looked at it and i said to myself "i spent so much time elsewhere, this page now looks small, i could try my best to write down the math from it on the side into a text editor, figure out what goes where, and see if i can make something out of it, and if i can omit some stuff easily (since that page talks about the gummel-poon model, which now i know is not bad, but for audio it might be even overkill, it depends)
well, i did that, and the only unclear thing was the actual contributions to the node equations, since that page gives you some table stuff, not equations
well i asked the chatbot about that, it kinda helped with that, the table is a matrix, i wasn't sure about the polarities of the "ieq" variables but i just threw it in and.... it didn't explode immediately...
so maybe i was getting close to something now.
after some more code butchering, i got a very dumb circuit with an NPN to at least not explode and give some values that are not too far from what i get in falstad.
so i tried a PNP circuit and x_x it exploded of course, turns out the "ieq" had to be inverted, but there were other problems too.
couldn't figure out what's wrong.. i went to see how falstad's transistor works, and hah - saw some familiar stuff (but with slightly different variable names), and i think it uses the "classic SPICE gummel-poon" model, with the "go" and "gm" conductances instead of the "gmf" "gmr".
but i saw some things that i clearly didn't have - one was a mechanism that limits the speed with which vbe and vbc can "move" versus the previous time (so this requires memory) ... i added something like that and it was still exploding
i was looking elsewhere in my code for bugs too and noticed that in my test circuit in the code i feed the oscillator (a ramp) backwards, it's supposed to be going from 11 to 5V but i had it going from 5 to 11V (upward), flipped that and:


looks very familiar
but the transistor is still not quite working properly because it explodes with an upward going ramp.
also, i'm not sure if i've done things properly...
okay, well, i just want to say a huge thank you to KVR people, Andy, mystran, aciddose, kunn, karrikuh, Robin, Urs, and the folks from #musicdsp Hideki, trip-, mike, jazzdude, ashafq, i'm probably forgetting a bunch, forgive me
so this CLI program right now works kinda in 2 steps with some manual hand-work inbetween (but tbh i think it's not bad):
- it reads a netlist, writes node equations in wxMaxima-friendly format into an output file.
- you're supposed to copy/paste them into wxMaxima (and if you know what you're doing you might actually modify some stuff, because i'm not good with Maxima either).
- Maxima solve()s the equations and gives voltage equations and optimize()s them, then this is printed as one line...
- you feed back that one line into the input netlist and re-run the program
- it picks up the voltage equations and fixes the variable names..
so it adds a C++ class with component declarations, init, and a tick() function
initial netlist:
Code: Select all
# circuit-to-code HP+LP+diode-clipper
#float_type: float
#tick_args: const float VS1
VS1 v1 v0
R1 v1 v2, 2.2k
C1 v3 v2, 470n
C2 v3 v0, 10n
R2 v3 v0, 6.8k
D1 v3 v0, isat=1e-15
D2 v0 v3, isat=1e-15
Code: Select all
kill(all);
R1: 2.2*1000;
gr1: 1.0/R1;
C1: 470/1000000000;
gc1: (2*C1)/dt;
C2: 10/1000000000;
gc2: (2*C2)/dt;
R2: 6.8*1000;
gr2: 1.0/R2;
/* ----------- */
v0: 0;
/* ----------- */
eq0: 0 = +c1_g*(v3-v2)-c1_ieq+c2_g*(v3-v0)-c2_ieq+r2_g*(v3-v0)-d1_gd*(v0-v3)-d1_ieq+d2_gd*(v3-v0)+d2_ieq;
eq1: 0 = -r1_g*(v1-v2)-c1_g*(v3-v2)+c1_ieq;
res: solve([eq0, eq1], [v3, v2]), numer;
res2: ratsimp(res);
res2: factor(res2);
res2: expand(res2);
res2: radcan(res2);
res2: horner(res2);
res2: float(res2);
res3: optimize(res2);
res3: subst(["^"=pow],res3);
display2d:false; negsumdispflag:true;
/* set_display('none); */
printf(false,"#maxima_opt: ~s",res3);
/* ----------- */
you get something like this back, copy it and add it to the netlist file:
Code: Select all
#maxima_opt: block([%1,%2,%3,%4,%5],%1:(d2_gd+d1_gd+c2_g+c1_g)*r1_g,%2:1/((r1_g+c1_g)*r2_g+%1+c1_g*d2_gd+c1_g*d1_gd+c1_g*c2_g),%3:c1_g*c2_ieq,%4:c1_g*d1_ieq,%5:-1.0*c1_g*d2_ieq,[[v3 = %2*(c1_g*r1_g*v1+((-1.0*d2_ieq)+d1_ieq+c2_ieq+c1_ieq)*r1_g+%5+%4+%3),v2 = %2*((r1_g*r2_g+%1)*v1-1.0*c1_ieq*r2_g+%5-1.0*c1_ieq*d2_gd+%4-1.0*c1_ieq*d1_gd+%3-1.0*c1_ieq*c2_g)]])"Code: Select all
struct circuit
{
float dt;
static constexpr size_t _max_iterations = 128;
size_t _iter;
as_bzzzt::resistor<float> r1;
as_bzzzt::capacitor<float> c1;
as_bzzzt::capacitor<float> c2;
as_bzzzt::resistor<float> r2;
as_bzzzt::diode<float, 1e-15f, 0.026f> d1;
as_bzzzt::diode<float, 1e-15f, 0.026f> d2;
// ----
float v3;
float v2;
static constexpr float v0 = 0.0;
float v1;
void init(const float Fs)
{
dt = 1.f/Fs;
r1.set_value(2200.f);
c1.init(Fs, 4.7e-07f, 0);
c2.init(Fs, 1e-08f, 0);
r2.set_value(6800.f);
v3 = 0; // TODO initial value!
v2 = 0; // TODO initial value!
}
void tick(const float VS1)
{
v1 = (v0+VS1);
_iter = 0;
while (_iter < _max_iterations)
{
const float _v3 = v3;
const float _v2 = v2;
d1.tick(v0-v3);
d2.tick(v3-v0);
// voltage equations from external math solver:
const float _tmp1 = (d2.gd+d1.gd+c2.g+c1.g)*r1.g;
const float _tmp2 = 1.f/((r1.g+c1.g)*r2.g+_tmp1+c1.g*d2.gd+c1.g*d1.gd+c1.g*c2.g);
const float _tmp3 = c1.g*c2.ieq;
const float _tmp4 = c1.g*d1.ieq;
const float _tmp5 = -1.f*c1.g*d2.ieq;
v3 = _tmp2*(c1.g*r1.g*v1+((-1.f*d2.ieq)+d1.ieq+c2.ieq+c1.ieq)*r1.g+_tmp5+_tmp4+_tmp3);
v2 = _tmp2*((r1.g*r2.g+_tmp1)*v1-1.f*c1.ieq*r2.g+_tmp5-1.f*c1.ieq*d2.gd+_tmp4-1.f*c1.ieq*d1.gd+_tmp3-1.f*c1.ieq*c2.g);
// end of equations
++_iter;
static constexpr float _quiet = 1e-06;
if (std::abs(_v3-v3) > _quiet) { continue; }
else if (std::abs(_v2-v2) > _quiet) { continue; }
else { break; }
}
c1.save(v3-v2);
c2.save(v3-v0);
}
/*
VS1 v1 v0
R1 v1 v2, 2.2k
C1 v3 v2, 470n
C2 v3 v0, 10n
R2 v3 v0, 6.8k
D1 v3 v0, isat=1e-15
D2 v0 v3, isat=1e-15
*/
};
Code: Select all
template<typename T>
struct resistor
{
T g; //!< conductance
inline void set_value(const T ohms) { g = 1 / ohms; }
};
template<typename T>
struct capacitor
{
T g; //!< conductance
T ieq; //!< current memory variable
inline void init(const T Fs, const T farads, const T _ieq = 0) { ieq = _ieq; set_value(farads, Fs); }
inline void set_value(const T farads, const T Fs) { g = Fs*2*farads; }
inline void save(const T vd)
{
ieq += 2*(g*vd-ieq);
}
};
template<typename T, T R_total, T R_min = 0.5>
struct rvar // variable resistor
{
T g;
static constexpr T R_add = R_total-R_min;
/// wiper goes 0 to 1 (and resistance grows)
inline void set_wiper(const T wiper) { g = _calc_vr_g<T, R_add, R_min>(wiper); }
};
template<typename T, T R_total, T R_min = 0.5>
struct rpot // potentiometer
{
T g_a, g_b;
static constexpr T R_add = R_total-(R_min+R_min);
/// wiper goes 0 to 1 (from A to B)
inline void set_wiper(const T wiper) { g_a = _calc_vr_g<T, R_add, R_min>(wiper); g_b = _calc_vr_g<T, R_add, R_min>(1-wiper); }
};
template <typename T, T R_off=1e+6, T R_on = 0.5>
struct sw_spst : public rvar<T, R_off, R_on> // SPST switch
{
using base_t = rvar<T, R_off, R_on>;
static constexpr T lut_g[2] = { _calc_vr_g<T, R_off-R_on, R_on>(0), _calc_vr_g<T, R_off-R_on, R_on>(1) };
inline void set(bool closed) { base_t::g = lut_g[closed]; }
};
template <typename T, T R_off=1e+6, T R_on = 0.5>
struct sw_spdt : public rpot<T, R_off, R_on> // SPDT switch, terminals A COM B
{
using base_t = rpot<T, R_off, R_on>;
static constexpr T lut_g[2] = { _calc_vr_g<T, R_off-R_on, R_on>(0), _calc_vr_g<T, R_off-R_on, R_on>(1) };
/// 0 == open; 1 == A<-COM; 2 == COM->B; 3 == A<-COM->B (fully closed)
inline void set(uint8_t mask)
{
base_t::g_a = lut_g[(mask )&0x01];
base_t::g_b = lut_g[(mask>>1)&0x01];
}
};
template
<
typename T,
T isat = 1e-15, // saturation/reverse current
T vt = 26e-3 // diode thermal voltage
>
struct diode
{
static constexpr T vt_inv = static_cast<T>(1)/vt;
T g; //!< diode conductance
T ieq;
inline void tick(const T vd)
{
// ed = exp(vd/vt);
// id = isat*ed-isat;
// gd = isat*ed/vt;
// ieq = id-gd*vd;
const T ed = std::exp(vd*vt_inv);
const T ed_isat = ed*isat;
g = ed_isat*vt_inv;
ieq = ed_isat-isat-g*vd;
}
};

