Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/instructions/lint.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,22 @@ const obj = { name, getValue() { return 1; } };
const obj = { name: name, getValue: function() { return 1; } };
```

## Unnecessary Conditions

Do not write conditions that TypeScript can prove are always true or always false. This triggers `@typescript-eslint/no-unnecessary-condition`.

```ts
// ✅ — only guard when the type is genuinely nullable
const slot: SlotPlugTypes | undefined = arr[row]?.[col];
if (!slot) return;

// ❌ — SlotPlugTypes is [symbol,symbol,symbol,symbol], never falsy
const slot: SlotPlugTypes = arr[row][col];
if (!slot) continue; // always false — remove it
```

Also remove optional chaining (`?.`) when the left-hand side is already a non-nullable type.

## Template Literals

Use template literals instead of string concatenation.
Expand Down
31 changes: 2 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ See the live demo here: https://pitpit.github.io/synt

![Synt screenshot](https://raw.githubusercontent.com/pitpit/synt/main/public/img/screenshot1.png)

![Arpeggiator example](https://raw.githubusercontent.com/pitpit/synt/main/public/img/screenshot-arpegiator.png)

## License

Copyright (C) 2026 Damien Pitard
Expand Down Expand Up @@ -112,8 +110,7 @@ An HTML report is generated in `playwright-report/` after each run.
- [ ] LFO-to-CV — dedicated slow LFO with depth and rate knobs
- [ ] Random / S&H — random voltage generator (stepped or smooth)
- [ ] Function generator — slew-limited ramp (rise/fall times)
- [ ] Sequencer — step sequencer with CV and gate outputs (8 or 16 steps)
- [ ] Arpeggiator — enhanced arpeggio patterns (already exists, keep improving)
- [X>] Sequencer — step sequencer with CV and gate outputs (8 or 16 steps)

#### Filters
- [ ] VCF low-pass — 12/24 dB/oct ladder-style low-pass filter with cutoff & resonance
Expand Down Expand Up @@ -155,34 +152,10 @@ An HTML report is generated in `playwright-report/` after each run.
- [ ] Scope / Oscilloscope — visual display of an audio or CV waveform


### Built module

#### Step sequencer

- Clock / Tempo
- Sequential switch (8 steps)

#### arpegiator
- **Clock / Tempo:** To drive the speed of the arpeggio.
- **Sequencer (8 or 16 steps):** To program the specific note intervals of your chord.
- **LFO-to-CV:** To create automated movement (like shifting octaves).
- **Mixer or Attenuverter:** To combine our note sequence with our octave shifts.
- **Quantizer:** To make sure all the raw voltages snap perfectly to musical notes.
- **An oscillator :** To actually hear the sound.

Step A: Establishing the RhythmPatch the Clock Out into the Clock In of the Sequencer. Your sequencer is now stepping at the speed of your project's tempo.

Step B: Programming the ChordOn your Sequencer, manually dial in the notes of a chord across the steps. For example, if you want a minor triad, dial the knobs to step voltages that represent the Root, Minor 3rd, 5th, and Octave.Patch the CV Out of the sequencer into Input 1 of your Mixer/Attenuverter.

Step C: Creating the "Octave Jump" Feature (The Arpeggiator Magic)An arpeggiator often jumps up an octave on subsequent repeats. We can fake this using a slow LFO or a second sequencer.4. Take a stepped or square wave from your LFO-to-CV (set to a slow rate, like 1/4 the speed of your sequencer).5. Patch that LFO into Input 2 of your Mixer/Attenuverter. Use the level knob to calibrate it so that when the LFO goes high, it adds exactly $1\text{V}$ (which equals one octave in the standard $1\text{V/Oct}$ protocol).

Step D: Keeping it in TuneBecause manual sequencer knobs and LFOs are imprecise, we need a musical safety net.6. Patch the Output of your Mixer (which is now your Chord CV + Octave Shift CV combined) into the input of the Quantizer.7. Select your desired scale on the Quantizer. It will instantly correct the mathematical voltages into perfect musical pitches.

Step E: Making SoundPatch the Quantizer CV Out into the 1V/Oct Input of your Wavetable Oscillator.Patch the Gate Out of your Sequencer into the Gate Input of the ADSR envelope.Route the ADSR Output to the CV input of your VCA to shape the volume of each arpeggiated note.


### UX

- [ ] when double clicking on stage, it open the modal with to integer input to set the stage size
- [ ] multi selection & multi drag'n drop for mass deletion?
- [ ] on Rack, set a "pan" cursor when the cursor is hover the stage and can be pan
- [ ] Add several layers to put more Mods?
Expand Down
3 changes: 0 additions & 3 deletions docs/01-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ Default plug layout:
| Module | Plug layout | Behaviour |
|--------|-------------|-----------|
| `Knob` | NORTH: `NULL`, EAST: `CTRLOUT`, SOUTH: `NULL`, WEST: `CTRLOUT` | Mouse-wheel or vertical touch-drag changes value in [0, 1]. Emits a `ControlSignal` from both EAST and WEST. |
| `Arpeggiator` | NORTH: `NULL`, EAST: `CTRLIN`, SOUTH: `NULL`, WEST: `CTRLOUT` | Emits a stepped `ControlSignal` sequence; EAST control input maps 0–1 to the arpeggio clock interval. |
| `Gate` | NORTH: `IN`, EAST: `NULL`, SOUTH: `OUT`, WEST: `NULL` | Extends `EffectMod` with a `ToneGain(1)` effect node — audio passes through at full volume. |
| `SwitchOn` | NORTH: `IN`, EAST: `NULL`, SOUTH: `OUT`, WEST: `NULL` | Extends `EffectMod` with a `ToneGain(0)` — press on/off toggles gain between 1 and 0. |
| `Keyboard` | NORTH: `NULL`, EAST: `NULL`, SOUTH: `NULL`, WEST: `CTRLOUT` | Visual keyboard display; in `src/control/Keyboard.ts` the exported class is currently named `Knob`. |
Expand Down Expand Up @@ -197,15 +196,13 @@ classDiagram
class SwitchOn
class Gate
class Knob
class Arpeggiator
class Keyboard
class StickyNote

Mod <|-- SourceMod
Mod <|-- EffectMod
Mod <|-- SinkMod
Mod <|-- Knob
Mod <|-- Arpeggiator
Mod <|-- Keyboard
Mod <|-- StickyNote
SourceMod <|-- Oscillator
Expand Down
2 changes: 1 addition & 1 deletion docs/03-testing-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Unit tests cover individual module classes in isolation. Real source classes are

- **Tone.js** is mocked globally via `tests/__mocks__/tone.ts`. It exports `jest.fn()` factories so classes that `import … from 'tone'` receive lightweight fakes that expose `connect`, `disconnect`, and `dispose` spies — no real audio graph is created.
- Jest is configured with `clearMocks: true` and `restoreMocks: true`, so spy state never leaks between tests.
- Time-dependent tests (e.g. `Arpeggiator`) use `jest.useFakeTimers()` / `jest.advanceTimersByTime()` in `beforeEach` / `afterEach`.
- Time-dependent tests use `jest.useFakeTimers()` / `jest.advanceTimersByTime()` in `beforeEach` / `afterEach`.

---

Expand Down
66 changes: 43 additions & 23 deletions docs/05-module-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This document describes every audio module in synt — its purpose, plug layout,
| 2 | SOUTH | Audio or control output |
| 3 | WEST | Second control input / control output |

Plug types: `IN` audio input · `OUT` audio output · `CTRLIN` CV input · `CTRLOUT` CV output · `NULL` no plug.
Plug types: `IN` audio input · `OUT` audio output · `CTRLIN` CV input · `CTRLOUT` CV output · `CLKIN` clock input · `CLKOUT` clock output · `NULL` no plug.

---

Expand Down Expand Up @@ -312,28 +312,6 @@ Displays a visual on-screen keyboard image. Outputs a CV signal on WEST represen

---

### Arpeggiator

**Source**: [src/control/Arpeggiator.ts](../src/control/Arpeggiator.ts)
**Base**: `Mod`

A 4-step CV sequencer that cycles through a fixed sequence at a tempo controlled by EAST CV. Outputs the current step's value on WEST.

| Position | Type | Role |
|----------|------|------|
| NORTH | `NULL` | — |
| EAST | `CTRLIN` | Tempo CV |
| SOUTH | `NULL` | — |
| WEST | `CTRLOUT` | Step CV output |

| Plug | Parameter | Mapping |
|------|-----------|---------|
| EAST | Step interval | `(1 - value) × 1500` → 1500 ms (slow) to 0 ms (fast) — inverted |

**Sequence**: `[0.3, 0.45, 0.55, 0.45]` repeating. The timer restarts whenever the tempo CV changes.

---

### MidiIn

**Source**: [src/control/MidiIn.ts](../src/control/MidiIn.ts)
Expand All @@ -356,6 +334,48 @@ Connects to a hardware MIDI input device via the Web MIDI API. Translates incomi

---

### Clock

**Source**: [src/control/Clock.ts](../src/control/Clock.ts)
**Base**: `Mod`

A free-running clock pulse generator. Toggles a `CLKOUT` signal between 0 and 1 at a configurable rate. The clock starts when its SOUTH plug is connected and stops when it is disconnected or the module is removed from the rack. Rate CV on EAST adjusts the tick frequency while the clock is running.

| Position | Type | Role |
|----------|------|------|
| NORTH | `NULL` | — |
| EAST | `CTRLIN` | Rate CV |
| SOUTH | `CLKOUT` | Clock pulse output |
| WEST | `NULL` | — |

| Plug | Parameter | Mapping |
|------|-----------|---------|
| EAST | Frequency | `0.5 + value × 9.5` → 0.5–10 Hz |

---

### Sequencer

**Source**: [src/control/Sequencer.ts](../src/control/Sequencer.ts)
**Base**: `Mod`
**Grid size**: 1 × 8

An 8-step sequential switch. On each rising or falling clock edge received on NORTH, it advances to the next step and forwards that step's CV input (from the corresponding EAST slot) to the WEST output. Each step's CV is supplied by a separate `CTRLIN` plug arranged top-to-bottom on the EAST face.

The perimeter layout for this 1 × 8 module follows the standard clockwise convention (2 × (1 + 8) = 18 slots):

| Face | Slots | Type | Role |
|------|-------|------|------|
| NORTH | 1 | `CLKIN` | Clock input |
| EAST | 8 (rows 0–7) | `CTRLIN` | Step 0–7 CV inputs |
| SOUTH | 1 | `NULL` | — |
| WEST | row 7 | `CTRLOUT` | Active step CV output |
| WEST | rows 0–6 | `NULL` | — |

**Step advance**: triggers on any change of the incoming `ControlSignal` value (both edges). Connect a `Clock` SOUTH → Sequencer NORTH; each toggle advances the step by one.

---

### Gate

**Source**: [src/control/Gate.ts](../src/control/Gate.ts)
Expand Down
86 changes: 71 additions & 15 deletions public/demo.synt.yaml
Original file line number Diff line number Diff line change
@@ -1,31 +1,87 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/pitpit/synt/main/synt.schema.json
synt:
rack:
width: 10
height: 10
width: 12
height: 12
mods:
- type: TriangleOscillator
x: 1
y: 3
- type: Gate
x: 0
y: 4
- type: Clock
x: 3
'y': 0
- type: Knob
x: 4
'y': 0
value: 0.86
- type: Sequencer
x: 3
'y': 1
- type: Knob
x: 4
'y': 1
value: 0.08
- type: Knob
x: 4
'y': 2
value: 0.355
- type: Knob
x: 4
'y': 3
value: 0.45499999999999996
- type: Knob
x: 4
'y': 4
value: 0.18999999999999992
- type: Knob
x: 4
'y': 5
value: 0.16499999999999998
- type: Knob
x: 4
'y': 6
value: 0.355
- type: Knob
x: 4
'y': 7
value: 0.13499999999999998
- type: Knob
x: 4
'y': 8
value: 0.26
- type: SineOscillator
x: 2
y: 3
'y': 8
- type: Speaker
x: 1
y: 5
x: 2
'y': 11
- type: Chorus
x: 2
'y': 9
- type: Phaser
x: 2
'y': 10
- type: Knob
x: 3
'y': 9
value: 0.915
- type: Knob
x: 3
'y': 10
value: 1
annotations:
- x: 400
y: 400
text: |
- x: 568.6591736935475
'y': 34.02402819750671
text: >
Welcome to Synt, the modular synthesizer!


⇑ ⇑ ⇑ Pan to the NORTH to get new modules to put in your Rack.


Connect modules by dragging from an output plug to an input plug.

Try by yourself: move the GATE module between the TRIANGLE oscillator and SPEAKER. Adjust the KNOB clicking|tapping and vertically dragging to change the oscillator's frequency.

Try by yourself: move the GATE module between the TRIANGLE oscillator
and SPEAKER. Adjust the KNOB clicking|tapping and vertically dragging to
change the oscillator's frequency.


Double-click any module to open its settings.
5 changes: 1 addition & 4 deletions public/test.synt.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ synt:
- type: Tremolo
x: 2
y: 1
- type: Arpeggiator
x: 3
y: 1
- type: Panner
x: 4
y: 1
Expand Down Expand Up @@ -81,7 +78,7 @@ synt:

Connect modules by dragging from an output plug to an input plug. The top row contains oscillators (Sine, Square, Sawtooth, Triangle) that generate audio signals. Use the Gate to control when sound plays, and Vibrato/Tremolo for pitch and volume modulation.

Arpeggiator (arp) cycles through notes automatically — connect an Oscillator to arpeggiate chords. Panner (pan) pans the audio left/right — connect a Knob to its EAST plug and turn it to sweep the stereo field.
Panner (pan) pans the audio left/right — connect a Knob to its EAST plug and turn it to sweep the stereo field.

Knobs adjust parameters — drag up/down to change values. SwitchOn modules toggle signals on or off. Speakers output the final audio signal.

Expand Down
48 changes: 0 additions & 48 deletions src/control/Arpeggiator.ts

This file was deleted.

Loading
Loading