CONTRAPUNK
In this chapter 22 sections

Making Electronic Oscillation Performable

Section I · Theory

Build the modelSound, history, and the mathematics that connects them.

Chapter contract

Prerequisites. Chapter 1 invariants, algebraic rearrangement, and the ability to distinguish pitch from amplitude while listening.

By the end of this chapter, you should be able to:

  • predict how inductance and capacitance affect an LC resonant frequency;
  • trace energy exchange and amplitude trajectories in passive and maintained LC tanks;
  • predict and hear constructive and destructive summation;
  • separate the variable RF oscillator from the audible difference frequency;
  • derive sum and difference components from nonlinear multiplication;
  • distinguish linear beating from heterodyne frequency conversion;
  • describe independent pitch and amplitude gesture mappings;
  • distinguish vibrato, portamento, glissando, and articulation;
  • compare the theremin and Ondes Martenot as different solutions to performability;
  • solve LC-ratio, frequency-difference, cents, interpolation, and phase-accumulation problems;
  • explain every numbered formula from its adjacent plot;
  • extend the Chapter 1 browser oscillator into a gesture and articulation renderer.

The problem: how does a body control an invisible oscillator?

Hold one hand near a pitch antenna and the other near a volume loop. Move either hand without touching the instrument. Pitch or loudness changes continuously.

How can body position control an oscillator without keys or contact?

Lev Termen developed his instrument around 1919–20. Public demonstrations followed by 1920–21, depending on which event a source dates. His instrument turned the performer’s proximity into control of electronic oscillators. It became known as the theremin. Because “first electronic instrument” depends on whether electromechanical predecessors, prototypes, and commercial availability are included, this book uses the narrower description one of the earliest fully electronic musical instruments (Verhulst n.d.b; Skeldon et al. 1998; Alonso-Pérez and Batista n.d.).

Theremin performance control schematic A performer stands between a vertical pitch antenna and a loop-shaped amplitude antenna. Arrows connect hand distance to pitch oscillator frequency and hand height to loudness. hand distancepitch antenna hand heightamplitude loop pitchloudness

Long description. A performer stands between a straight vertical antenna on the right and a horizontal loop on the left. The right hand moves toward and away from the straight antenna to alter pitch. The left hand moves above the loop to alter amplitude. The diagram abstracts the control relationships without reproducing a rights-sensitive historical photograph.

The theremin joins three models:

  1. a resonant electronic oscillator;
  2. a nonlinear frequency converter;
  3. a learned gesture-to-music mapping.

Sustained LC oscillation

LC means inductor–capacitor. The letter L stands for inductance, and C stands for capacitance. An ideal LC resonator exchanges energy between the capacitor’s electric field and the inductor’s magnetic field. Its natural frequency is:

fLC=12πLC.(2.1) f_{\mathrm{LC}}=\frac{1}{2\pi\sqrt{LC}}. \qquad\text{(2.1)}

Symbol Meaning Unit
LL inductance henries
CC effective capacitance farads
fLCf_{\mathrm{LC}} resonant frequency hertz
Equation 2.1 plotted with L=1 mH and capacitance from 50 to 500 pF; frequency falls as capacitance rises.

Long description. The curve falls from about 712 kHz at 50 pF toward 225 kHz at 500 pF. Markers show 503.3 kHz at 100 pF and 251.6 kHz at 400 pF. Quadrupling capacitance halves frequency when inductance stays fixed.

A real oscillator adds active feedback to replace losses. Without it, an idealized LC tank is only a resonator and a real one rings down.

How an LC tank exchanges energy

A tank circuit connects an inductor and capacitor so energy can move between two storage forms. Start by charging the capacitor, then let it discharge through the inductor:

  1. The charged capacitor stores energy in its electric field. Current is initially zero.
  2. The capacitor discharges. Current through the inductor rises, so magnetic-field energy grows.
  3. When the capacitor reaches zero charge, inductor current is largest. The collapsing magnetic field keeps current moving.
  4. That current charges the capacitor with opposite polarity. The exchange then reverses and repeats.

Capacitor charge and inductor current reach their extrema one quarter cycle apart. Their values change sign, but ideal total stored energy stays constant. Real wire resistance, capacitor loss, and radiation remove some energy on every cycle. Damping is this loss of oscillation amplitude over time (Electronics Tutorials n.d.).

An amplitude trajectory is the path a signal’s level takes through time. The tank makes three trajectories easy to compare:

ModelAmplitude trajectoryWhat it sounds like at an audible frequency
Ideal lossless tankConstant foreverA steady sine wave
Passive real tankFalls toward zeroA ringing tone that fades away
Active oscillatorRises from startup, then settles when controlled feedback balances lossA tone that stabilizes; excess gain eventually clips and adds distortion

The complete oscillator therefore needs a frequency-selective tank, an amplifier, and positive feedback returned with the required phase. Startup requires loop gain above one. A stable sustained state replaces about as much energy as the tank loses. Too little feedback decays. Too much grows until circuit limits reshape the waveform (Electronics Tutorials n.d.).

TANK CIRCUIT PLAYGROUND

Exchange energy, lose it, then replace it

Choose audible-scale component values. Compare a passive tank ringing down with a teaching oscillator that starts, replaces the loss, and settles.

Resonance159.2 HzEquation 2.1
The current tank-circuit traces are described below.

With L at 100 mH and C at 10.0 microfarads, resonance is 159.2 Hz. Capacitor charge and inductor current exchange energy one quarter-cycle apart. The passive amplitude trajectory decays with a 1.2-second time constant. The simplified active trajectory rises with an 80-millisecond startup time constant, then settles.

Ready

Hearing safety. Start with a low playback level. These component values deliberately place resonance in the audible range. Theremin pitch tanks use much smaller capacitances and operate at radio frequencies.

Try these checks

  1. Double capacitance. Predict whether frequency rises or falls before reading the result.
  2. Shorten the decay time. Listen and watch the amplitude trajectory lose energy faster.
  3. Compare passive and maintained playback. Frequency stays fixed while one amplitude decays and the other rises, then settles.
  4. Explain why the tank alone rings down but the complete oscillator can continue.
Read the exact source running this playground

This TypeScript implements Equation 2.1, passive loss, a simplified active startup-to-steady envelope, energy-exchange traces, and both listening examples.

export const SAMPLE_RATE = 48_000;
const TAU = 2 * Math.PI;

export function lcFrequency(inductanceMillihenries: number, capacitanceMicrofarads: number) {
  const inductanceHenries = inductanceMillihenries / 1000;
  const capacitanceFarads = capacitanceMicrofarads / 1_000_000;
  return 1 / (TAU * Math.sqrt(inductanceHenries * capacitanceFarads));
}

export function ringEnvelope(timeSeconds: number, decaySeconds: number, maintained = false) {
  const time = Math.max(0, timeSeconds);
  if (maintained) return 1 - Math.exp(-time / 0.08);
  return Math.exp(-time / Math.max(0.01, decaySeconds));
}

export function tankTrace(
  inductanceMillihenries: number,
  capacitanceMicrofarads: number,
  decaySeconds: number,
  seconds: number,
  points = 480
) {
  const frequencyHz = lcFrequency(inductanceMillihenries, capacitanceMicrofarads);
  return Array.from({ length: points }, (_, index) => {
    const timeSeconds = seconds * index / (points - 1);
    const envelope = ringEnvelope(timeSeconds, decaySeconds);
    const phase = TAU * frequencyHz * timeSeconds;
    return {
      timeSeconds,
      capacitorCharge: envelope * Math.cos(phase),
      inductorCurrent: envelope * Math.sin(phase),
      envelope
    };
  });
}

export function renderTank(
  inductanceMillihenries: number,
  capacitanceMicrofarads: number,
  decaySeconds: number,
  maintained: boolean,
  seconds = 2.5
) {
  const frequencyHz = lcFrequency(inductanceMillihenries, capacitanceMicrofarads);
  const frameCount = Math.round(seconds * SAMPLE_RATE);
  const fadeFrames = Math.round(0.012 * SAMPLE_RATE);
  const output = new Float32Array(frameCount);

  for (let frame = 0; frame < frameCount; frame++) {
    const timeSeconds = frame / SAMPLE_RATE;
    const edgeFade = Math.min(1, frame / fadeFrames, (frameCount - 1 - frame) / fadeFrames);
    output[frame] = 0.18 * edgeFade * ringEnvelope(timeSeconds, decaySeconds, maintained)
      * Math.sin(TAU * frequencyHz * timeSeconds);
  }

  return output;
}

Hold LL fixed and increase CC. Frequency falls:

CfLC. C\uparrow\quad\Rightarrow\quad f_{\mathrm{LC}}\downarrow.

The performer’s hand and body contribute a small distributed capacitance involving the antenna, instrument ground, room, and nearby objects. Distributed means that the electric field and its charge-storage effect spread across several physical paths instead of living in one component. A picofarad, abbreviated pF, is one trillionth of a farad. Reported hand-motion changes are commonly fractions of a picofarad to a few picofarads (Skeldon et al. 1998; Alonso-Pérez and Batista n.d.).

