#!/usr/bin/env python3
"""Generate Chapter 4 teaching schematics for VCO, VCA, and VCF actions."""
from pathlib import Path
import textwrap
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, FancyArrowPatch, Polygon, Rectangle

ROOT = Path(__file__).resolve().parents[3]
FIG = ROOT / "assets/figures/svg"
FIG.mkdir(parents=True, exist_ok=True)
plt.rcParams["svg.hashsalt"] = "contrapunk-ch04-electronics-v1"
plt.rcParams["svg.fonttype"] = "none"
RED = "#7f1d1d"
TEAL = "#0f6f70"
GOLD = "#b07d21"
INK = "#211d1a"
PAPER = "#fffdf8"


def wire(axis, start, end, color=INK, width=1.8, arrow=False):
    if arrow:
        axis.add_patch(FancyArrowPatch(start, end, arrowstyle="->", mutation_scale=12, linewidth=width, color=color))
    else:
        axis.plot([start[0], end[0]], [start[1], end[1]], color=color, linewidth=width)


def terminal(axis, x, y, label, side="above", color=RED):
    axis.add_patch(Circle((x, y), .07, facecolor=PAPER, edgecolor=color, linewidth=1.8))
    offset = .28 if side == "above" else -.34
    axis.text(x, y + offset, label, ha="center", va="center", color=INK, fontsize=11)


def ground(axis, x, y):
    wire(axis, (x, y), (x, y-.16), width=1.4)
    for index, half_width in enumerate([.22, .14, .07]):
        level = y - .16 - index*.07
        wire(axis, (x-half_width, level), (x+half_width, level), width=1.2)


def capacitor(axis, x, y_top, y_bottom, label="C"):
    middle = (y_top + y_bottom) / 2
    wire(axis, (x, y_top), (x, middle+.08))
    wire(axis, (x-.23, middle+.08), (x+.23, middle+.08), width=2.2)
    wire(axis, (x-.23, middle-.08), (x+.23, middle-.08), width=2.2)
    wire(axis, (x, middle-.08), (x, y_bottom))
    axis.text(x+.35, middle, label, va="center", color=INK, fontsize=12)


def resistor(axis, x, y_top, y_bottom, label="R"):
    segments = 8
    height = y_top - y_bottom
    xs, ys = [x], [y_top]
    for index in range(1, segments):
        xs.append(x + (.12 if index % 2 else -.12))
        ys.append(y_top - height * index / segments)
    xs.append(x)
    ys.append(y_bottom)
    axis.plot(xs, ys, color=INK, linewidth=1.5)
    axis.text(x+.32, (y_top+y_bottom)/2, label, va="center", color=INK, fontsize=12)


def block(axis, x, y, width, height, title, detail):
    axis.add_patch(Rectangle((x, y), width, height, facecolor="#f5eee5", edgecolor=RED, linewidth=1.8))
    axis.text(x+width/2, y+height*.64, title, ha="center", va="center", color=RED, fontsize=12, fontweight="bold")
    axis.text(x+width/2, y+height*.3, detail, ha="center", va="center", color=INK, fontsize=9)


def ota(axis, x, y, width=1.5, height=1.45):
    points = [(x, y-height/2), (x, y+height/2), (x+width, y)]
    axis.add_patch(Polygon(points, closed=True, facecolor="#eef4f0", edgecolor=TEAL, linewidth=2))
    axis.text(x+.19, y+.27, "+", color=INK, fontsize=13)
    axis.text(x+.19, y-.36, "−", color=INK, fontsize=13)
    axis.text(x+.72, y, "OTA", ha="center", va="center", color=TEAL, fontsize=12, fontweight="bold")
    return x+width, y


def finish(axis, title, subtitle, name):
    axis.set_xlim(0, 10)
    axis.set_ylim(0, 4.3)
    axis.set_aspect("equal")
    axis.axis("off")
    axis.set_title(title, loc="left", color=RED, fontsize=16, fontweight="bold", pad=10)
    note = axis.text(.15, .12, textwrap.fill(subtitle, 105), color="#514840", fontsize=9, va="bottom")
    note.set_in_layout(False)
    plt.tight_layout()
    path = FIG / name
    plt.savefig(path, format="svg", metadata={"Date": None}, facecolor=PAPER)
    plt.close()
    path.write_text("\n".join(line.rstrip() for line in path.read_text().splitlines()) + "\n")


