{"id":51106737,"url":"https://github.com/zivl/synthesisers","last_synced_at":"2026-06-24T14:31:12.505Z","repository":{"id":362210434,"uuid":"1210546309","full_name":"zivl/synthesisers","owner":"zivl","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-03T05:18:36.000Z","size":29,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-03T07:15:25.301Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zivl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-14T14:19:13.000Z","updated_at":"2026-06-03T05:18:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/zivl/synthesisers","commit_stats":null,"previous_names":["zivl/synthesisers"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/zivl/synthesisers","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zivl%2Fsynthesisers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zivl%2Fsynthesisers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zivl%2Fsynthesisers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zivl%2Fsynthesisers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zivl","download_url":"https://codeload.github.com/zivl/synthesisers/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zivl%2Fsynthesisers/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34737397,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-24T02:00:07.484Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-24T14:31:11.641Z","updated_at":"2026-06-24T14:31:12.487Z","avatar_url":"https://github.com/zivl.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# synthesisers\n\nArduino sketches for ESP32-based synth and visualiser experiments. Each sketch lives in its own folder matching the sketch name (Arduino IDE requirement).\n\n## Board\n\n### ESP32-C3 Super Mini with 0.42\" OLED\n- ESP32-C3, single-core RISC-V, 160 MHz, **no FPU**\n- 0.42\" SSD1306 OLED: 72×40 visible pixels offset on a 128×64 framebuffer\n- Native USB-CDC + USB-JTAG (no external USB-UART chip, no driver needed on macOS)\n- Built-in LED on **GPIO 8 (active-low)**\n- BOOT button on **GPIO 9**\n- Pads labeled `RX` and `TX` on the board correspond to **GPIO 20** and **GPIO 21**\n\n---\n\n## Wiring map — ESP32-C3 Super Mini\n\nThe current audio sketches (`Acid303`, `DarkSynth`, `Disco80`, `WaveMorphAudio`) target this exact wiring.\n\n### Power rails (use the breadboard's rails)\n\n| Rail | Source on C3 |\n|---|---|\n| 5V | `5V` pin (USB bus power) |\n| 3V3 | `3V3` pin |\n| GND | `GND` pin |\n\n### Signal-direction rule\n\n\u003e **Anything that drives a signal INTO the C3 must run from 3V3.** The C3's GPIOs are not 5V-tolerant. The amp is the only 5V device here because the C3 *sends* I²S to it (3.3V logic, which the amp accepts as HIGH); its 5V VIN is independent of its logic inputs.\n\n### OLED (SSD1306, 72×40, I²C)\n\n| OLED pin | C3 |\n|---|---|\n| VCC | 3V3 |\n| GND | GND |\n| SDA | **GPIO 5** |\n| SCL | **GPIO 6** |\n\n### MAX98357A I²S mono amp\n\n| Amp pin | C3 | Notes |\n|---|---|---|\n| VIN | **5V** | 3V3 works per datasheet but in practice the Super Mini's onboard 3V3 regulator can't deliver enough current for audible output |\n| GND | GND | |\n| BCLK | **GPIO 7** | |\n| LRC | **GPIO 10** | |\n| DIN | **GPIO 20 (RX pad)** | |\n| GAIN | floating | 9 dB default. 100 kΩ to VDD = 12 dB, direct to VDD = 15 dB, direct to GND = 6 dB, 100 kΩ to GND = 3 dB |\n| SD | floating | Internal pull-up enables \"play L+R mix\" mode. **Do NOT tie to GND** (= shutdown) |\n| SPK+ | speaker + | |\n| SPK− | speaker − | Bridged output — **do NOT ground** |\n\n### 4× B10k pots (linear taper, 10 kΩ)\n\nEach pot is identical: one outer leg → 3V3, other outer leg → GND, wiper → ADC GPIO. If a pot feels inverted (CW = quieter), swap its two outer legs.\n\n| Pot | Function (Acid303) | Wiper → |\n|---|---|---|\n| #1 | Cutoff | **GPIO 0** |\n| #2 | Resonance | **GPIO 1** |\n| #3 | Env Mod | **GPIO 3** |\n| #4 | Decay | **GPIO 4** |\n\n### 2× HW-763 capacitive touch modules (TTP223)\n\nVCC must be **3V3** — the module drives its OUT pin at VCC level and the C3's GPIO is not 5V tolerant.\n\n| Module | VCC | GND | OUT → | Special |\n|---|---|---|---|---|\n| Touch 1 (Octave Up) | 3V3 | GND | **GPIO 21 (TX pad)** | none |\n| Touch 2 (Pattern Variant) | 3V3 | GND | **GPIO 2** | Bridge the **\"A\" solder pad** on the back of the module to flip it to active-LOW. GPIO 2 is a boot strapping pin and the default active-HIGH idle would keep the chip from booting. |\n\n### Full GPIO usage\n\n| GPIO | Use | Notes |\n|---|---|---|\n| 0 | Pot 1 — Cutoff | ADC1_CH0 |\n| 1 | Pot 2 — Resonance | ADC1_CH1 |\n| 2 | Touch 2 — Pattern Variant | ADC1_CH2; **strapping pin** — must read HIGH at boot |\n| 3 | Pot 3 — Env Mod | ADC1_CH3 |\n| 4 | Pot 4 — Decay | ADC1_CH4 |\n| 5 | OLED SDA | also ADC2_CH0 but avoid (Wi-Fi shared) |\n| 6 | OLED SCL | |\n| 7 | I²S BCLK | |\n| 8 | Onboard LED | **strapping pin**, active-low |\n| 9 | BOOT button | **strapping pin**, LOW at boot = download mode |\n| 10 | I²S LRC | |\n| 20 (RX) | I²S DIN | UART0 RX — free when `CDCOnBoot=cdc` routes Serial to USB |\n| 21 (TX) | Touch 1 — Octave Up | UART0 TX — same |\n\n---\n\n## Sketches\n\n| Sketch | Description |\n|---|---|\n| `BlinkC3/` | LED blink — note GPIO 8 is active-low |\n| `OLEDHello/` | 0.42\" OLED hello via U8g2 (handles panel offset) |\n| `WaveMorph/` | Sine → saw → square morph visualised on the OLED |\n| `WaveMorphAudio/` | Same morph made audible via MAX98357A. Uses band-limited Fourier-series saw/square + audio-rate integer crossfade to eliminate aliasing and buffer-boundary clicks |\n| `Disco80/` | 118 BPM four-on-the-floor disco loop: pitched-sine kick with pitch sweep, noise hi-hat, octave-jumper bass, sine lead riff |\n| `DarkSynth/` | Slow synthwave drone in D minor through a 1-pole LPF — three saw voices, long decays, a 13.5 s loop |\n| `Acid303/` | TB-303-style acid bassline: saw oscillator → 2-pole Chamberlin state-variable resonant LPF with filter envelope, slides, accents. Live control via the 4 pots; touch sensors for octave-up and pattern variant |\n| `HelloWorld/`, `LEDCFade/`, `DiscoBall/`, `FSharpTone/` | Legacy experiments — kept in tree for reference |\n\n---\n\n## Build \u0026 upload\n\n`arduino-cli` is the build path. Arduino IDE works too with the same FQBN.\n\n### FQBN\n**Always use `CDCOnBoot=cdc`** — without it, `Serial.print` is routed to UART0 (GPIO 20/21) instead of USB CDC, and the serial monitor stays silent.\n\n```bash\n# Compile\narduino-cli compile --fqbn \"esp32:esp32:esp32c3:CDCOnBoot=cdc\" Acid303/\n\n# Find the port (re-enumerates each replug)\narduino-cli board list\n\n# Upload\narduino-cli upload -p /dev/cu.usbmodem\u003cNNN\u003e --fqbn \"esp32:esp32:esp32c3:CDCOnBoot=cdc\" Acid303/\n\n# Serial monitor\narduino-cli monitor -p /dev/cu.usbmodem\u003cNNN\u003e --config baudrate=115200\n```\n\n### Required libraries\n- `U8g2` — install with `arduino-cli lib install \"U8g2\"`\n- `ESP_I2S` — bundled with ESP32 Arduino core 3.x\n- `Wire` — bundled\n\n### Port re-enumeration on macOS\nThe C3's native USB shows up as `/dev/cu.usbmodem\u003cNNN\u003e`. The trailing digits **change every time the board is unplugged** (e.g. `usbmodem11101` → `usbmodem1101` → `usbmodem101`). Always re-check `arduino-cli board list` after replugging.\n\n---\n\n## References\n\n### Boards \u0026 cores\n- ESP32 Arduino core: https://github.com/espressif/arduino-esp32\n- ESP32-C3 datasheet: https://www.espressif.com/sites/default/files/documentation/esp32-c3_datasheet_en.pdf\n- ESP32-C3 technical reference manual: https://www.espressif.com/sites/default/files/documentation/esp32-c3_technical_reference_manual_en.pdf\n\n### Audio\n- MAX98357A datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/MAX98357A-MAX98357B.pdf\n- Adafruit MAX98357A guide (excellent for wiring \u0026 gain pin behaviour): https://learn.adafruit.com/adafruit-max98357-i2s-class-d-mono-amp\n\n### Display\n- SSD1306 datasheet: https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf\n- U8g2 library: https://github.com/olikraus/u8g2 — use `U8G2_SSD1306_72X40_ER_F_HW_I2C` constructor for the 0.42\" panel; it handles the column/page offset automatically\n\n### Input\n- TTP223 datasheet (HW-763 touch module): https://www.tontek.com.tw/uploads/product/74/TTP223C-BA6_V1.2.pdf\n  - Solder pad **A** flips the output polarity (default active-HIGH → active-LOW)\n  - Solder pad **B** switches between momentary and toggle behaviour\n\n---\n\n## Notes for Claude (self-context for future sessions)\n\nWhen resuming this project:\n\n1. **Wiring map above is the source of truth** — don't re-derive pin assignments from scratch.\n2. **`CDCOnBoot=cdc` is mandatory** in the FQBN for C3 audio sketches, otherwise Serial output is invisible.\n3. **C3 has no FPU.** Audio-rate code uses integer fixed-point math. Float math per-sample (especially `sinf`) is too slow — pre-compute LUTs in `setup()` or use Q8/Q16 fixed-point.\n4. **Aliasing is real.** Naive saw/square at 22 kHz sample rate sounds buzzy because high harmonics fold back. Either band-limit (see `WaveMorphAudio`'s Fourier-series tables) or filter aggressively.\n5. **Audio-rate parameter changes need per-sample interpolation.** Snapshotting a parameter once per buffer and using it for the whole 1024–2048 sample chunk creates audible clicks at buffer boundaries (~22 Hz click train sounds like grain). See `WaveMorphAudio` for the integer-blend pattern.\n6. **Hardware-first debugging on new audio modules.** When a brand-new amp produces noise/clicks/grain, suggest swapping the module *before* multiple rounds of software optimization. See private memory `hardware-first-debug.md`.\n7. **Arduino IDE auto-generates function prototypes** at the top of `.ino` files. Functions taking user-defined struct types as parameters fail to compile because the prototype is placed before the struct definition. Workaround: don't pass structs as parameters in `.ino` files — give each voice its own dedicated function (`process_bass()`, `process_lead()` etc.), or move the struct to a `.h` file.\n8. **Pin-name conflicts.** ESP32 Arduino core defines `A0`, `A1`, `A2`… as analog-pin macros. Don't name musical-note constants `A2`, `A3` etc. — use `NOTE_A2`, `NOTE_A3`.\n9. **Port re-enumerates on replug** — always run `arduino-cli board list` before uploading after disconnecting the board.\n10. **Only one board is in use** — the ESP32-C3 Super Mini with 0.42\" OLED. Don't reintroduce CYD/dual-board pinouts.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzivl%2Fsynthesisers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzivl%2Fsynthesisers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzivl%2Fsynthesisers/lists"}