Distributed capacitance around a theremin pitch antenna A performer, pitch antenna, instrument chassis, room, and nearby object form several capacitance paths. The changing hand-to-antenna path is one part of the effective capacitance seen by the oscillator. performerpitch antennainstrument chassisnearby object changing hand pathbody-to-reference return pathroom and object pathsfixed chassis path effective C = fixed paths + changing performer paths
Several field paths combine into the effective capacitance seen by the pitch oscillator.

Long description. A performer stands left of a vertical pitch antenna. Dashed paths connect the hand to the antenna, the body to the instrument reference, the antenna to the chassis, and the antenna to a nearby object. The changing performer paths join fixed room and instrument paths to form one effective capacitance.

Diagram provenance. Project-authored conceptual SVG based on the distributed-capacitance discussions by Skeldon and by Alonso-Pérez and Batista. It is a signal explanation, not a literal map of field strength or a circuit-construction diagram.

Instrument ground here means the circuit’s electrical reference and return path. It does not require a separate earth stake. The room, floor, chassis, performer, and nearby objects establish a baseline capacitance. Hand motion changes part of that total. Calibration absorbs much of the baseline so the small changing part can control pitch.

A parallel-plate model gives one useful direction: bringing conductive surfaces closer usually increases capacitance. A hand and rod are not parallel plates, however. Their curved shapes produce fringe fields, and the performer’s body participates in the return path. Use the plate idea to predict direction, not to calculate an exact theremin value.

Illustrative one-picofarad LC frequency shift With teaching values L equals one millihenry, effective capacitance rises from 100 to 101 picofarads as a hand approaches. Equation 2.1 predicts variable oscillator frequency falling from 503.3 to 500.8 kilohertz. Hand farther awayHand one step nearer effective C = 100 pFf = 503.3 kHz illustrative C = 101 pFf = 500.8 kHz closer hand → C rises → variable RF frequency falls
One illustrative picofarad changes a high-frequency LC oscillator by about 2.5 kHz.

Long description. The left panel places a hand farther from the antenna and labels the teaching model 100 pF and 503.3 kHz. The right panel moves the hand nearer and labels 101 pF and 500.8 kHz. A bottom arrow states that smaller distance raises effective capacitance and lowers the variable radio-frequency oscillator.

Diagram provenance. Project-authored calculation from Equation 2.1 with inductance fixed at 1 mH. The one-picofarad change is an illustrative teaching case within the cited scale of reported changes. It is not a measurement of a named theremin.

RF frequency is not audible pitch

Approaching the pitch antenna increases effective capacitance and lowers the variable radio-frequency oscillator’s own frequency. Yet the heard pitch can rise. Why? The output is not that RF oscillator directly. The instrument produces the difference between a variable RF oscillator and a fixed RF oscillator. Depending on how the pair is tuned, lowering the variable oscillator can increase their separation.

Confusing RF frequency with audible difference frequency is a common conceptual failure.

Heterodyning: make a small difference audible

Start with 500 and 501 kHz:

difference: 1 kHz
sum:        1001 kHz

A nonlinear mixer creates both components. A low-pass filter keeps the audible 1 kHz difference and rejects the RF sum.

Let the two oscillators be

x1(t)=cos(2πf1t),x2(t)=cos(2πf2t). x_1(t)=\cos(2\pi f_1t),\qquad x_2(t)=\cos(2\pi f_2t).

The mixer multiplies them. The product-to-sum identity gives

cos(2πf1t)cos(2πf2t)=12cos(2π(f1f2)t)+12cos(2π(f1+f2)t).(2.2) \cos(2\pi f_1t)\cos(2\pi f_2t) =\frac{1}{2}\cos\!\left(2\pi(f_1-f_2)t\right) +\frac{1}{2}\cos\!\left(2\pi(f_1+f_2)t\right). \qquad\text{(2.2)}

Equation 2.2 plotted with scaled frequencies 10 and 11.5; multiplication produces 1.5 and 21.5 components.

Long description. The top panel overlays the two inputs. The middle panel shows their product and its slow 1.5 difference component. The bottom spectrum has lines at 1.5 and 21.5; the shaded low-pass region keeps only the difference.

Figure provenance. Author-generated by assets/figures/src/ch02_heterodyne.py. Frequencies are scaled so individual oscillations remain visible.

Therefore,

fdifference=|f1f2|,fsum=f1+f2.(2.3) f_{\mathrm{difference}}=|f_1-f_2|, \qquad f_{\mathrm{sum}}=f_1+f_2. \qquad\text{(2.3)}

Equation 2.3 plotted with a fixed 500 kHz oscillator and a variable oscillator from 498 to 502 kHz.

Long description. The upper V-shaped curve shows absolute difference falling to zero when both oscillators equal 500 kHz, then rising again. The lower line shows the sum increasing from 998 to 1002 kHz.

Circuit frequencies differ among designs. The 500/501 kHz pair is a teaching example, not a universal theremin specification (Skeldon et al. 1998; Alonso-Pérez and Batista n.d.).

Constructive and destructive summation

Play two equal 220 Hz sine waves. Frequency and amplitude match. Only relative phase changes:

0° apart:   crest + crest       → double amplitude
90° apart:  partial alignment   → √2 times one-wave amplitude
180° apart: crest + trough      → ideal cancellation

Constructive summation, also called constructive interference, occurs when aligned wave values reinforce one another. The ideal 0° pair keeps the same 220 Hz pitch and sine-wave timbre, but its peak amplitude doubles. Relative to either input alone, that is about 6.02 dB greater peak level. Destructive summation occurs when opposite values subtract. At 180°, two perfectly matched waves cancel to digital silence (Smith 2011; Puckette 2007).

Relative phasePeak ratio versus one waveIdeal sound
2.000Same pitch, clearly louder
90°1.414Same pitch, partly reinforced
180°0.000Silence

Point to note. Constructive and destructive summation change the level of this matched-frequency result. They do not create a new sum-frequency or difference-frequency spectrum line. Those new components require nonlinear mixing.

Exact cancellation is a strict model boundary. Frequency, amplitude, waveform, timing, and listening position must match. A small mismatch leaves a quiet residue. Keep playback level low before comparing the louder constructive case. The phase-summation playground lets you hear one wave, reinforcement, partial cancellation, and ideal cancellation without normalizing their levels.

PHASE-SUMMATION PLAYGROUND

Hear phase become level

Keep two 220 Hz sine waves equal. Move only their relative phase, then hear their unnormalized sum.

Peak ratio: 2.000+6.02 dB versus one wave

The current summation is described below.

Two equal 220 Hz sine waves are aligned. Their sum keeps the 220 Hz pitch and has twice the peak amplitude, 6.02 dB above one wave.

Ready

Hearing safety. Start quietly. The 0° sum is intentionally 6.02 dB above one input because the examples are not loudness-normalized.

Try these checks

  1. At 0°, identify the unchanged pitch and increased level.
  2. At 90°, predict the 1.414 peak ratio and +3.01 dB result.
  3. At 180°, explain why ideal playback is silence rather than a new tone.
  4. Move one degree away from 180°. Listen for the small residual that returns.
Read the exact source running this playground

This TypeScript calculates the phase-dependent peak ratio and renders the one-wave and two-wave listening examples.

export const SAMPLE_RATE = 48_000;
const TAU = 2 * Math.PI;

export function phaseSumGain(phaseDegrees: number) {
  const gain = 2 * Math.abs(Math.cos(phaseDegrees * Math.PI / 360));
  return gain < 1e-12 ? 0 : gain;
}

export function phaseSumWaveforms(phaseDegrees: number, points = 480) {
  const phaseRadians = phaseDegrees * Math.PI / 180;
  return Array.from({ length: points }, (_, index) => {
    const phase = TAU * 3 * index / (points - 1);
    const first = Math.sin(phase);
    const second = Math.sin(phase + phaseRadians);
    return { first, second, sum: first + second };
  });
}

export function renderSingleTone(frequencyHz = 220, seconds = 1.4) {
  const frameCount = Math.round(seconds * SAMPLE_RATE);
  const fadeFrames = Math.round(0.012 * SAMPLE_RATE);
  const output = new Float32Array(frameCount);

  for (let frame = 0; frame < frameCount; frame++) {
    const edgeFade = Math.min(1, frame / fadeFrames, (frameCount - 1 - frame) / fadeFrames);
    output[frame] = 0.09 * edgeFade * Math.sin(TAU * frequencyHz * frame / SAMPLE_RATE);
  }
  return output;
}