# VCO: control changes charging current; state ramps until a reset threshold fires.
fig, axis = plt.subplots(figsize=(10, 4.3))
terminal(axis, .55, 2.35, "pitch control v")
wire(axis, (.62, 2.35), (1.45, 2.35), arrow=True, color=TEAL)
block(axis, 1.45, 1.75, 2.1, 1.2, "voltage → current", "linear or exponential")
wire(axis, (3.55, 2.35), (4.45, 2.35), arrow=True, color=TEAL)
axis.text(4.0, 2.62, "Ictrl", ha="center", color=TEAL, fontsize=10)
wire(axis, (4.45, 2.35), (4.45, 3.25))
capacitor(axis, 4.45, 3.25, .75, "C")
ground(axis, 4.45, .75)
wire(axis, (4.45, 2.35), (5.45, 2.35), color=RED)
terminal(axis, 5.45, 2.35, "ramp output", side="above")
block(axis, 6.0, 1.75, 1.55, 1.2, "threshold", "comparator ΔV")
wire(axis, (5.45, 2.35), (6.0, 2.35), arrow=True)
block(axis, 7.95, 1.75, 1.25, 1.2, "reset", "switch")
wire(axis, (7.55, 2.35), (7.95, 2.35), arrow=True, color=GOLD)
wire(axis, (8.58, 1.75), (8.58, 1.05), color=GOLD)
wire(axis, (8.58, 1.05), (4.85, 1.05), color=GOLD)
wire(axis, (4.85, 1.05), (4.85, 1.48), arrow=True, color=GOLD)
axis.text(6.65, .82, "threshold closes the switch and resets capacitor state", ha="center", color=GOLD, fontsize=9)
terminal(axis, 9.45, 2.35, "pulse output")
wire(axis, (9.2, 2.35), (9.38, 2.35), arrow=True)
axis.text(4.45, 3.78, "C dv/dt = Ictrl", ha="center", color=INK, fontsize=11)
finish(axis, "One teaching VCO topology", "Control voltage changes current; the capacitor ramp and reset create repeated cycles. Musical volts-per-octave control needs an exponential converter before the current source.", "ch04-vco-electronics.svg")

# VCA: an OTA turns control current into transconductance, then a load turns current into voltage.
fig, axis = plt.subplots(figsize=(10, 4.3))
terminal(axis, .6, 2.55, "audio x(t)")
wire(axis, (.67, 2.55), (2.0, 2.55), arrow=True)
wire(axis, (1.45, 1.95), (2.0, 1.95))
ground(axis, 1.45, 1.95)
out_x, out_y = ota(axis, 2.0, 2.25, 2.0, 1.65)
terminal(axis, 3.0, .55, "control current")
wire(axis, (3.0, .62), (3.0, 1.45), arrow=True, color=TEAL)
axis.text(3.18, 1.02, "Ictrl sets gm", color=TEAL, fontsize=10)
wire(axis, (out_x, out_y), (5.7, out_y), arrow=True, color=RED)
axis.text(4.85, 2.55, "iout = gm x", ha="center", color=RED, fontsize=11)
resistor(axis, 5.7, 2.25, .9, "RL")
ground(axis, 5.7, .9)
wire(axis, (5.7, 2.25), (7.5, 2.25), color=RED)
terminal(axis, 7.5, 2.25, "audio y(t)")
block(axis, 8.05, 1.55, 1.5, 1.4, "gain", "g = RL gm")
wire(axis, (7.57, 2.25), (8.05, 2.25), arrow=True, color=GOLD)
axis.text(5.25, 3.45, "small-signal relation: y(t) = g(t)x(t)", ha="center", color=INK, fontsize=12)
finish(axis, "One teaching VCA topology", "An operational transconductance amplifier (OTA) uses control current to change gm. The load resistor converts controlled output current back into voltage.", "ch04-vca-electronics.svg")

# VCF: an OTA drives a capacitor from the input/output difference; control current changes gm and cutoff.
fig, axis = plt.subplots(figsize=(10, 4.3))
terminal(axis, .55, 2.65, "audio input")
wire(axis, (.62, 2.65), (2.0, 2.65), arrow=True)
out_x, out_y = ota(axis, 2.0, 2.35, 2.0, 1.65)
terminal(axis, 3.0, .55, "cutoff control")
wire(axis, (3.0, .62), (3.0, 1.55), arrow=True, color=TEAL)
axis.text(3.18, 1.02, "Ictrl sets gm", color=TEAL, fontsize=10)
wire(axis, (out_x, out_y), (5.35, out_y), arrow=True, color=RED)
capacitor(axis, 5.35, 2.35, .8, "C")
ground(axis, 5.35, .8)
wire(axis, (5.35, 2.35), (7.15, 2.35), color=RED)
block(axis, 7.15, 1.8, 1.25, 1.1, "buffer", "high input Z")
terminal(axis, 9.0, 2.35, "low-pass output")
wire(axis, (8.4, 2.35), (8.93, 2.35), arrow=True, color=RED)
wire(axis, (9.0, 2.28), (9.0, 1.35), color=GOLD)
wire(axis, (9.0, 1.35), (1.55, 1.35), color=GOLD)
wire(axis, (1.55, 1.35), (1.55, 2.05), color=GOLD)
wire(axis, (1.55, 2.05), (2.0, 2.05), arrow=True, color=GOLD)
axis.text(7.2, 1.08, "Vout feedback → OTA −", ha="center", color=GOLD, fontsize=8.5)
axis.text(5.3, 3.45, "C dVout/dt = gm(Vin − Vout)     fc = gm/(2πC)", ha="center", color=INK, fontsize=11)
finish(axis, "One teaching voltage-controlled low-pass topology", "The OTA charges a capacitor according to the input/output difference. Control current changes gm and therefore cutoff. This is not a Moog ladder schematic.", "ch04-vcf-electronics.svg")

print("Generated Chapter 4 VCO, VCA, and VCF teaching schematics.")