export function renderPhaseSum(phaseDegrees: number, frequencyHz = 220, seconds = 1.4) {
  const frameCount = Math.round(seconds * SAMPLE_RATE);
  const fadeFrames = Math.round(0.012 * SAMPLE_RATE);
  const phaseRadians = phaseDegrees * Math.PI / 180;
  const output = new Float32Array(frameCount);

  for (let frame = 0; frame < frameCount; frame++) {
    const phase = TAU * frequencyHz * frame / SAMPLE_RATE;
    const edgeFade = Math.min(1, frame / fadeFrames, (frameCount - 1 - frame) / fadeFrames);
    output[frame] = 0.09 * edgeFade * (Math.sin(phase) + Math.sin(phase + phaseRadians));
  }
  return output;
}

When frequencies differ slightly, their relative phase keeps moving. The waves pass repeatedly through constructive and destructive regions, so loudness rises and falls. Add the same two sinusoids instead of multiplying them:

cos(2πf1t)+cos(2πf2t)=2cos(π(f2f1)t)cos(2πf1+f22t).(2.4) \cos(2\pi f_1t)+\cos(2\pi f_2t) =2\cos\!\left(\pi(f_2-f_1)t\right) \cos\!\left(2\pi\frac{f_1+f_2}{2}t\right). \qquad\text{(2.4)}

Equation 2.4 plotted with scaled frequencies 10 and 11.5; the time waveform beats while the spectrum retains only 10 and 11.5.

Long description. The upper panel shows a fast linear sum bounded by a slow dashed envelope. The lower spectrum contains only the two input lines at 10 and 11.5. There is no line at the 1.5 difference frequency.

The amplitude envelope rises and falls at a rate related to the frequency difference, so we hear beating. But a linear spectrum still contains only f1f_1 and f2f_2. A nonlinear mixer is required to place actual spectral energy at |f1f2||f_1-f_2|. This distinction will return when we study ring modulation, intermodulation, and spectral distortion.

The theremin signal path

Pitch-hand capacitance controls a variable RF oscillator; nonlinear mixing and low-pass filtering recover an audible difference, while the other hand shapes amplitude.

Long description. The pitch hand changes hand–antenna capacitance, which changes a variable RF oscillator. That oscillator and a fixed RF oscillator enter a nonlinear mixer. A low-pass filter keeps the difference component. The theremin’s separate volume-hand and loop-antenna path controls amplitude before the speaker.

The canonical two-antenna theremin separates two musical dimensions:

  • Pitch hand: proximity to the vertical rod changes the difference frequency.
  • Volume hand: proximity to the horizontal loop changes amplitude and can reach silence.

Circuit details vary. The design principle is independent control. Continuous pitch without independent amplitude would expose every move between notes as a glide. Amplitude control creates attacks, releases, accents, separation, and rests (Skeldon et al. 1998; Verhulst n.d.b).

Gesture is a mapping, not a natural scale

A classic theremin does not contain invisible piano keys. The relation from hand distance to pitch is continuous, nonlinear, and affected by calibration, body position, component drift, and the surrounding environment.

Idealized progression from hand distance to capacitance, the magnitude of a downward variable-RF shift, and logarithmic musical pitch.

Long description. Three panels show that decreasing hand distance can increase capacitance nonlinearly; increased capacitance increases the magnitude of a downward shift in the variable RF oscillator; and, under the stated below-fixed tuning, the audible difference frequency rises. Equal musical intervals then require a logarithmic rather than linear frequency interpretation. The chain explains why physical uniformity does not guarantee musically uniform spacing.

Figure provenance and limitation. Author-generated by assets/figures/src/ch02_heterodyne.py. The first curve uses a parallel-plate-like inverse-distance analogy and the remaining curves are normalized pedagogical mappings. The rising audible-difference curve assumes the variable oscillator is tuned below the fixed oscillator, so a larger downward RF shift increases their separation. This is not a calibration curve for a specific theremin.

The performer learns a closed feedback loop:

intend pitch → place hand → hear result → correct position

The instrument makes intonation part of continuous motor control. In the high register, equal musical intervals can occupy increasingly compressed physical distances.

Amplitude as a trajectory

Pitch tells us how fast a waveform repeats. Amplitude trajectory tells us how its level changes through time. The shape is often called an amplitude envelope. A steady trajectory sustains one connected sound. A fast rise creates a clear onset. A fall to zero makes a release or rest. Repeating rises and falls separate events even when the pitch path stays unchanged.

The passive tank’s ring-down is a physically caused amplitude trajectory. The theremin performer creates another with the volume hand. The Ondes player presses the intensity key to create one mechanically. The Chapter 2 browser gesture plots pitch and amplitude separately, then uses that same amplitude path for playback. Listen for event shape, not only loudness.

Pitch as a trajectory

Continuous control makes several musical gestures possible. Keep the vocabulary distinct:

  • Vibrato: periodic variation around a target pitch.
  • Portamento: continuous travel between two target pitches.
  • Glissando: an audible sweep through the intervening pitch region; usage overlaps with portamento but often emphasizes the sweep itself.
  • Articulation: shaping the onset, connection, emphasis, and release of events, primarily through amplitude in this design.

A convenient frequency model for vibrato measured in cents is:

f(t)=fc2c(t)/1200,(2.5) f(t)=f_c\,2^{c(t)/1200}, \qquad\text{(2.5)}

Equation 2.5 plotted around f_c=440 Hz; −50, 0, and +50 cents map to 427.47, 440, and 452.89 Hz.

Long description. Frequency rises smoothly and slightly curves upward as cent offset moves from −100 to +100. Three markers show that equal positive and negative cent distances form reciprocal frequency ratios rather than equal hertz differences.

where fcf_c is the center frequency and c(t)c(t) is the time-varying cent offset. For sinusoidal vibrato,

c(t)=Dsin(2πfvt),(2.6) c(t)=D\sin(2\pi f_vt), \qquad\text{(2.6)}

Equation 2.6 plotted with depth D=25 cents and rate f_v=5.5 Hz for one second.

Long description. The cent trajectory completes five and a half cycles in one second. Dashed horizontal lines mark the ±25-cent limits. The curve crosses zero at the center pitch twice per vibrato cycle.

with depth DD cents and vibrato rate fvf_v hertz.

Vibrato does not repair an unknown center pitch. It oscillates around whatever center the performer is actually holding.

The Ondes Martenot: preserve continuity, add landmarks and articulation

Maurice Martenot also used heterodyne generation, but treated performability as an evolving instrument-design problem. The Ondes Martenot premiered at the Paris Opéra on 3 May 1928. Early versions used wire/ring control; around 1930, commonly associated with model no. 4 onward, a seated keyboard became an additional pitch mechanism. Sources often group the instrument into seven principal models through 1975, but hand-built variations make that count a useful classification rather than a perfectly uniform production sequence (Verhulst n.d.a).

A later keyboard-and-ring form of the Ondes Martenot.

Long description. A long wooden instrument has a piano-style keyboard, a control drawer to the performer’s left, and a wire/ring pitch-control path in front of the keys. The keyboard provides landmarks while the ring mechanism preserves continuous pitch movement.

Image credit and licence. Photograph by 30rKs56MaE, 2006. Creative Commons Attribution–ShareAlike 3.0 (30rKs56MaE 2006).

Two pitch interfaces

The mature instrument offers two complementary approaches:

  1. Mobile keyboard. Discrete visual and tactile landmarks support intonation, while lateral key motion permits pitch inflection and vibrato.
  2. Ring and wire. A finger ring mechanically moves along a wire or cable aligned with a pitch reference, supporting continuous glissando, portamento, microtonal inflection, and vibrato.

Calling the second interface a modern ribbon controller obscures its mechanism. It is historically a ring-and-wire linkage.

The intensity key

The right hand selects pitch; the left hand shapes loudness through the spring-loaded touche d’intensité. Its pressure and travel control attack, sustain, accent, and release. This is not merely an on/off key. It is an early haptic amplitude controller (Quartier et al. 2015).

Timbre through controls and diffuseurs

The control drawer selects tone colours and outputs. Martenot also developed specialized loudspeakers or diffuseurs. Different units could provide direct sound, sympathetic-string resonance, metallic resonance, or other sustained colour. Exact first-use dates vary among prototypes, patents, and surviving objects, so this chapter avoids a false tidy chronology. The important architecture is:

pitch interface → heterodyne generator → intensity/timbre controls
→ selected output or diffuseur

The loudspeaker is part of synthesis. A resonant diffuser transforms the spectrum and decay instead of merely making an unchanged signal louder (Verhulst n.d.a; Najnudel et al. 2023).

Comparing the two instruments as interface design

Design question Theremin Ondes Martenot
How is pitch located? Free-space hand position Keyboard or ring/wire
How is loudness shaped? Separate volume antenna Pressure-sensitive intensity key
How is intonation supported? Auditory/proprioceptive learning Landmarks plus auditory feedback
How is timbre extended? Circuit and amplifier character Timbre controls and diffuseurs

Each interface solves a different problem. The theremin maximizes continuous free-space control. The Ondes adds landmarks, tactile resistance, explicit articulation, and resonant outputs.

First-principles lessons for later wavetable instruments

These instruments establish principles that will reappear in contemporary synthesis:

  1. An oscillator and its controller are different systems.
  2. Raw sensor space is not automatically musical parameter space.
  3. Pitch and amplitude need independent trajectories.
  4. Smoothing, calibration, and feedback determine playability.
  5. The output resonator or filter participates in timbre.
  6. Continuous control creates expression and instability at the same time.

A future wavetable-position control will face the same design problem as the pitch antenna: should equal physical movement produce equal frame-index movement, equal spectral change, or approximately equal perceived change?

Section II · Practice

Use the modelCalculate, listen, build, diagnose, and check your understanding.

Mathematical concepts 2: ratios, differences, and pitch trajectories

LC scaling by ratio

If capacitance quadruples while inductance stays fixed, resonant frequency halves. For two LC states,

f2f1=L1C1L2C2.(2.7) \frac{f_2}{f_1}=\sqrt{\frac{L_1C_1}{L_2C_2}}. \qquad\text{(2.7)}

Equation 2.7 plotted with fixed inductance and capacitance ratio C_2/C_1 from 0.25 to 4.

Long description. The frequency ratio falls as capacitance ratio rises. Markers show equal capacitance giving ratio 1 and four times the capacitance giving frequency ratio 0.5.

With fixed inductance, this reduces to f2/f1=C1/C2f_2/f_1=\sqrt{C_1/C_2}. Use the ratio before inserting small farad values.

Difference, sum, and units

Subtraction is meaningful only after units match. For 260.000 kHz and 259.560 kHz,

260.000259.560=0.440kHz=440Hz. 260.000-259.560=0.440\ \mathrm{kHz}=440\ \mathrm{Hz}.

Multiplication creates both this difference and the 519.560 kHz sum. Linear addition creates neither new spectral line, even though its time-domain envelope beats.

Octaves and cents

220 Hz to 440 Hz is one octave. So is 440 Hz to 880 Hz. Each octave is a 2:1 ratio and contains 1200 cents. A cent displacement cc uses

ffc=2c/1200.(2.8) \frac{f}{f_c}=2^{c/1200}. \qquad\text{(2.8)}

Equation 2.8 plotted from −1200 to +1200 cents; the frequency ratios are 0.5, 1, and 2 at octave landmarks.

Long description. The curve rises exponentially from half the center frequency at −1200 cents, through the center frequency at 0 cents, to twice the center frequency at +1200 cents.

The same cent interval covers more hertz at a higher center frequency. Musical pitch spacing is logarithmic, not linear in hertz.

Logarithmic interpolation

The musical midpoint between 220 and 880 Hz is 440 Hz, not 550 Hz. Equal progress in log-frequency space uses

f(u)=fa(fbfa)u,0u1.(2.9) f(u)=f_a\left(\frac{f_b}{f_a}\right)^u, \qquad 0\le u\le1. \qquad\text{(2.9)}

Equation 2.9 plotted from 220 to 880 Hz; logarithmic interpolation reaches 440 Hz halfway while linear-hertz interpolation reaches 550 Hz.

Long description. Both curves start at 220 Hz and end at 880 Hz. The solid logarithmic path stays below the dashed linear path between the endpoints. At normalized time 0.5, their marked values are 440 and 550 Hz.

Substitute u=1/2u=1/2 to verify the 440 Hz midpoint.

Discrete phase accumulation

At 48 kHz, a 440 Hz oscillator advances by 2π(440/48,000)0.05762\pi(440/48{,}000)\approx0.0576 radians per sample. A changing frequency requires a new increment at every sample:

θ[n+1]=θ[n]+2πf[n]Fs,x[n]=sin(θ[n]).(2.10) \theta[n+1]=\theta[n]+2\pi\frac{f[n]}{F_s}, \qquad x[n]=\sin(\theta[n]). \qquad\text{(2.10)}

Equation 2.10 plotted at 48 kHz for 1920 samples; f[0]=220 Hz and f[1919]=880 Hz.

Long description. The top panel shows 1920 endpoint-inclusive frequency values over sample times 0 through 39.979 ms. The middle panel shows each phase increment. The bottom uses θ[0]=0\theta[0]=0, plots x[n]=sin(θ[n])x[n]=\sin(\theta[n]), and applies increment nn to obtain phase n+1n+1. Cycles compress as frequency rises.

Substituting a changing f(t)f(t) directly into sin(2πf(t)t)\sin(2\pi f(t)t) generally adds an unintended tf(t)t f'(t) contribution to instantaneous frequency. The browser oscillator therefore accepts a new frequency every sample while preserving phase.

Concept check: calculate and predict

  1. With fixed inductance, capacitance becomes four times larger. Find f2/f1f_2/f_1.
  2. Find the difference and sum of 260.000 kHz and 259.560 kHz in hertz.
  3. Explain why adding those two RF sinusoids does not create a 440 Hz spectrum line.
  4. Calculate frequencies 50 cents below and above A4 = 440 Hz.
  5. Find the logarithmic midpoint of a glide from 220 to 880 Hz and compare it with the linear-hertz midpoint.
  6. At Fs=48,000F_s=48{,}000 Hz and constant f[n]=440f[n]=440 Hz, find the phase increment per sample in radians. Explain how Equation 2.10 changes for a glide.
  7. A fixed oscillator is 300.000 kHz. Find the below-fixed variable frequency for D5 = 587.33 Hz.
  8. For each plot from Equations 2.1 through 2.10, name the axes or panels, the fixed parameters, and one prediction visible before calculation.

Worked example: heterodyne pitch from oscillator frequencies

A fixed oscillator runs at 260.000 kHz. The variable oscillator is initially 259.560 kHz.

The audible difference is:

|260.000259.560|kHz=0.440kHz=440Hz. |260.000-259.560|\ \mathrm{kHz}=0.440\ \mathrm{kHz}=440\ \mathrm{Hz}.

The hand increases effective capacitance so that the variable RF frequency falls to 259.340 kHz:

|260.000259.340|kHz=0.660kHz=660Hz. |260.000-259.340|\ \mathrm{kHz}=0.660\ \mathrm{kHz}=660\ \mathrm{Hz}.

The variable oscillator went down by 220 Hz while the audible difference pitch went up by 220 Hz. This is the distinction between the RF state and the musical output state.

Listening station 2: steady pitch, glide, vibrato, articulation

Open the Chapter 2 listening file:

assets/audio/ch02/ch02-continuous-control-studies.wav

The studies are separated by silence:

  1. steady 330 Hz reference;
  2. logarithmic glide from 220 to 660 Hz;
  3. 5.5 Hz vibrato around 440 Hz with a depth of ±25 cents;
  4. steady 392 Hz with independent amplitude articulation.

Procedure

  1. Draw pitch versus time for each segment without using a spectrum analyzer.
  2. Draw amplitude versus time separately.
  3. Identify which segment changes pitch but not intended amplitude.
  4. Identify which changes amplitude but not intended pitch.
  5. Describe the musical function of the silence between segments.
  6. For the vibrato example, estimate whether rate or depth is more immediately noticeable.

The exercise trains separation of dimensions. A wavetable synthesizer later adds a third trajectory: timbre position. The ear must distinguish it from pitch and level.

Song study 2: one phrase, two articulations

LAB 02

Turn oscillator difference into gesture

Hear addition and multiplication, then shape a continuous pitch path.

Difference: 440 HzSum: 1560 Hz

The current pitch and amplitude trajectories are described below the plot.

Pitch moves logarithmically from 220 to 880 Hz with 18-cent vibrato at 5.0 Hz. The amplitude trajectory is one continuous event.

Ready

Try these checks

  1. Set the variable oscillator to 780 Hz. Predict the difference before reading it.
  2. Compare addition with multiplication. Listen for the new difference component.
  3. Set vibrato depth to zero. The glide remains continuous because phase still accumulates.
  4. Switch only articulation. The pitch path stays fixed while the amplitude trajectory changes.
Read the exact source running this lab

This TypeScript implements amplitude trajectories and Equations 2.3, 2.8, 2.9, and 2.10.

export const SAMPLE_RATE = 48_000;
const TAU = 2 * Math.PI;

export function heterodyneComponents(aHz: number, bHz: number) {
  return { differenceHz: Math.abs(aHz - bHz), sumHz: aHz + bHz };
}

export function centsRatio(cents: number) {
  return 2 ** (cents / 1200);
}

export function logFrequencyLerp(startHz: number, endHz: number, position: number) {
  const u = Math.max(0, Math.min(1, position));
  return startHz * (endHz / startHz) ** u;
}

export function renderHeterodyne(
  multiply: boolean,
  fixedHz = 1000,
  variableHz = 560,
  seconds = 1.4
) {
  const frameCount = Math.round(seconds * SAMPLE_RATE);
  const output = new Float32Array(frameCount);
  const fadeFrames = Math.round(0.012 * SAMPLE_RATE);

  for (let frame = 0; frame < frameCount; frame++) {
    const time = frame / SAMPLE_RATE;
    const fixed = Math.cos(TAU * fixedHz * time);
    const variable = Math.cos(TAU * variableHz * time);
    const signal = multiply ? fixed * variable : 0.5 * (fixed + variable);
    const envelope = Math.min(1, frame / fadeFrames, (frameCount - 1 - frame) / fadeFrames);
    output[frame] = 0.18 * envelope * signal;
  }
  return output;
}

export function frequencyTrajectory(
  startHz: number,
  endHz: number,
  vibratoCents: number,
  vibratoHz: number,
  points = 480
) {
  return Array.from({ length: points }, (_, index) => {
    const position = index / (points - 1);
    const baseHz = logFrequencyLerp(startHz, endHz, position);
    return baseHz * centsRatio(vibratoCents * Math.sin(TAU * vibratoHz * 2 * position));
  });
}

export function amplitudeAtTime(timeSeconds: number, totalSeconds: number, detached: boolean) {
  let envelope = Math.min(1, timeSeconds / 0.012, Math.max(0, totalSeconds - timeSeconds) / 0.012);
  if (detached) {
    const localTime = timeSeconds % 0.4;
    envelope *= localTime < 0.3
      ? Math.min(1, localTime / 0.008, (0.3 - localTime) / 0.008)
      : 0;
  }
  return Math.max(0, envelope);
}

export function amplitudeTrajectory(detached: boolean, points = 480, seconds = 2) {
  return Array.from({ length: points }, (_, index) =>
    amplitudeAtTime(seconds * index / (points - 1), seconds, detached)
  );
}

export function renderGesture(
  startHz: number,
  endHz: number,
  vibratoCents: number,
  vibratoHz: number,
  detached: boolean
) {
  const seconds = 2;
  const frameCount = seconds * SAMPLE_RATE;
  const output = new Float32Array(frameCount);
  let phase = 0;

  for (let frame = 0; frame < frameCount; frame++) {
    const time = frame / SAMPLE_RATE;
    const position = frame / (frameCount - 1);
    const baseHz = logFrequencyLerp(startHz, endHz, position);
    const frequencyHz = baseHz * centsRatio(vibratoCents * Math.sin(TAU * vibratoHz * time));
    const sample = Math.sin(phase) + 0.16 * Math.sin(2 * phase);
    phase = (phase + TAU * frequencyHz / SAMPLE_RATE) % TAU;

    const envelope = frame === frameCount - 1 ? 0 : amplitudeAtTime(time, seconds, detached);
    output[frame] = 0.18 * envelope * sample / 1.16;
  }
  return output;
}

Open the browser-generated Chapter 2 gesture study. It begins with two one-second scaled examples separated by silence. Linear addition of 1000 and 560 Hz retains only those two spectrum lines; multiplication of the same pair produces components at 440 and 1560 Hz. A 48 kHz renderer cannot represent the historical RF oscillators directly, so these segments demonstrate the algebra rather than a literal theremin circuit.

The next two passes use the public-domain NEW BRITAIN pitch incipit 5-1-3-1-3-2-1-6-5-5, commonly associated with Amazing Grace. The Library of Congress documents the tune’s 1835 pairing with John Newton’s text; the exercise uses no lyrics and no modern arrangement (“Timeline of the Song Amazing Grace” n.d.; “Tune: New Britain” n.d.). Durations are deliberately simplified.

  1. Continuous pass: portamento near each boundary, nearly continuous amplitude, final-note vibrato.
  2. Articulated pass: the same pitch centers and durations, amplitude closure between events, an accent on the fifth event, and final-note vibrato.

Listening questions

  1. Which pass makes the number of note events easiest to count?
  2. Which pass makes the phrase feel like one continuous gesture?
  3. Does the accent change pitch, amplitude, or both?
  4. Can you hear the final vibrato as motion around a center rather than travel to a new note?
  5. Draw separate pitch and amplitude trajectories for both passes.
  6. Name one musical situation where you would choose each articulation.

Figure lab 2: inspect and alter continuous control

Run:

python3 assets/figures/src/ch02_heterodyne.py

Verify regeneration of:

assets/figures/svg/ch02-heterodyne-principle.svg
assets/figures/svg/ch02-gesture-to-pitch.svg
assets/audio/ch02/ch02-continuous-control-studies.wav

Then make one change at a time:

  1. change vibrato depth while holding rate fixed;
  2. restore depth and change rate;
  3. change the glide from logarithmic frequency interpolation to linear frequency interpolation.

Listen for how “equal progress through time” differs between linear hertz and logarithmic musical pitch.

Browser lab 2: extend the oscillator

Use the gesture lab on this page. It runs the published TypeScript directly in the browser and exposes the same source beside the controls.

  1. verify the 440 Hz difference and 599.560 kHz sum shown by the model;
  2. explain why the audible demonstration uses scaled frequencies instead of real RF values;
  3. change the glide timing and predict where each transition begins;
  4. compare continuous and detached articulation without changing the pitch list.

Practice: choose the variable oscillator

A fixed RF oscillator runs at 300.000 kHz. Choose a variable oscillator below it to produce each audible target:

A4: 440.00 Hz
C5: 523.25 Hz
E5: 659.25 Hz
  1. Calculate all three variable RF frequencies.
  2. Explain why the variable oscillator moves downward as the audible note rises in this arrangement.
  3. State what the low-pass filter must reject.
  4. Check one answer by reversing the subtraction.

Musical application 2: separate pitch from articulation

Draw or perform a four-note phrase twice.

  1. In version A, connect every pitch with continuous portamento and keep amplitude nearly constant.
  2. In version B, use the same pitch centers but close the amplitude between notes and accent the third note.
  3. Add a small vibrato only to the final sustained note in both versions.
  4. Record separate pitch-versus-time and amplitude-versus-time sketches.
  5. Ask a listener which version communicates four events more clearly and which feels more continuous.

This is the interface lesson of the theremin and Ondes Martenot: musical phrasing emerges from coordinated but independently controllable trajectories.

Challenge: diagnose the signal model

A student writes:

“The hand blocks a radio beam. More capacitance raises the LC oscillator frequency. Adding two radio-frequency sine waves automatically creates a difference-frequency spectral line. The Ondes Martenot is just a theremin with piano keys.”

Classify and correct every error. Your answer must distinguish the physical sensor, LC relation, linear superposition, nonlinear mixing, and performance interfaces.

Chapter 2 readiness gate

Commit answers before consulting the answer invariants.

  1. If effective capacitance rises while inductance remains fixed, what happens to the variable RF oscillator, and why may audible theremin pitch nevertheless rise?
  2. Use Equation 2.2 for 500 kHz and 501 kHz. What mixer components result, and which survives an audio low-pass filter?
  3. Why can linear addition sound like beating without containing a difference-frequency spectral line?
  4. What do equal 220 Hz waves produce at 0° and 180° relative phase, and how do the two results sound?
  5. Why does a passive real LC tank ring down while a maintained oscillator can hold a steady amplitude trajectory?
  6. Describe the standard two-hand theremin mapping and explain how amplitude control turns continuous pitch into articulated musical events.
  7. Distinguish vibrato, portamento, and articulation using one gesture example for each.
  8. Name the two mature Ondes pitch interfaces and state what the intensity key and a resonant diffuser add.

Chapter 2 invariants

  • An LC oscillator’s frequency decreases as effective capacitance increases.
  • Audible theremin pitch is a difference frequency, not the variable RF frequency itself.
  • Nonlinear mixing creates sum and difference components; linear addition creates beats without those new spectral lines.
  • Equal-frequency waves reinforce at 0° relative phase and cancel at 180° only when their amplitudes and waveforms also match.
  • A passive real tank rings down; correctly phased active feedback can replace loss and maintain oscillation.
  • Classic theremin pitch is continuous and requires a learned feedback loop.
  • Independent amplitude trajectories create articulation, accents, and silence.
  • The Ondes Martenot combines continuous pitch, landmarks, haptic amplitude, timbre selection, and resonant output.
  • A controller’s physical coordinate is not automatically a perceptually uniform musical coordinate.

Chapter 2 glossary additions

Term Working definition
Amplitude envelope A curve or sequence that describes how amplitude changes through time.
Amplitude trajectory The path a signal’s level follows through time, including attack, sustain, decay, and release behavior.
Articulation Shaping of onset, connection, accent, and release.
Beat Slow amplitude variation caused by linear addition of nearby frequencies.
Capacitance Ability to store electric charge; increased effective CC lowers LC frequency.
Distributed capacitance Combined charge-storage effect of several electric-field paths among the performer, antenna, instrument reference, room, and nearby objects.
Cent Logarithmic pitch unit; 1200 cents equal one octave.
Constructive summation Addition in which aligned waveform values reinforce one another; two matched in-phase waves double peak amplitude.
Damping Loss that makes passive oscillation amplitude decrease through time.
Destructive summation Addition in which opposite waveform values reduce one another; two perfectly matched waves 180° apart cancel.
Difference frequency |f1f2||f_1-f_2|, created as a spectral component by nonlinear mixing.
Diffuseur Specialized Ondes Martenot output that changes spectrum and decay.
Electrical ground Circuit reference and return path used to compare voltages; it is not necessarily a separate earth connection.
Heterodyning Nonlinear mixing used to create sum and difference frequencies.
Inductance Magnetic energy-storage property denoted LL and measured in henries.
LC oscillator Inductor–capacitor feedback oscillator whose resonant frequency depends on inductance LL and capacitance CC.
Logarithmic interpolation Movement by equal frequency ratios rather than equal hertz steps.
Low-pass filter Stage that passes low frequencies and rejects higher components.
Mixer Nonlinear stage that combines inputs and creates intermodulation products.
Phase accumulator Sample-by-sample sum of phase increments used to generate a waveform.
Picofarad (pF) One trillionth of a farad, or 10−12 farad.
Portamento Continuous travel between target pitches.
Positive feedback Returning part of an output with a phase and level that reinforce an oscillation; controlled feedback can replace tank-circuit loss.
Radio frequency (RF) Oscillator range above the audible output used in the heterodyne system.
Sum frequency f1+f2f_1+f_2, created with the difference component by nonlinear mixing.
Tank circuit An inductor–capacitor resonator that exchanges electric-field and magnetic-field energy at its natural frequency.
Vibrato Periodic pitch variation around a center pitch.

IMPLEMENTATION NOTEBOOK

Chapter 2 source and generated output

Each website-owned Python or Mermaid source appears beside the deterministic output it generated. The browser labs above publish their executed TypeScript separately.

SOURCE AND OUTPUT

Heterodyne and gesture studies

This Python program generates both control figures and the steady-tone, glide, vibrato, and articulation listening file.

Output

Nearby oscillators, their nonlinear product, and sum and difference spectrum
Generated heterodyne-principle figure.
Hand distance mapped through capacitance and RF shift to pitch
Generated gesture-to-pitch figure.

Source

ch02_heterodyne.py

assets/figures/src/ch02_heterodyne.pyPython

#!/usr/bin/env python3
"""Generate Chapter 2 heterodyne/control figures and gesture-listening audio."""

from __future__ import annotations

import wave
from pathlib import Path

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np

ROOT = Path(__file__).resolve().parents[3]
SVG = ROOT / "assets/figures/svg"
AUDIO = ROOT / "assets/audio/ch02"
SVG.mkdir(parents=True, exist_ok=True)
AUDIO.mkdir(parents=True, exist_ok=True)

plt.rcParams.update(
    {
        "font.family": "DejaVu Sans",
        "font.size": 9,
        "axes.spines.top": False,
        "axes.spines.right": False,
        "axes.titleweight": "bold",
        "svg.fonttype": "none",
    }
)
RED = "#7F1D1D"
TEAL = "#0F766E"
BLUE = "#1D4ED8"
PURPLE = "#7C3AED"


def normalize_rms(signal: np.ndarray, target: float = 0.17) -> np.ndarray:
    signal = signal - np.mean(signal)
    rms = np.sqrt(np.mean(signal**2))
    return signal * (target / rms) if rms else signal


def fade(signal: np.ndarray, sample_rate: int, seconds: float = 0.03) -> np.ndarray:
    frames = min(int(sample_rate * seconds), len(signal) // 2)
    ramp = np.linspace(0.0, 1.0, frames)
    signal = signal.copy()
    signal[:frames] *= ramp
    signal[-frames:] *= ramp[::-1]
    return signal


def write_wav(path: Path, signal: np.ndarray, sample_rate: int = 48_000) -> None:
    signal = np.clip(signal, -0.98, 0.98)
    pcm = np.round(signal * 32767).astype("<i2")
    with wave.open(str(path), "wb") as output:
        output.setnchannels(1)
        output.setsampwidth(2)
        output.setframerate(sample_rate)
        output.writeframes(pcm.tobytes())


# Figure 1: scaled heterodyne example. Frequencies are deliberately low enough to see.
t = np.linspace(0.0, 1.4, 6000, endpoint=False)
f_fixed = 10.0
f_variable = 11.5
fixed = np.cos(2 * np.pi * f_fixed * t)
variable = np.cos(2 * np.pi * f_variable * t)
mixed = fixed * variable
low_component = 0.5 * np.cos(2 * np.pi * (f_variable - f_fixed) * t)
high_component = 0.5 * np.cos(2 * np.pi * (f_variable + f_fixed) * t)

fig, axes = plt.subplots(3, 1, figsize=(7.2, 7.1), constrained_layout=True)
axes[0].plot(t, fixed, color=BLUE, linewidth=0.9, label="fixed oscillator: 10")
axes[0].plot(t, variable, color=TEAL, linewidth=0.9, alpha=0.85, label="variable oscillator: 11.5")
axes[0].set_xlim(0, 0.55)
axes[0].set_ylabel("amplitude")
axes[0].set_title("Two nearby oscillators")
axes[0].legend(loc="upper right", frameon=False, ncol=2)

axes[1].plot(t, mixed, color="#111827", linewidth=0.75, label="mixer output")
axes[1].plot(t, low_component, color=RED, linewidth=1.8, label="difference component: 1.5")
axes[1].set_xlim(0, 1.4)
axes[1].set_ylabel("amplitude")
axes[1].set_title("Multiplication creates a slowly varying difference component")
axes[1].legend(loc="upper right", frameon=False)

markerline, stemlines, baseline = axes[2].stem(
    [f_variable - f_fixed, f_variable + f_fixed], [0.5, 0.5], basefmt=" "
)
plt.setp(markerline, color=PURPLE, markersize=5)
plt.setp(stemlines, color=PURPLE, linewidth=1.5)
axes[2].axvspan(0, 5, color="#FEE2E2", alpha=0.8, label="low-pass output region")
axes[2].set_xlim(0, 25)
axes[2].set_ylim(0, 0.62)
axes[2].set_xlabel("frequency in scaled units")
axes[2].set_ylabel("relative amplitude")
axes[2].set_title("The mixer contains difference and sum frequencies")
axes[2].legend(loc="upper right", frameon=False)
fig.suptitle("Heterodyning: the audible pitch is a frequency difference", fontsize=12, fontweight="bold")
fig.savefig(SVG / "ch02-heterodyne-principle.svg", bbox_inches="tight")
plt.close(fig)

# Figure 2: idealized control chain. The capacitance curve is a teaching analogy, not a theremin calibration.
distance = np.linspace(0.12, 1.0, 500)
capacitance = 1.0 / distance
capacitance = (capacitance - capacitance.min()) / (capacitance.max() - capacitance.min())
downward_shift_magnitude = np.sqrt(capacitance + 0.03)
downward_shift_magnitude = (
    (downward_shift_magnitude - downward_shift_magnitude.min())
    / (downward_shift_magnitude.max() - downward_shift_magnitude.min())
)
pitch_octaves = np.log2(1.0 + 3.0 * downward_shift_magnitude)

fig, axes = plt.subplots(1, 3, figsize=(8.2, 3.0), constrained_layout=True)
axes[0].plot(distance, capacitance, color=TEAL, linewidth=2)
axes[0].invert_xaxis()
axes[0].set(xlabel="hand approaches antenna →", ylabel="relative capacitance", title="Distance → capacitance")
axes[1].plot(capacitance, downward_shift_magnitude, color=BLUE, linewidth=2)
axes[1].set(
    xlabel="relative capacitance",
    ylabel="magnitude of downward RF shift",
    title="Capacitance → RF shift",
)
axes[2].plot(downward_shift_magnitude, pitch_octaves, color=RED, linewidth=2)
axes[2].set(
    xlabel="relative audible difference frequency",
    ylabel="pitch in octaves",
    title="Difference → octaves",
)
fig.suptitle("Why a playable electronic instrument needs a calibrated gesture map", fontsize=11, fontweight="bold")
fig.savefig(SVG / "ch02-gesture-to-pitch.svg", bbox_inches="tight")
plt.close(fig)

# Listening file: steady tone, portamento, vibrato, and independent volume articulation.
sample_rate = 48_000
silence = np.zeros(int(sample_rate * 0.4))
segments = []

def expressive_tone(frequency: np.ndarray, amplitude: np.ndarray) -> np.ndarray:
    phase = 2 * np.pi * np.cumsum(frequency) / sample_rate
    tone = np.sin(phase) + 0.22 * np.sin(2 * phase) + 0.08 * np.sin(3 * phase)
    return normalize_rms(tone * amplitude)

# Steady reference.
duration = 2.0
time = np.arange(int(sample_rate * duration)) / sample_rate
segments += [fade(expressive_tone(np.full_like(time, 330.0), np.ones_like(time)), sample_rate), silence]

# Continuous glissando: interpolation in log-frequency gives equal octave motion.
duration = 3.0
time = np.arange(int(sample_rate * duration)) / sample_rate
frequency = 220.0 * np.power(660.0 / 220.0, time / duration)
segments += [fade(expressive_tone(frequency, np.ones_like(time)), sample_rate), silence]

# Vibrato, ±25 cents around A4.
duration = 3.0
time = np.arange(int(sample_rate * duration)) / sample_rate
cents = 25.0 * np.sin(2 * np.pi * 5.5 * time)
frequency = 440.0 * np.power(2.0, cents / 1200.0)
segments += [fade(expressive_tone(frequency, np.ones_like(time)), sample_rate), silence]

# Independent amplitude articulation while pitch stays steady.
duration = 3.0
time = np.arange(int(sample_rate * duration)) / sample_rate
amplitude = np.clip(0.5 + 0.48 * np.sin(2 * np.pi * 1.2 * time - np.pi / 2), 0.02, 1.0)
segments += [fade(expressive_tone(np.full_like(time, 392.0), amplitude), sample_rate)]

write_wav(AUDIO / "ch02-continuous-control-studies.wav", np.concatenate(segments), sample_rate)
print("Generated Chapter 2 figures and audio.")

SOURCE AND OUTPUT

Equation figures

One deterministic Python program renders the exact plots paired with the numbered formulas. Equation 2.2 is rendered by the heterodyne program above.

Output

Equation 2.1 LC frequency
Equation 2.1 LC frequency
Equation 2.3 sum and difference
Equation 2.3 sum and difference
Equation 2.4 linear beating
Equation 2.4 linear beating
Equation 2.5 cents to frequency
Equation 2.5 cents to frequency
Equation 2.6 vibrato in cents
Equation 2.6 vibrato in cents
Equation 2.7 LC ratio
Equation 2.7 LC ratio
Equation 2.8 cents ratio
Equation 2.8 cents ratio
Equation 2.9 logarithmic interpolation
Equation 2.9 logarithmic interpolation
Equation 2.10 phase accumulation
Equation 2.10 phase accumulation

Source

formula_visuals_ch02.py

assets/figures/src/formula_visuals_ch02.pyPython

#!/usr/bin/env python3
"""Generate deterministic visuals for the numbered formulas in Chapter 2."""

from __future__ import annotations

from pathlib import Path

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np

ROOT = Path(__file__).resolve().parents[3]
SVG = ROOT / "assets/figures/svg"
SVG.mkdir(parents=True, exist_ok=True)

RED = "#7F1D1D"
TEAL = "#0F766E"
BLUE = "#1D4ED8"
PURPLE = "#7C3AED"
GOLD = "#B45309"
GRAY = "#6B7280"
BLACK = "#111827"

plt.rcParams.update(
    {
        "font.family": "DejaVu Sans",
        "font.size": 9,
        "axes.spines.top": False,
        "axes.spines.right": False,
        "axes.titleweight": "bold",
        "svg.fonttype": "none",
    }
)


def save(fig: plt.Figure, filename: str) -> None:
    fig.savefig(SVG / filename, bbox_inches="tight")
    plt.close(fig)


def zero_line(axis: plt.Axes) -> None:
    axis.axhline(0.0, color=GRAY, linewidth=0.6)


# Equation 2.1: LC resonance falls as capacitance rises.
inductance = 1e-3
capacitance_pf = np.linspace(50.0, 500.0, 900)
capacitance_f = capacitance_pf * 1e-12
lc_frequency_khz = 1.0 / (2 * np.pi * np.sqrt(inductance * capacitance_f)) / 1000
fig, axis = plt.subplots(figsize=(7.2, 3.2), constrained_layout=True)
axis.plot(capacitance_pf, lc_frequency_khz, color=BLUE, linewidth=2)
for c_pf in [100.0, 400.0]:
    f_khz = 1.0 / (2 * np.pi * np.sqrt(inductance * c_pf * 1e-12)) / 1000
    axis.scatter([c_pf], [f_khz], color=RED, zorder=3)
    axis.annotate(f"{c_pf:.0f} pF, {f_khz:.1f} kHz", (c_pf, f_khz), xytext=(8, 6), textcoords="offset points")
axis.set(xlabel="capacitance C (pF)", ylabel="resonant frequency (kHz)", title="Equation 2.1: L = 1 mH; four times C gives half the frequency")
save(fig, "eq-2-1-lc-frequency.svg")

# Equation 2.3: difference and sum move differently as one oscillator moves.
fixed_khz = 500.0
variable_khz = np.linspace(498.0, 502.0, 900)
difference_khz = np.abs(fixed_khz - variable_khz)
sum_khz = fixed_khz + variable_khz
fig, axes = plt.subplots(2, 1, figsize=(7.2, 4.8), sharex=True, constrained_layout=True)
axes[0].plot(variable_khz, difference_khz, color=RED, linewidth=2)
axes[0].set(ylabel="difference (kHz)", title="Absolute difference reaches zero when oscillators match")
axes[1].plot(variable_khz, sum_khz, color=BLUE, linewidth=2)
axes[1].set(xlabel="variable oscillator f2 (kHz); fixed f1 = 500 kHz", ylabel="sum (kHz)", title="Sum remains near 1000 kHz")
fig.suptitle("Equation 2.3: |f1 − f2| and f1 + f2", fontweight="bold")
save(fig, "eq-2-3-sum-difference.svg")

# Equation 2.4: linear addition beats but keeps only the input spectrum lines.
f1 = 10.0
f2 = 11.5
t_beats = np.linspace(0.0, 2.0, 5000, endpoint=False)
linear_sum = np.cos(2 * np.pi * f1 * t_beats) + np.cos(2 * np.pi * f2 * t_beats)
envelope = 2 * np.cos(np.pi * (f2 - f1) * t_beats)
fig, axes = plt.subplots(2, 1, figsize=(7.2, 5.0), constrained_layout=True)
axes[0].plot(t_beats, linear_sum, color=BLACK, linewidth=0.8, label="linear sum")
axes[0].plot(t_beats, envelope, color=RED, linewidth=1.4, linestyle="--", label="± envelope")
axes[0].plot(t_beats, -envelope, color=RED, linewidth=1.4, linestyle="--")
axes[0].set(xlabel="time (s)", ylabel="amplitude", xlim=(0, 2), title="The waveform has a slow envelope")
axes[0].legend(frameon=False)
markerline, stemlines, _ = axes[1].stem([f1, f2], [1.0, 1.0], basefmt=" ")
plt.setp(markerline, color=PURPLE, markersize=6)
plt.setp(stemlines, color=PURPLE, linewidth=1.6)
axes[1].set(xlabel="frequency (scaled Hz)", ylabel="relative amplitude", xticks=[f1, f2], xlim=(0, 15), ylim=(0, 1.1), title="The linear spectrum still has only f1 and f2")
fig.suptitle("Equation 2.4: f1 = 10, f2 = 11.5; beating is not a new 1.5 line", fontweight="bold")
save(fig, "eq-2-4-linear-beating.svg")

# Equation 2.5: map cent offset to frequency around A4.
cents = np.linspace(-100.0, 100.0, 800)
center_hz = 440.0
mapped_hz = center_hz * np.power(2.0, cents / 1200.0)
fig, axis = plt.subplots(figsize=(7.2, 3.2), constrained_layout=True)
axis.plot(cents, mapped_hz, color=RED, linewidth=2)
for c in [-50.0, 0.0, 50.0]:
    f = center_hz * 2 ** (c / 1200)
    axis.scatter([c], [f], color=BLUE, zorder=3)
    axis.annotate(f"{c:+.0f} cents = {f:.2f} Hz", (c, f), xytext=(5, 7), textcoords="offset points")
axis.set(xlabel="cent offset c", ylabel="frequency f(t) (Hz)", title="Equation 2.5: fc = 440 Hz; equal cents form frequency ratios")
save(fig, "eq-2-5-cents-frequency.svg")

# Equation 2.6: sinusoidal vibrato in cent space.
t_vibrato = np.linspace(0.0, 1.0, 1600)
depth_cents = 25.0
vibrato_rate = 5.5
cent_trajectory = depth_cents * np.sin(2 * np.pi * vibrato_rate * t_vibrato)
fig, axis = plt.subplots(figsize=(7.2, 3.2), constrained_layout=True)
axis.plot(t_vibrato, cent_trajectory, color=TEAL, linewidth=1.8)
axis.axhline(depth_cents, color=GRAY, linestyle="--", linewidth=0.8, label="±D = ±25 cents")
axis.axhline(-depth_cents, color=GRAY, linestyle="--", linewidth=0.8)
zero_line(axis)
axis.set(xlabel="time t (s)", ylabel="c(t) (cents)", ylim=(-32, 32), title="Equation 2.6: D = 25 cents, fv = 5.5 Hz")
axis.legend(frameon=False)
save(fig, "eq-2-6-vibrato-cents.svg")

# Equation 2.7: LC frequency ratio as capacitance ratio changes.
capacitance_ratio = np.linspace(0.25, 4.0, 900)
frequency_ratio = np.sqrt(1.0 / capacitance_ratio)
fig, axis = plt.subplots(figsize=(7.2, 3.2), constrained_layout=True)
axis.plot(capacitance_ratio, frequency_ratio, color=BLUE, linewidth=2)
for ratio in [1.0, 4.0]:
    value = np.sqrt(1 / ratio)
    axis.scatter([ratio], [value], color=RED, zorder=3)
    axis.annotate(f"C2/C1 = {ratio:g}, f2/f1 = {value:g}", (ratio, value), xytext=(7, 7), textcoords="offset points")
axis.set(xlabel="capacitance ratio C2/C1", ylabel="frequency ratio f2/f1", title="Equation 2.7: fixed L; frequency scales as 1/√C")
save(fig, "eq-2-7-lc-ratio.svg")

# Equation 2.8: cents expressed directly as a frequency ratio.
cents_wide = np.linspace(-1200.0, 1200.0, 1200)
ratio = np.power(2.0, cents_wide / 1200.0)
fig, axis = plt.subplots(figsize=(7.2, 3.2), constrained_layout=True)
axis.plot(cents_wide, ratio, color=PURPLE, linewidth=2)
for c, r in [(-1200, 0.5), (0, 1.0), (1200, 2.0)]:
    axis.scatter([c], [r], color=RED, zorder=3)
    axis.annotate(f"{c:+d} cents → {r:g}×", (c, r), xytext=(5, 7), textcoords="offset points")
axis.set(xlabel="cent displacement c", ylabel="frequency ratio f/fc", title="Equation 2.8: 1200 cents doubles frequency")
save(fig, "eq-2-8-cents-ratio.svg")

# Equation 2.9: logarithmic and linear interpolation do not share a midpoint.
u = np.linspace(0.0, 1.0, 800)
start_hz = 220.0
end_hz = 880.0
log_path = start_hz * np.power(end_hz / start_hz, u)
linear_path = start_hz + (end_hz - start_hz) * u
fig, axis = plt.subplots(figsize=(7.2, 3.2), constrained_layout=True)
axis.plot(u, log_path, color=RED, linewidth=2, label="log-frequency path")
axis.plot(u, linear_path, color=GRAY, linewidth=1.4, linestyle="--", label="linear-hertz path")
axis.scatter([0.5, 0.5], [440, 550], color=[RED, GRAY], zorder=3)
axis.annotate("440 Hz", (0.5, 440), xytext=(-35, -18), textcoords="offset points")
axis.annotate("550 Hz", (0.5, 550), xytext=(8, 8), textcoords="offset points")
axis.set(xlabel="normalized progress u", ylabel="frequency (Hz)", title="Equation 2.9: 220 → 880 Hz")
axis.legend(frameon=False)
save(fig, "eq-2-9-log-interpolation.svg")

# Equation 2.10: frequency controls phase increment; accumulated phase controls the waveform.
sample_rate = 48_000
samples = 1_920
time = np.arange(samples) / sample_rate
progress = np.linspace(0.0, 1.0, samples)
frequency_path = 220.0 * np.power(880.0 / 220.0, progress)
increment = 2 * np.pi * frequency_path / sample_rate
phase_path = np.zeros(samples)
phase_path[1:] = np.cumsum(increment[:-1])
waveform = np.sin(phase_path)
assert frequency_path[0] == 220.0 and frequency_path[-1] == 880.0
assert phase_path[0] == 0.0
fig, axes = plt.subplots(3, 1, figsize=(7.2, 6.0), sharex=True, constrained_layout=True)
axes[0].plot(time * 1000, frequency_path, color=BLUE, linewidth=1.8)
axes[0].set(ylabel="f[n] (Hz)", title="Input frequency rises from 220 to 880 Hz")
axes[1].plot(time * 1000, increment, color=TEAL, linewidth=1.8)
axes[1].set(ylabel="phase step (rad)", title="Each sample gets a new 2πf[n]/Fs increment")
axes[2].plot(time * 1000, waveform, color=RED, linewidth=0.9)
zero_line(axes[2])
axes[2].set(xlabel="time (ms)", ylabel="x[n]", title="The sine reads the accumulated phase")
fig.suptitle("Equation 2.10: Fs = 48 kHz, N = 1920, f[0] = 220 Hz, f[1919] = 880 Hz", fontweight="bold")
save(fig, "eq-2-10-phase-accumulation.svg")

print("Generated Chapter 2 formula visuals.")

SOURCE AND OUTPUT

Theremin signal diagram

The Mermaid source produces the full control and heterodyne signal path.

Output

Theremin pitch and volume control paths through oscillators, mixer, filter, and amplifier
Theremin control and signal path.

Source

ch02-heterodyne-instrument.mmd

assets/diagrams/src/ch02-heterodyne-instrument.mmdMermaid

flowchart LR
    HP[Pitch<br/>hand] --> CAP[Hand<br/>antenna<br/>capacitance]
    CAP --> VAR[Variable-RF<br/>oscillator]
    FIX[Fixed-RF<br/>oscillator] --> MIX[Nonlinear<br/>mixer]
    VAR --> MIX
    MIX --> LP[Low-pass<br/>difference]
    HV[Volume<br/>hand<br/>loop] --> AMP[Amplitude<br/>control]
    LP --> AMP
    AMP --> OUT[Speaker<br/>diffuseur]

Chapter 2 Answers and Fault Invariants

Chapter 2 mathematical-practice answers

  1. f2/f1=1/4=1/2f_2/f_1=1/\sqrt{4}=1/2.
  2. Difference: 440 Hz. Sum: 519,560 Hz.
  3. Linear superposition retains spectral components at the two inputs. The 440 Hz envelope rate is not a new 440 Hz spectrum line; multiplication or another nonlinearity is required.
  4. 440250/1200427.47440\cdot2^{-50/1200}\approx427.47 Hz and 440250/1200452.89440\cdot2^{50/1200}\approx452.89 Hz.
  5. Logarithmic midpoint: 220(880/220)1/2=440220(880/220)^{1/2}=440 Hz. Linear midpoint: (220+880)/2=550(220+880)/2=550 Hz.
  6. 2π(440/48,000)0.057602\pi(440/48{,}000)\approx0.05760 radians per sample. For a glide, recompute the increment from each f[n]f[n] and add it to the previous phase.
  7. 300,000587.33=299,412.67300{,}000-587.33=299{,}412.67 Hz = 299.41267 kHz.
  8. A valid response identifies every axis or panel, copies the stated parameters, and links one visual feature to the formula. Examples include LC frequency falling with capacitance, multiplication creating two new lines, log interpolation reaching 440 Hz halfway, and phase steps increasing during the glide.

Chapter 2 readiness answers

  1. The variable LC oscillator falls because f1/Cf\propto1/\sqrt{C}. Its distance from the fixed oscillator can increase, so the audible difference frequency rises.
  2. The nonlinear mixer produces 1 kHz and 1001 kHz. An audio low-pass filter retains 1 kHz.
  3. The trigonometric envelope of a linear sum varies, but its Fourier components remain at the original two frequencies. A nonlinear product generates cross-components at the sum and difference.
  4. At 0°, equal 220 Hz waves align and produce the same pitch at twice the peak amplitude, about 6.02 dB above one wave. At 180°, every value has an equal opposite and the ideal sum is silence.
  5. A passive real tank loses energy to resistance, dielectric loss, and radiation, so its amplitude trajectory decays. Correctly phased active feedback can replace approximately that loss each cycle and sustain a steady trajectory.
  6. The pitch hand controls capacitance near the vertical rod; the volume hand shapes amplitude near the loop. Amplitude control creates attack, release, separation, accent, and rest instead of exposing every pitch movement continuously.
  7. Vibrato is periodic pitch motion around a target; portamento is continuous travel between targets; articulation shapes onset, connection, emphasis, and release, commonly through amplitude.
  8. Mobile keyboard and ring/wire. The intensity key provides pressure-shaped amplitude and articulation. A resonant diffuser changes spectral colour and decay rather than only increasing level.

Chapter 2 faded and musical-station invariants

  • Variable frequencies below 300.000 kHz are 299.56000 kHz for A4, 299.47675 kHz for C5, and 299.34075 kHz for E5.
  • As the target audio frequency rises, the below-fixed variable oscillator moves farther downward.
  • The low-pass stage must reject the RF sum and other high-frequency mixer products.
  • Acceptable musical proof contains separate pitch and amplitude trajectories, two contrasting articulations of the same pitch centers, and a listener observation about event clarity versus continuity.

Chapter 2 fault invariant

  • The antenna is primarily a capacitive proximity electrode, not a blocked beam.
  • Increased capacitance lowers LC frequency directly.
  • Linear addition is not nonlinear heterodyne mixing.
  • The Ondes adds distinct pitch interfaces, haptic amplitude control, timbre selection, and specialized outputs; it is not merely a keyed theremin.