Compare commits

...

No commits in common. "main" and "concepts" have entirely different histories.

205 changed files with 39497 additions and 139 deletions

View file

@ -1,48 +1,70 @@
# CLAUDE.md
This file guides Claude Code (claude.ai/code) when working in this repository.
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is the **slim `main` branch** of VARASYS PolyMeter — a polymetric groove-trainer /
metronome. Only three things ship here:
- `index.html` — the landing chooser: two buttons, **Mobile**`mobile.html` and
**Desktop**`pm_e-2.html`.
- `mobile.html` — the touch-first phone/tablet **PWA** (+ `mobile-sessions.html`, its practice
journal). Installable, works offline via `mobile-sw.js` + `manifest.webmanifest`.
- `pm_e-2.html` — the engraved-notation editor.
The **full project** (the PM_E-1 editor, the embeddable widget, every hardware form-factor page,
the Pico **firmware** editions, the **Rust** port, and the **KiCad/SPICE hardware** design) lives
on the **`concepts`** branch. Pull anything back from there if it needs to return to the front page.
VARASYS PolyMeter: a polymetric groove-trainer / metronome **engine** that ships in three guises from one repo — a set of self-contained web pages (editor + form-factor gallery + embeddable widget), MicroPython/CircuitPython **firmware** for real hardware, and a native **Rust** port. `README.md` is the product-level reference (share-language grammar, page list, features, keyboard shortcuts) — read it for "what does this token mean"; this file is for how the pieces fit and how to work on them.
## Commands
```sh
./build.sh # assemble the self-contained pages into dist/ (git-ignored)
./deploy.sh # build, stamp version, mirror dist/ to the Caddy web root, smoke-test
./build.sh # assemble self-contained pages into dist/ (also precompiles firmware .mpy + zips bundles)
./deploy.sh # build, stamp version, copy to the Caddy web root, smoke-test (auto-run after changes — see memory)
./release.sh [X.Y.Z] # bump VERSION (optional) + tag v<VERSION>; requires clean tree
node tests/run.mjs # track-format conformance: every golden vector through engine.js AND pico-cp/app.py
node tests/run.mjs -v # + print expected/actual diffs for unexpected failures
./rust/run.sh # cargo test (Rust workspace — track-format crate vs the same golden vectors), in container
./rust/run.sh cargo build # or `bash` for a shell
./rust/pm-grid/build.sh # build the PM_G-1 Grid firmware (pm-grid.uf2)
./hardware/eda/run.sh # interactive shell in the KiCad/ngspice container (lands in hardware/kicad/)
./hardware/eda/run.sh kicad-cli sch erc pm_k1_core.kicad_sch # ERC on the board schematic
./hardware/eda/run.sh ngspice -b ../eda/sim/<deck>.cir # run a SPICE deck
```
There's no test suite on this branch (the track-format conformance suite lives on `concepts`).
There is no lint step and no single-test filter — `node tests/run.mjs` is the one gate; its exit code is non-zero on any unexpected failure or round-trip break, so it doubles as CI.
## Build system
## The track format is the contract
Every deployed page is **one self-contained `.html` file, zero runtime dependencies** — no
framework, no CDN, no audio samples (all voices are synthesized in Web Audio). Pages share code
through build markers that `build.sh` resolves:
The "program" / "patch" / share-language string is the spine of the whole project: the same string drives the web editor, a hardware device, and the Rust crate. Its grammar is **hand-implemented in three places that can silently drift**, so it is formally specced and conformance-tested:
- `/*@BUILD:include:src/<file>@*/` inlines a shared partial (`engine.js`, `setlists.js`,
`base.css`, `chrome.js`, `header.html`/`footer.html`, `notation.js`, `midiout.js`).
- `@BUILD:favicon@` / `@BUILD:logo-*@` / `@BUILD:bravura@` inline base64 blobs from `assets/`.
- **Spec (source of truth):** `docs/track-format.md`
- **Web:** `src/engine.js``patchToSetup` / `laneStrToCfg` / `setupToPatch` / `laneCfgToStr`
- **Firmware:** `pico-cp/app.py``parse_program` / `_parse_lane` / `lane_to_str` / `_prog_str`
- **Rust:** `rust/track-format/src/lib.rs`
- **Golden vectors:** `tests/fixtures/track-format.json` — each case has `in` (a patch), `norm` (expected meaning), a `status`, and optional `expectFail` listing impls known to differ today.
`build.sh` asserts no `@BUILD:` markers survive. **`dist/` is generated and git-ignored — never
edit it by hand.** Edit the source `*.html` in the repo root and the partials in `src/`;
`deploy.sh` always builds first, then mirrors `dist/` to the web root with `rsync --delete`
(so anything no longer built is removed from the live site).
**Rule: any change to the grammar or its meaning must update the spec, add/adjust a golden vector, and keep all three implementations passing.** When you fix a divergence in one impl, delete it from that case's `expectFail`. The test adapters parse the *real* `engine.js` and `app.py` (the Python one via `ast` extraction) rather than copies, so a code change is what the suite actually sees.
State (set lists, practice log, theme) lives in `localStorage`; nothing is uploaded — share
links encode everything in the URL hash (`#p=` patch, `#sl=` base64url set list). Source files
keep an `APP_VERSION` placeholder; only the deployed copy is stamped (from `VERSION`).
## Web build system
## License
Every deployed page is **one self-contained `.html` file, zero runtime dependencies** — no framework, no CDN, no audio samples (all voices are synthesized in Web Audio). Pages stay in sync by sharing code through build markers that `build.sh` resolves:
GNU AGPL v3 (`LICENSE`).
- `/*@BUILD:include:src/<file>@*/` inlines a shared partial. The important ones: `engine.js` (audio scheduler + DSL), `setlists.js` (seed set lists baked into every page), `base.css`, `header.html`/`footer.html`/`chrome.js`, `progbox.{html,js}` (program box), `infoembed.{html,js}` (info-page live widget), `livesync.js` (beta only).
- `@BUILD:favicon@` / `@BUILD:logo-dark@` / `@BUILD:logo-light@` inline base64 blobs from `assets/`.
`build.sh` asserts no `@BUILD:` markers survive. **`dist/` is generated and git-ignored — never edit it by hand.** Edit the source `*.html` in the repo root and the partials in `src/`; `deploy.sh` always builds first. Each page exists as a lean widget (`<device>.html`, what `?embed=1` serves) plus a spec page (`info-<device>.html` that embeds it). `editor.html` is the main app; `editor-beta.html` is identical plus `livesync.js` (live mirror to a connected device over Web-MIDI SysEx — protocol in `docs/livesync-protocol.md`). `mobile.html` is the installable phone/tablet **PWA** (with `mobile-sessions.html` as its practice journal); `build.sh` ships its PWA support files (`manifest.webmanifest` + the `mobile-sw.js` service worker for offline use).
State (set lists, practice log, theme) lives in `localStorage`; nothing is uploaded — share links encode everything in the URL hash (`#p=` patch, `#sl=` base64url set list).
## Firmware editions
All run the same DSL and `programs.json`. When you touch the grammar, the `pico-cp/app.py` half is covered by the conformance suite; the others are not, so keep them in step by hand.
- `pico/main.py` — single-file **MicroPython**, the simple no-computer fallback (52Pi EP-0172 Kit).
- `pico-cp/`**CircuitPython** appliance (USB-drive, push-programming over USB-MIDI, on-device practice log, one-click A/B firmware update). `build.sh` precompiles `app.py``app.mpy` (the RP2040 OOMs compiling the ~56KB source at boot).
- `pico-explorer/` — CircuitPython sibling for the Pimoroni Explorer (RP2350, buttons-only, no touch).
- `pico-scroll/`**superseded** CircuitPython prototype for the Pimoroni Pico Scroll Pack (PIM545: 17×7 mono LED matrix + 4 buttons on a plain RP2040 Pico). It was the UI prototype for the Grid; **the shipping Grid firmware is now the Rust `rust/pm-grid` crate** (`build.sh` no longer bundles the CircuitPython build). Kept for reference — see `docs/rust-port.md`.
Firmware device IDs (reported on the SysEx `0x02→0x03` version query, used by the editor's firmware-push to pick the right firmware — `.mpy` for the CircuitPython editions; Grid is a Rust `.uf2`): `K` Kit, `X` Explorer, `G` Grid.
**`pico-cp/app.py` and `pico-explorer/app.py` must stay pure ASCII** — they are pushed over USB-MIDI as 7-bit data, and a stray non-ASCII char gets mangled to NUL and bricks the device. `build.sh` enforces this with an assert.
## Hardware (PM_K-1 custom board) & Rust port — containerized only
Per the standing rule (see memory), **all EDA and Rust tooling runs in containers via the `run.sh` wrappers — never install these on the host.** Add tools to the respective `Containerfile`. `hardware/eda/` holds the KiCad 9 + ngspice environment; the board design is `hardware/DESIGN.md` + `BOM*.csv` + `kicad/` + the code-defined circuits in `eda/circuits/`. The Rust port is staged inside-out (`docs/rust-port.md`): the `track-format` codec **and** scheduler are done, passing the golden vectors, and build for the RP2350. Per-board firmware crates now exist under `rust/` (`pm-kit`, `pm-grid`, `pm-explorer`, plus support crates `pm-synth`/`pm-ui`/`uisim`/`glyphgen`); `pm-kit` runs on real hardware and **PM_G-1 "Grid" ships the native Rust firmware today** (`rust/pm-grid` → `pm-grid.uf2`, built by `rust/pm-grid/build.sh`).
## Versioning
`VERSION` holds the formal version. `deploy.sh` stamps the served pages: a clean tree on a commit tagged `v<VERSION>``X.Y.Z`; anything else → `X.Y.Z-dev.<utc-ts>.<sha>[.dirty]`. Source files keep a placeholder `APP_VERSION`; only the deployed copy is stamped.

309
README.md
View file

@ -1,43 +1,298 @@
# VARASYS PolyMeter
A **polymetric groove trainer & metronome**. Stack as many "meter lanes" as you like —
each its own little metronome with a grouping, subdivision, drum voice and a perstep
pattern with accents. Layering lanes produces polymeter and true ratio polyrhythm.
A small **website** built around one **polymetric groove trainer / metronome** engine.
A landing page is the front door; the main app is the **PM_E1 PolyMeter Editor** — a full
web app where you stack as many "meter lanes" as you like, each its own little metronome
with a grouping, subdivision, drum voice and a perstep pattern with accents. Layering lanes
produces polymeter and true ratio polyrhythm. The same engine drives an everexpanding library
of **formfactor concepts** (idealized and buildable hardware mockups), ships as an
**embeddable widget** anyone can drop into their own page, and even runs as **firmware** on a
real Raspberry Pi Pico build (the **PM_K1 Kit**).
**Live:** https://metronome.varasys.io · **Source:** https://codeberg.org/VARASYS/metronome
## What ships here
Every **deployed page is a single, selfcontained `.html` file****zero dependencies**:
no framework, no CDN libraries, nothing fetched at runtime. They're assembled by a small
build step (`build.sh`) that inlines a shared engine, the seed set lists, base styling and
the brand assets (kept in `assets/`) into each page, so the sources stay lean. Every voice is
**synthesized** in Web Audio — there are no audio samples to load. State (set lists, the
practice log, theme and UI preferences) lives in `localStorage`.
The landing page (`/`) is a simple chooser with two doors:
## Pages
- **Mobile**`mobile.html` — the touchfirst phone/tablet app (tap a beat, set the tempo,
practice). It's an installable **PWA** that works fully offline, with a practice journal
(`mobile-sessions.html`).
- **Desktop**`pm_e-2.html` — the engravednotation editor: build rhythms on a staff with
full keyboard control. Best on a large screen.
The site is **one editor + a gallery of form factors**, and each form factor is split into a
**lean widget page** and a **separate info page**:
Every **deployed page is a single, selfcontained `.html` file** with **zero runtime
dependencies** — no framework, no CDN, nothing fetched at runtime. `build.sh` inlines a shared
engine, the seed set lists, base styling and the brand assets (`assets/`) into each page. Every
voice is **synthesized** in Web Audio (no audio samples). State (set lists, the practice log,
theme) lives in `localStorage`; share links encode everything in the URL hash — nothing is
uploaded.
- **`<device>.html`** — just the live widget (front view, controls, program box). This is what
`?embed=1` serves and what the landing embeds; it never ships the BOM/narrative.
- **`info-<device>.html`** — the spec page: it embeds the live widget at the top, then the
description, dimensioned drawings and a priced **Bill of Materials** (for the buildable hardware).
## Build & deploy
| URL | What |
|-----|------|
| [`/`](https://metronome.varasys.io/) `index.html` | **Concepts** — the landing / formfactor gallery; each box embeds the live widget (Open ↗ / Specs & info ⓘ) |
| `/editor.html` · `/info-editor.html` | **PM_E1 — PolyMeter Editor** (the main app) + its overview |
| `/pm_e-2.html` · `/info-pm_e-2.html` | **PM_E2 — PolyMeter Editor (Notation)** — second-gen, engraved drum notation (Bravura/SMuFL): Staff / TUBS / Konnakol views, edit-on-staff |
| `/kit.html` · `/info-kit.html` | **PM_K1 Kit** — buildable Raspberry Pi Pico touchscreen unit (52Pi EP0172); info page has the wiring, parts and firmware |
| `/player.html` · `/info-player.html` | **PM_C1 Concept** — idealized concept device (full display + setlist nav, theme, fullscreen "stage" view) |
| `/teacher.html` · `/info-teacher.html` | **PM_T1 Teacher** — studio / lesson console (colour TFT, arcade buttons, 1/4″ instrument passthrough with analog click injection) |
| `/stage.html` · `/info-stage.html` | **PM_S1 Stage** — footpedal stompbox (two footswitches, expressionpedal in, RGB beat light, instrument passthrough) |
| `/micro.html` · `/info-micro.html` | **PM_P1 Practice** — inline practice bar (instrument in / out passthrough, clickable thumbroller, 14segment display) |
| `/showcase.html` · `/info-showcase.html` | **PM_D1 Display** — pyramid display piece; the pendulum is an RGB light bar combining every lane's subdivisions/accents |
| `/embed.html` · `/embed.js` | embed docs and the dropin loader |
| `/pico-main.py` | the PM_K1 MicroPython firmware (download) |
```sh
./build.sh # assemble the self-contained pages into dist/ (git-ignored)
./deploy.sh # build, stamp version, mirror dist/ to the Caddy web root, smoke-test
The buildable units (Teacher, Stage, Practice, Display, Kit) carry a priced BOM on their info
page; the Editor (web app) and Concept have none. Every page shares the same VARASYS header
(official logo with bakedin tagline, nav, theme toggle). The editor also shows a subtle live
**program string** of what's loaded — editable, with copy/paste — under the app (press `Enter`
or paste to apply; see [the share language](#the-share-language)).
Because nothing loads from the network, you can save a page (`Ctrl`/`⌘`+`S`) and open it
straight from disk to run fully offline. One catch from a local `file://`: the browser may not
persist `localStorage` between sessions, so use **Export all** (setlist **⋯** menu) to back up.
## Features
- **Meter lanes** — grouping (odd meters), subdivision (incl. swing), a drum/percussion
voice, per**step dynamics** (accent / normal / ghost / mute), mute, live measure counter.
- **Sounds** — every voice is **synthesized** in Web Audio: a friendly drum kit
(`kick`, `snare`, `hatClosed`, …) rendered with the **808 / 909** voices, the 808/909 voices
by name, and electronic/percussion tones. No samples are loaded.
- **Perlane gain** — a dB trim knob per lane (`@<db>` in the share language), applied at
schedule time so changing it never stutters playback.
- **Polyrhythm** — a perlane *poly* toggle fits a lane's beats evenly into lane 1's
bar (e.g. 5over4, 3over2).
- **Euclidean rhythms** — spread *k* hits evenly across *n* steps with `(k,n[,rot])`.
- **Practice** — gap/mute trainer (play N / mute M bars) and a tempo ramp with a
start BPM and signed step.
- **Set lists** — named, ordered lists of saved setups; **cue** across lists and commit
on a bar/beat boundary with no audible gap (see **Live performance**); each play is logged.
- **Sharing** — copy a link to your current settings or a whole set list.
- **Theming** — System / Light / Dark.
## The share language
A compact, humanreadable text encodes a full configuration (a *patch*). It's what
goes in a share link, the editor's program box, and a device's program list. You can
handwrite or edit it.
### Patch grammar
```
v1 ; t<bpm> [; vol<pct>] [; cd<sec>] [; b<bars>] ; <lane> … [; tr<play>/<mute>] [; rmp<start>/<step>/<every>]
```
## The `concepts` branch
| Token | Meaning | Example |
|-------|---------|---------|
| `v1` | format version (always first) | `v1` |
| `t<bpm>` | tempo | `t120` |
| `vol<pct>` | master volume 0100 | `vol70` |
| `cd<sec>` | time countdown, seconds (auto-advance with Continue) | `cd60` |
| `b<bars>` | segment length in bars (auto-advance with Continue) | `b16` |
| `tr<play>/<mute>` | gap trainer: play N bars, mute M | `tr2/2` |
| `rmp<start>/<step>/<every>` | tempo ramp: start BPM, ±step, every N bars | `rmp80/5/4` |
| `<lane>` | a meter lane (see below) | `kick:4` |
This `main` branch is intentionally lean. The **full project** — the PM_E1 editor, the
embeddable widget, the whole gallery of hardware **formfactor concepts**, the Raspberry Pi
Pico **firmware** editions, the **Rust** port, and the **KiCad/SPICE hardware** design — lives
on the [`concepts`](https://codeberg.org/VARASYS/metronome/src/branch/concepts) branch. It can
be promoted back to the front page at any time.
Tokens are joined with `;`. `tr` and `rmp` are omitted when off.
### Lane grammar
```
<sound> : <grouping> [ / <sub> ] [ (<k>,<n>[,<rot>]) ] [ = <pattern> ] [ @ <db> ] [ ~ ] [ ! ]
```
- **sound** — a synthesized voice. Friendly kit names (rendered with 808/909):
`beep`, `kick`, `snare`, `rim`, `clap`, `hatClosed`, `hatOpen`, `ride`, `crash`, `tomLow`,
`tomMid`, `tomHigh`, `tambourine`, `cowbell`, `woodblock`, `claves`; the drummachine voices
by name — `kick808 snare808 clap808 hat808 openHat808 cowbell808 tom808` and
`kick909 snare909 clap909 hat909 ride909 crash909`; or a **GeneralMIDI note number**
(`36`→kick, `38`→snare, `42`→closed hat, …). Unknown → `beep`.
- **grouping** — beats per bar, optionally grouped for odd meters: `4`, `3`, `2+2+3`.
Groups get a visual divider; accents are perstep (see `=pattern`).
- **`/sub`** — subdivision: `1` quarter (default), `2` eighth, `3` triplet, `4` sixteenth,
`6` sextuplet — also sets how many **pads** each beat splits into. Append **`s`** for **swing**
on even subdivisions (`2s`, `4s`) to delay the offbeats to a 2:1 triplet feel.
- **`(k,n[,rot])`** — **Euclidean** fill: place `k` hits as evenly as possible across `n`
steps, optionally rotated by `rot`. e.g. `kick:4(3,8)`.
- **`=pattern`** — per**step dynamics**, one char per pad: **`X`** accent, **`x`** normal,
**`g`** ghost (soft), **`.`** `-` `_` mute (rest). Length = beats × `sub`. Omit to get the
default — first step of each beat accented, the rest normal. e.g. `4=.X.X` accents 2 & 4.
- **`@<db>`** — perlane gain trim in decibels, e.g. `@-3` or `@+2`.
- **`~`** — polyrhythm: fit this lane's beats evenly into **lane 1's** bar.
- **`!`** — mute the lane.
### Examples
| Patch / lane | What it is |
|---|---|
| `kick:4` | kick on 4 quarter beats |
| `snare:4=.X.X` | accented snare backbeat (2 & 4) |
| `hatClosed:4/2` | eighthnote hihats (downbeat of each beat accented) |
| `ride:4/2s` | **swung** eighthnote ride |
| `kick:4(3,8)` | a 3over8 Euclidean kick |
| `claves:5~` | 5 evenly across lane 1's bar (5over4 if lane 1 is `4`) |
| `hat909:4/2@-4` | eighth 909 hats, trimmed 4 dB |
| `kick:2+2+3=x..x..x` | 7/8, kick on each group start |
| **Full:** `v1;t120;kick:4;snare:4=.x.x;hatClosed:4/2;tr2/2` | backbeat groove with gap trainer |
### In URLs
- **Settings:** `…/#p=<patch>` — readable, e.g. `…/#p=v1;t120;kick:4;claves:5~`
- **Set list:** `…/#sl=<base64url>` — a JSON `{title, description, items[]}` where each
item's config is a patch string.
Opening such a link applies the settings (or imports the set list) on load, then clears
the hash so a refresh won't reimport.
## Sharing
In the setlist panel's **⋯** menu:
- **Share settings link** / **Share setlist link** open a dialog with the link to **Copy**
or **Open**. The link encodes everything in the URL — nothing is uploaded.
- **Export all / Import file** back up your set lists and practice log as a JSON file.
## Embedding
Any form factor can be embedded in another page as a selfsizing widget. Drop in a
container and the loader script — it builds an `<iframe>` to the chromestripped
(`?embed=1`) page, preloads your config string, and autoresizes to the content:
```html
<div data-varasys-metronome="micro"
data-patch="v1;t120;kick:4;snare:4=.X.X;hatClosed:4/2"></div>
<script src="https://metronome.varasys.io/embed.js"></script>
```
- `data-varasys-metronome` — variant: `editor` · `kit` · `initial` · `teacher` · `stage` · `micro` · `showcase`.
- `data-patch` — a [patch string](#patch-grammar) (maps to `#p=`); or `data-setlist`
for a setlist code (maps to `#sl=`).
- `data-width` / `data-height` — optional initial size (default `100%` × `300px`;
height then tracks the widget, which posts `{type:'varasys-h', h}` to the parent).
Prefer your own iframe? `…/<variant>.html?embed=1#p=<patch>` works directly. The
[Concepts landing](index.html) and every `info-*.html` page dogfood this exact mechanism.
See `/embed.html`.
## Build it (hardware) — PM_K1 "Kit"
The **PM_K1 Kit** runs the same engine and program strings on a real device you can build today:
a **Raspberry Pi Pico** on the **52Pi EP0172 "Pico Breadboard Kit Plus"** — a 3.5″ ST7796
320×480 capacitivetouch screen (GT911), a PSP joystick, a WS2812 RGB LED, a buzzer and two
buttons, all prewired. See **`/info-kit.html`** for the pinout, parts (~$45 incl. Pico) and
flashing steps. Firmware lives in **`pico/`**:
- **`pico/main.py`** — singlefile **MicroPython** firmware: an ST7796 driver, GT911 touch,
WS2812 RGB, PWM buzzer, ADC joystick, baked antialiased fonts, and the polymeter engine.
It parses the same program strings as the web editor. Flash MicroPython, copy `main.py`,
edit the `PROGRAMS` list to change grooves. Download: `/pico-main.py`.
- **`pico/gen_font.py`** — generates the baked antialiased fonts (used by both firmwares).
- **`pico-cp/`** — a **CircuitPython** edition (download `/pm_k1_circuitpy.zip`): a selfcontained
appliance. The Pico mounts as a USB drive carrying the firmware + your `programs.json` + an offline
editor, drives a full lanes/pads touchscreen, **logs practice to `history.json`** on the device, takes
set lists **pushed from the editor over USBMIDI** (with a universal downloadanddrag fallback), and
plays **out your computer's speakers over USBMIDI** (the editor's **🎹 Device audio**). By default the
firmware owns the drive (readonly to the computer, so it's protected); hold **button A** at poweron for
editor mode (drive writable). **Firmware updates are one click** from the editor (⋯ → Update firmware) —
pushed over USBMIDI as an A/B update with automatic rollback. The MicroPython build stays the simple,
nocomputer option.
## Keyboard shortcuts
| Key | Action |
|-----|--------|
| `Space` | play / stop (works everywhere except while typing in a text field) |
| `T` | tap tempo |
| `←` / `→` | tempo ±1 (`Shift` = ±10) |
| `A` | add meter lane |
| `↑` / `↓` / `Home` / `End` | move the **cue** cursor (crosses set lists) |
| `PgUp` / `PgDn` | cue the previous / next set list |
| `Enter` | commit the cued item — switches on the next **bar** (smooth) |
| `Shift`+`Enter` | commit now — switches on the next **beat** (rude) |
| `N` / `P` | load next / previous immediately (rude quickstep) |
| `Alt`+`↑` / `Alt`+`↓` | reorder the cued item |
| `1``9` | enable / silence lane 19 |
| `?` | shortcuts help |
| `Esc` | close the help / share dialog · cancel an armed switch |
(Arrow / navigation keys are left alone while a slider or dropdown is focused, so they still adjust it.)
## Live performance
The set list is performance-ready: you can line up where you're going next without
disturbing what's playing, then commit on a musical boundary — no audible gap.
- **Cue, then commit.** The arrows / `Home` / `End` / `PgUp` / `PgDn` move a *cue cursor*
(amber outline) through items — across set lists, without loading anything. **`Enter`**
commits with a **smooth** cutover at the next **bar**; **`Shift`+`Enter`** is a **rude**
cutover at the next **beat**. `N` / `P` are immediate rude quicksteps. `Esc` cancels.
- **Barlength segments.** Give an item a **bar** count (Timers box, or `b<n>`) and a bar
countdown (▦) shows bars remaining. With **Continue** on, it autoadvances at the bar
boundary — so a *song* is just a set list of segments that hand off seamlessly.
- All transitions keep the clock continuous; the loaded item can live in a set list you're
not currently viewing (the player names it).
## Build
Every page is a source that shares code through `@BUILD:*` markers, so they all stay in sync:
- `/*@BUILD:include:src/…@*/` inlines a **shared partial** — the audio/scheduler engine
(`src/engine.js`), the seed set lists (`src/setlists.js`, so every page ships the **same
default set lists**), base styling (`src/base.css`), the site **header/footer/chrome**
(`src/header.html`, `src/footer.html`, `src/chrome.js`), the perdevice **program box**
(`src/progbox.{html,js}`) and the infopage **livewidget embed** (`src/infoembed.{html,js}`).
- `@BUILD:favicon@`, `@BUILD:logo-dark@`, `@BUILD:logo-light@` inline the base64 assets from
`assets/` (the official logos already include the tagline).
`./build.sh` resolves every marker into a selfcontained page in `dist/` (the Concepts landing,
the editor, the device/formfactor pages and their `info-*.html`), copies `embed.js` through
asis, and copies the Pico firmware to `dist/pico-main.py`. `dist/` is generated, gitignored —
don't edit it by hand. `deploy.sh` runs the build first, so a deploy always serves freshly
assembled pages.
## Versioning
`VERSION` holds the formal version. `deploy.sh` builds, then stamps the served page:
- **Formal** — a clean commit tagged `v<VERSION>``X.Y.Z`.
- **Dev** — anything else → `X.Y.Z-dev.<utc-timestamp>.<short-sha>[.dirty]`.
Cut a release with `./release.sh [X.Y.Z]` — the optional arg bumps & commits `VERSION`; it then
tags the current commit `v<VERSION>` (requires a clean tree). Push the tag, then deploy.
## Files
| File | Purpose |
|------|---------|
| `index.html` | the **Concepts** landing / gallery (embeds each widget live) |
| `editor.html` | the **PM_E1 editor** app (source, with `@BUILD:*` markers) |
| `pm_e-2.html` · `src/notation.js` | the **PM_E2 notation editor** + its Bravura/SMuFL engraving engine (`tools/bravura/` subsets the font → `assets/bravura.woff2.b64`, inlined via `@BUILD:bravura@`) |
| `kit.html` · `player.html` · `teacher.html` · `stage.html` · `micro.html` · `showcase.html` | the device widget pages (PM_K1 Kit, PM_C1 Concept, Teacher, Stage, PM_P1 Practice, PM_D1 Display) |
| `info-*.html` | performfactor spec pages (embed the live widget + description + dimensions + BOM) |
| `embed.html` · `embed.js` | embed docs and the dropin widget loader |
| `src/` | shared partials inlined into every page: `engine.js`, `setlists.js`, `base.css`, `header.html`, `footer.html`, `chrome.js`, `progbox.{html,js}`, `infoembed.{html,js}` |
| `assets/` | base64 blobs inlined at build (`favicon`, `logo-dark`, `logo-light`) |
| `pico/` | PM_K1 MicroPython firmware: `main.py`, `gen_font.py` (font generator), `README.md` |
| `pico-cp/` | PM_K1 CircuitPython edition: `code.py`, `programs.json`, `font_*.bin`, `README.md` (bundled + served as `/pm_k1_circuitpy.zip`) |
| `build.sh` | resolve markers → selfcontained `dist/` pages (+ `pico-main.py`) |
| `deploy.sh` | build, then publish to the Caddy web root |
| `release.sh` | tag a formal version |
| `VERSION` | formal version string |
| `LICENSE` | GNU AGPL v3 license text |
## License
[GNU AGPL v3](./LICENSE) © VARASYS.
Copyright (C) 2026 Varasys.
This program is free software: you can redistribute it and/or modify it under the terms of the
**GNU Affero General Public License** as published by the Free Software Foundation, either
version 3 of the License, or (at your option) any later version. See [`LICENSE`](LICENSE).
Because the app is served over a network, the AGPL's §13 applies: anyone interacting with a
hosted instance must be able to get its source — the public repository is
**<https://codeberg.org/VARASYS/metronome>** (also linked from the inapp **?** help).
### Credits
All drum and percussion voices are **synthesized in Web Audio** (808/909style and electronic) —
there are no audio samples. The ondevice fonts (PM_K1) are rendered from **DejaVu Sans**.

View file

@ -1,23 +1,34 @@
#!/usr/bin/env bash
# Assemble the deployed single-file pages from source + shared partials + assets/.
#
# Each page is a source that shares code via markers:
# Every page (the Concepts landing, the editor app, and the device/form-factor
# pages) is a source that shares code via markers:
# /*@BUILD:include:src/<file>@*/ inlines a shared partial (engine, seed lists, base CSS, header/footer/chrome)
# @BUILD:favicon@ / @BUILD:logo-*@ / @BUILD:bravura@ inline base64 assets
# @BUILD:favicon@ / @BUILD:logo-*@ inline base64 assets (voices are all synthesized — no samples)
# This resolves them so each built page in dist/ is one self-contained file
# (zero deps, works fully offline). deploy.sh runs this first. dist/ is generated —
# don't edit or commit it.
#
# NOTE: this is the slim `main` branch — only the landing chooser, the mobile app
# (+ its practice journal) and the pm_e-2 notation editor ship. The full
# multi-form-factor project (all device pages, firmware, Rust, hardware) lives on
# the `concepts` branch.
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
rm -rf dist && mkdir -p dist # start clean so no stale artifact survives into deploy's rsync --delete
mkdir -p dist
# Precompile the PM_K-1 CircuitPython firmware to .mpy. CircuitPython compiles a ~56KB .py at boot,
# which fragments the heap and OOMs on the RP2040; a precompiled .mpy loads without compiling. Needs
# Adafruit's mpy-cross matching the device's CircuitPython (10.2.1) -> emits CircuitPython mpy v6.
MPYC="$PWD/tools/mpy-cross"; ROOT="$PWD"
[[ -x "$MPYC" ]] || { echo "error: $MPYC missing (Adafruit mpy-cross for CircuitPython 10.2.1)" >&2; exit 1; }
( cd pico-cp && "$MPYC" app.py -o "$ROOT/dist/app.mpy" ) # compile from pico-cp/ so tracebacks read "app.py"
echo "precompiled dist/app.mpy ($(stat -c%s dist/app.mpy) bytes <- $(stat -c%s pico-cp/app.py) source)"
# PM_X-1 Explorer firmware uses the same mpy-cross. Output as dist/explorer-app.mpy so the Kit + Explorer
# bundles each ship their own precompiled binary; the served URLs follow the same one-target-per-file rule.
( cd pico-explorer && "$MPYC" app.py -o "$ROOT/dist/explorer-app.mpy" )
echo "precompiled dist/explorer-app.mpy ($(stat -c%s dist/explorer-app.mpy) bytes <- $(stat -c%s pico-explorer/app.py) source)"
# PM_G-1 "Grid" ships the native Rust firmware now (rust/pm-grid → pm-grid.uf2, built by
# rust/pm-grid/build.sh and served by deploy.sh). The old CircuitPython build (pico-scroll/app.py)
# is no longer bundled or served; the source stays in-repo as the reference port.
python3 - <<'PY'
import pathlib, re
import os, pathlib, re
A = pathlib.Path("assets")
def build(name):
@ -25,7 +36,7 @@ def build(name):
# 1) inline shared partials (function-replacement: no backslash/group interpretation)
src = re.sub(r"/\*@BUILD:include:([^@]+)@\*/",
lambda m: pathlib.Path(m.group(1)).read_text().rstrip("\n"), src)
# 2) inline base64 assets (all voices are synthesized — no audio samples)
# 2) inline base64 assets (voices are all synthesized now — no samples)
src = src.replace("@BUILD:favicon@", (A / "favicon.b64").read_text().strip())
src = src.replace("@BUILD:logo-dark@", (A / "logo-dark.b64").read_text().strip())
src = src.replace("@BUILD:logo-light@", (A / "logo-light.b64").read_text().strip())
@ -37,13 +48,51 @@ def build(name):
out.write_text(src)
return out.stat().st_size
for name in ("index.html", "mobile.html", "mobile-sessions.html", "pm_e-2.html"):
for name in ("index.html","editor.html","editor-beta.html","pm_e-2.html","player.html","mobile.html","mobile-sessions.html","teacher.html","stage.html","micro.html","showcase.html","kit.html","explorer.html","grid.html",
"embed.html",
"info-editor.html","info-pm_e-2.html","info-player.html","info-teacher.html","info-stage.html","info-micro.html","info-showcase.html","info-kit.html","info-explorer.html","info-grid.html"):
print("built %s (%dKB)" % (name, build(name) // 1024))
pathlib.Path("dist/embed.js").write_text(pathlib.Path("embed.js").read_text()) # loader, served as-is
print("copied embed.js")
# PWA support files for mobile.html (the phone/tablet app): manifest, service worker, icons.
for f in ("manifest.webmanifest", "mobile-sw.js"):
pathlib.Path("dist/" + f).write_text(pathlib.Path(f).read_text())
for f in ("icon-192.png", "icon-512.png", "icon-180.png"):
pathlib.Path("dist/" + f).write_bytes((A / f).read_bytes())
print("copied PWA files (manifest.webmanifest, mobile-sw.js, icon-{192,512,180}.png)")
pathlib.Path("dist/pico-main.py").write_text(pathlib.Path("pico/main.py").read_text()) # PM_K-1 firmware, downloadable
print("copied pico-main.py")
_appsrc = pathlib.Path("pico-cp/app.py").read_text()
# The A/B updater pushes app.py over USB-MIDI as 7-bit data, so it MUST be pure ASCII -- a stray
# non-ASCII char (e.g. an em-dash in a comment) gets mangled to a NUL byte and bricks the device.
_bad = [(i, c) for i, c in enumerate(_appsrc) if ord(c) > 0x7F]
assert not _bad, "pico-cp/app.py has non-ASCII at %r -- keep it ASCII (version regex + clean source)" % (_bad[:5],)
pathlib.Path("dist/pico-cp-app.py").write_text(_appsrc) # served for version reading + as readable reference
# the editor pushes the PRECOMPILED .mpy (base64); serve it next to the source
pathlib.Path("dist/pico-cp-app.mpy").write_bytes(pathlib.Path("dist/app.mpy").read_bytes())
print("copied pico-cp-app.py + pico-cp-app.mpy")
_xsrc = pathlib.Path("pico-explorer/app.py").read_text() # PM_X-1 Explorer firmware (sibling to the Kit)
_xbad = [(i, c) for i, c in enumerate(_xsrc) if ord(c) > 0x7F]
assert not _xbad, "pico-explorer/app.py has non-ASCII at %r -- keep it ASCII (version regex + clean source)" % (_xbad[:5],)
pathlib.Path("dist/pico-explorer-app.py").write_text(_xsrc) # editor reads APP_VERSION from here
pathlib.Path("dist/pico-explorer-app.mpy").write_bytes(pathlib.Path("dist/explorer-app.mpy").read_bytes())
print("copied pico-explorer-app.py + pico-explorer-app.mpy")
import zipfile # PM_K-1 CircuitPython drive bundle (download → unzip onto CIRCUITPY)
with zipfile.ZipFile("dist/pm_k1_circuitpy.zip", "w", zipfile.ZIP_DEFLATED) as z:
for f in ("code.py", "boot.py", "programs.json", "font_s.bin", "font_m.bin", "font_l.bin",
"logo.bin", "midi.bin", "usb.bin", "README.md", "protect-firmware.sh"):
z.write("pico-cp/" + f, f)
z.write("dist/app.mpy", "app.mpy") # the precompiled firmware (NOT app.py - too big to compile on-device)
z.write("dist/editor.html", "editor.html") # offline copy of the editor, on the drive
print("zipped pm_k1_circuitpy.zip")
# PM_X-1 Explorer drive bundle (download → unzip onto CIRCUITPY on the Pimoroni Explorer with CircuitPython for Pico 2)
with zipfile.ZipFile("dist/pm_x1_circuitpy.zip", "w", zipfile.ZIP_DEFLATED) as z:
for f in ("code.py", "boot.py", "programs.json", "README.md"):
z.write("pico-explorer/" + f, f)
for f in ("font_s.bin", "font_m.bin", "font_l.bin", "logo.bin", "midi.bin", "usb.bin"):
z.write("pico-cp/" + f, f) # fonts + icons are resolution-agnostic; reuse the Kit's baked blobs
z.write("dist/explorer-app.mpy", "app.mpy")
z.write("dist/editor.html", "editor.html")
print("zipped pm_x1_circuitpy.zip")
# PM_G-1 Grid is the native Rust firmware now — no CircuitPython bundle (see rust/pm-grid).
PY

View file

@ -5,10 +5,9 @@
# Caddy config: /var/lib/caddy/Caddyfile (metronome.varasys.io:8443 block)
# Bind-mount: /etc/containers/systemd/caddy.container
#
# The web root is bind-mounted read-only into the Caddy container and served by
# file_server, which picks up changes immediately — so a plain file copy is all
# that's needed (no container restart). The web root is mirrored from dist/ with
# `rsync --delete`, so anything no longer built is removed from the live site.
# The web root is bind-mounted read-only into the Caddy container and
# served by file_server, which picks up changes immediately — so a plain
# file copy is all that's needed (no container restart).
set -euo pipefail
SRC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@ -39,20 +38,63 @@ else
BUILD="$VER-dev.$(date -u +%Y%m%dT%H%M%SZ)" # not a git checkout
fi
# stamp the version into the built copies only (source keeps the APP_VERSION placeholder)
for f in index.html mobile.html mobile-sessions.html pm_e-2.html; do
[[ -f "$DIST_DIR/$f" ]] || continue
tmp="$(mktemp)"
sed "s|const APP_VERSION = \"[^\"]*\";|const APP_VERSION = \"$BUILD\";|" "$DIST_DIR/$f" > "$tmp"
mv "$tmp" "$DIST_DIR/$f"
done
# Mirror dist/ -> web root, deleting anything that's no longer built (old pages, firmware, …)
rsync -a --delete "$DIST_DIR/" "$DEST_DIR/"
# stamp the version into the built copy only (source stays clean)
echo "deployed v$BUILD -> $DEST_DIR"
for f in index.html mobile.html mobile-sessions.html pm_e-2.html; do
for f in index.html editor.html editor-beta.html pm_e-2.html player.html mobile.html mobile-sessions.html teacher.html stage.html micro.html showcase.html kit.html explorer.html grid.html \
embed.html \
info-editor.html info-pm_e-2.html info-player.html info-teacher.html info-stage.html info-micro.html info-showcase.html info-kit.html info-explorer.html info-grid.html; do
sed "s|const APP_VERSION = \"[^\"]*\";|const APP_VERSION = \"$BUILD\";|" "$DIST_DIR/$f" > "$DEST_DIR/$f"
echo " $f ($(stat -c '%s' "$DEST_DIR/$f") bytes)"
done
cp "$DIST_DIR/embed.js" "$DEST_DIR/embed.js"; echo " embed.js ($(stat -c '%s' "$DEST_DIR/embed.js") bytes)"
# PWA assets for mobile.html (manifest + service worker + icons) — served at the web root
for f in manifest.webmanifest mobile-sw.js icon-192.png icon-512.png icon-180.png; do
cp "$DIST_DIR/$f" "$DEST_DIR/$f"; echo " $f ($(stat -c '%s' "$DEST_DIR/$f") bytes)"
done
cp "$DIST_DIR/pico-main.py" "$DEST_DIR/pico-main.py"; echo " pico-main.py ($(stat -c '%s' "$DEST_DIR/pico-main.py") bytes)" # PM_K-1 firmware download
# Rust firmware (RP2350) — served if built via rust/pm-kit/build.sh (gitignored artifact, not in dist/)
if [[ -f "$SRC_DIR/rust/pm-kit/pm-kit.uf2" ]]; then
cp "$SRC_DIR/rust/pm-kit/pm-kit.uf2" "$DEST_DIR/pm-kit.uf2"
echo " pm-kit.uf2 ($(stat -c '%s' "$DEST_DIR/pm-kit.uf2") bytes) # Rust RP2350 firmware (alpha live metronome)"
fi
# ELF with defmt info — `probe-rs run --chip RP235x pm-kit.elf` flashes over the Debug Probe and
# streams logs/panics. Needed locally (not the uf2) because defmt decodes log strings from the ELF.
if [[ -f "$SRC_DIR/rust/pm-kit/pm-kit.elf" ]]; then
cp "$SRC_DIR/rust/pm-kit/pm-kit.elf" "$DEST_DIR/pm-kit.elf"
echo " pm-kit.elf ($(stat -c '%s' "$DEST_DIR/pm-kit.elf") bytes) # probe-rs flash + defmt RTT logging"
fi
# Rust firmware (RP2040 / Pico Scroll Pack) — served if built via rust/pm-grid/build.sh (gitignored).
# BOOTSEL-drag pm-grid.uf2 onto the RPI-RP2 drive to flash the PM_G-1 Grid LED metronome.
if [[ -f "$SRC_DIR/rust/pm-grid/pm-grid.uf2" ]]; then
cp "$SRC_DIR/rust/pm-grid/pm-grid.uf2" "$DEST_DIR/pm-grid.uf2"
echo " pm-grid.uf2 ($(stat -c '%s' "$DEST_DIR/pm-grid.uf2") bytes) # Rust RP2040 firmware (the PM_G-1 Grid)"
fi
# ELF with defmt info — `probe-rs run --chip RP2040 pm-grid.elf` flashes over the Pi Debug Probe and
# streams defmt RTT logs/panics (decoded from the ELF). Served for local probe-based debugging.
if [[ -f "$SRC_DIR/rust/pm-grid/pm-grid.elf" ]]; then
cp "$SRC_DIR/rust/pm-grid/pm-grid.elf" "$DEST_DIR/pm-grid.elf"
echo " pm-grid.elf ($(stat -c '%s' "$DEST_DIR/pm-grid.elf") bytes) # probe-rs flash + defmt RTT logging"
fi
cp "$DIST_DIR/pm_k1_circuitpy.zip" "$DEST_DIR/pm_k1_circuitpy.zip"; echo " pm_k1_circuitpy.zip ($(stat -c '%s' "$DEST_DIR/pm_k1_circuitpy.zip") bytes)" # PM_K-1 CircuitPython bundle
cp "$DIST_DIR/pico-cp-app.py" "$DEST_DIR/pico-cp-app.py"; echo " pico-cp-app.py ($(stat -c '%s' "$DEST_DIR/pico-cp-app.py") bytes)" # served for version reading + reference
cp "$DIST_DIR/pico-cp-app.mpy" "$DEST_DIR/pico-cp-app.mpy"; echo " pico-cp-app.mpy ($(stat -c '%s' "$DEST_DIR/pico-cp-app.mpy") bytes)" # precompiled firmware the editor pushes (base64)
cp "$DIST_DIR/pm_x1_circuitpy.zip" "$DEST_DIR/pm_x1_circuitpy.zip"; echo " pm_x1_circuitpy.zip ($(stat -c '%s' "$DEST_DIR/pm_x1_circuitpy.zip") bytes)" # PM_X-1 Explorer CircuitPython bundle
cp "$DIST_DIR/pico-explorer-app.py" "$DEST_DIR/pico-explorer-app.py"; echo " pico-explorer-app.py ($(stat -c '%s' "$DEST_DIR/pico-explorer-app.py") bytes)" # served for version reading
cp "$DIST_DIR/pico-explorer-app.mpy" "$DEST_DIR/pico-explorer-app.mpy"; echo " pico-explorer-app.mpy ($(stat -c '%s' "$DEST_DIR/pico-explorer-app.mpy") bytes)" # PM_X-1 firmware (the editor pushes this when device id = X)
# PM_G-1 Grid is the native Rust firmware now (pm-grid.uf2 above) — the CircuitPython grid
# artifacts (pm_g1_circuitpy.zip / pico-scroll-app.{py,mpy}) are no longer built or served.
rm -f "$DEST_DIR/pm_g1_circuitpy.zip" "$DEST_DIR/pico-scroll-app.py" "$DEST_DIR/pico-scroll-app.mpy" # remove stale Python grid downloads
rm -f "$DEST_DIR/player-asbuilt.html" # renamed to teacher.html
rm -f "$DEST_DIR/concepts.html" # Concepts is now the landing (/)
# info-*.html are first-class pages again: each form factor has a lean widget page
# (<device>.html) + a separate spec/BOM page (info-<device>.html that embeds it).
# If real audio samples are added later (see the plan's GM-sample note),
# sync that directory too.
if [[ -d "$SRC_DIR/samples" ]]; then
rsync -a --delete "$SRC_DIR/samples/" "$DEST_DIR/samples/"
echo "synced samples/ -> $DEST_DIR/samples"
fi
# Smoke test: Caddy serves on :8443 with tls internal; resolve the host
# to localhost so SNI matches the site block.

132
docs/daisy-spike.md Normal file
View file

@ -0,0 +1,132 @@
# Daisy Pod spike — scope + status
**Status:** **code complete, host-verified, awaiting hardware.** The firmware crate
[`rust/pm-daisy`](../rust/pm-daisy/) builds for the target and the shared engine is proven on the
host (see "What's built" below). Hardware has arrived (a Daisy Seed **1.2 / Seed2 DFM**, PCM3060
codec — build with `./build.sh seed_1_2`) — flash + listen, then append the verdict below.
**Target hardware: Daisy *Pod*** (a Daisy Seed on a dev board with audio jack, buttons, knobs,
encoder, RGB LEDs). The Seed is the brain; the spike itself uses its audio out + the Seed USER LED.
> **Follow-on (code-complete, awaiting the same hardware bring-up):** the Pod's extra controls — 2
> buttons, encoder + push, 2 knobs, 2 RGB LEDs — are now wired (`rust/pm-daisy/src/controls.rs` +
> `leds.rs`, mapped in `programs.rs`): encoder = tempo / play-pause, buttons = program step, knob 1 =
> volume, RGB LEDs = beat-by-dynamic + transport. Boot still plays the spike groove, so the criteria
> below are unaffected. Pin map + control table in [`rust/pm-daisy/README.md`](../rust/pm-daisy/README.md).
**Decision it informs:** is the [Electrosmith Daisy](https://electro-smith.com/products/pod)
platform (STM32H750, Cortex-M7 @ 480 MHz + onboard 24-bit audio codec) the right home for the audio
engine *if* PolyMeter ever grows past "metronome" into a real-time audio workstation (live input FX,
heavy polyphony, recording)? Today the answer is "RP2350 is the pick" (see [rust-port.md](rust-port.md))
— this spike exists to get a **first-hand, low-stakes** read on the alternative, not to commit to it.
## What's built (this is done)
| Piece | Where | Verified |
|---|---|---|
| Shared self-running sequencer `Player` | `rust/pm-synth/src/lib.rs` | host build + render |
| `pm-synth` made `#![no_std]` (libm float math) | same | builds host **and** thumbv7em |
| Host preview / "simulator" → `pm-daisy-preview.wav` | `rust/synthrender` | renders; 48 kHz, on-grid onsets |
| Daisy firmware (heap, board init, SAI-DMA callback, beat LED) | `rust/pm-daisy/` | **cross-compiles + links**, ~87 KB (fits 128 KB flash), all 3 codec revisions build |
| Flashing + revision docs | `rust/pm-daisy/README.md` | — |
**The host preview IS the simulator.** Full STM32H7 emulation (Renode) exists but won't faithfully
model the audio codec — useless for "does it sound right." Instead, because the engine is shared,
`synthrender` drives the *exact* `Player` the firmware runs and writes `pm-daisy-preview.wav` — a
bit-identical preview of the hardware output (before its codec). Run `cargo run` in `rust/synthrender`
and listen. When the Pod arrives, flashing should reproduce that WAV out the jack.
This is a **digital/DSP-home** experiment only. It deliberately does **not** touch the heirloom
pro-analog chain (THAT receivers/drivers, low-jitter clock, mute relay) from
[`hardware/DESIGN.md`](../hardware/DESIGN.md) — the Daisy's onboard codec is a consumer part. The
spike answers "does the engine feel at home on this chip," not "is this the heirloom audio path."
## The bet, in one sentence
Almost the entire engine already transfers: `track-format` is `no_std` and RP2350-proven, `pm-synth`
is pure `f32` with no real dependencies, **the STM32H750 has a hardware FPU so those `f32` voices run
natively** (no fixed-point rewrite — the opposite of the RP2350's Cortex-M0+ situation flagged in
`pm-synth/Cargo.toml`), and `synthrender/src/main.rs`'s render loop *is* the audio callback. So the
spike is mostly **transport bring-up**, not an engine rewrite.
## What transfers for free vs. what's new
| Piece | Source | Spike work |
|---|---|---|
| Track parse + schedule | `rust/track-format` (`#![no_std]`, builds for RP2350) | **none** — use verbatim |
| Drum voices / click engine | `rust/pm-synth` (`Synth::new` / `trigger(name,level)` / `next_sample()->f32`) | **small** — see "no_std-ify" below |
| Render→audio loop shape | `rust/synthrender/src/main.rs` `render()` | **adapt** — pre-rendered buffer → streaming callback |
| Audio transport (codec + SAI) | — | **new** — the actual spike |
| Toolchain / flash / logs | reuse the probe-rs + defmt workflow from `pm-grid`/`pm-kit` ([probe-flash.md](../rust/probe-flash.md)) | **setup** |
### no_std-ifying `pm-synth` (mechanical, ~½ day)
`pm-synth` already uses `alloc::vec` and `core::f32::consts` — it's half-way there. To build for the
target it needs:
1. `#![no_std]` + `extern crate alloc;` at the crate root.
2. **`libm`** for the float methods it calls as inherent fns (`.sin()`, `.cos()`, `.powf()`,
`.tanh()`, `.floor()` — see `pm-synth/src/lib.rs:19,21,49,56,85,221`). In `no_std` these aren't
inherent on `f32`; route them through `libm::{sinf,cosf,powf,tanhf,floorf}` (or the `num-traits`
`Float` shim). Gate with `#[cfg(feature = "std")]` so the host `synthrender` keeps building.
3. A **global allocator** on-device (`embedded-alloc`), because `trigger()` does `alloc::vec![…]`
per voice. The STM32H750 has ample RAM (≥1 MB SRAM, +64 MB SDRAM on the Seed), so a small heap is
trivial — **but** allocating in the audio path is a real-time smell. Whether it glitches is itself
a useful spike finding (see Decision criteria); production would pre-allocate fixed voices.
### Streaming the render loop (~½ day)
`synthrender` pre-renders an entire `Vec<i16>`. On-device, keep a running sample counter `n` and a
click index `ci`, and inside the SAI block callback do per-frame what the host loop does per-sample:
advance `t_ns`, `trigger()` any clicks whose `time_ns <= t_ns`, then `next_sample()`. The engine is
48 kHz mono; the codec is stereo — duplicate the sample to L/R. Loop the pattern by resetting `n`/`ci`
at `master_bar_ns * bars`.
## Phases (time-boxed ~2 days)
1. **Toolchain + flash hello (½ day).** Add `rust/pm-daisy` (thin BSP binary). Target
`thumbv7em-none-eabihf`. Pick the Rust BSP: **`daisy`** (zlosynth) or **`libdaisy`** for a
blocking SAI-DMA callback, or **`daisy-embassy`** if we want async. Blink + a defmt "hello" over
RTT to confirm flashing. Flash via **USB DFU** (no extra hardware — Daisy boots to DFU) *or*
probe-rs RTT with the Pi Debug Probe we already use.
2. **Codec test tone (½ day).** Stand up the SAI audio callback at 48 kHz and emit a sine — confirms
the codec init (match the Seed revision: **AK4556** rev4 / **WM8731** Seed 1.1 / **PCM3060** Seed
1.2/2 — the BSP crate selects this; verify the rev at purchase) and that audio comes out the line jack.
3. **Drop in the engine (½ day).** Depend on `track-format` + `pm-synth`, no_std-ify `pm-synth`, port
the streaming loop. Play a hardcoded program (`t124;kick909:4;clap909:4=.X.X;hat909:4/2=.X.X.X.X`,
the same vector `synthrender` already auditions) on a loop.
4. **Measure + write up (½ day).** Capture findings against the criteria below; append a verdict to
this file and a one-line pointer in `rust-port.md`.
## Deliverables
- `rust/pm-daisy/` — a thin BSP crate playing a hardcoded 909 pattern out the Daisy line jack.
- `pm-synth` building `no_std` (host `synthrender` still builds — guarded by a `std` feature).
- A **findings verdict** appended here: go/no-go, with the numbers below.
## Decision criteria (what the verdict answers)
- **Sound:** clean line-out, no audible glitches/zipper noise on the looping pattern.
- **Timing:** clicks land tight (callback keeps up; no underruns logged).
- **Alloc-in-callback:** does `trigger()`'s per-voice `alloc::vec!` cause dropouts under polyphony
(the `poly` demo vector)? If yes → production needs fixed pre-alloc voices (true on *any* target —
useful regardless of chip choice).
- **Porting friction:** how much of the ~2-day estimate was real? Low friction + the codec "just
working" = Daisy is a credible workstation home. High friction / ecosystem fights = confirms RP2350.
## Non-goals (explicitly out of scope for the spike)
USB-MIDI, live-sync/SysEx, display/LEDs, set lists, buttons, the pro-analog chain, fixed-point/
production voice allocation, A/B firmware update. The spike proves **one thing**: the click engine
sings on this chip with acceptable effort.
## Risks / unknowns
- **Codec revision mismatch** — confirm which Seed rev (AK4556/WM8731/PCM3060) ships; the BSP crate
must match. Verify at purchase ([verify-datasheets memory]).
- **`pm-synth` float port** — mechanical but touches every voice; the `std`-feature gate keeps the
host renderer green so the conformance story is unaffected.
- **Heap in the audio path** — flagged above; the spike is exactly how we learn if it matters.
- **Sunk-cost honesty** — even a *great* result doesn't move the metronome off RP2350 today; it only
de-risks a *future* workstation pivot. Keep that framing in the verdict.
## References
- [Daisy Seed product page + datasheet](https://electro-smith.com/products/daisy-seed) — STM32H750, 24-bit/96 kHz codec, FPU
- Rust BSPs: [`daisy` (zlosynth)](https://github.com/zlosynth/daisy) · [`libdaisy`](https://crates.io/crates/libdaisy) · [`daisy-embassy`](https://crates.io/crates/daisy-embassy)
- [libDaisy audio-callback model](https://electro-smith.github.io/libDaisy/md_doc_2md_2__a3___getting-_started-_audio.html) (interleaved/non-interleaved; the C reference for the SAI callback shape)
- Reuse targets in-repo: `rust/track-format`, `rust/pm-synth`, `rust/synthrender/src/main.rs`
</content>
</invoke>

369
docs/livesync-protocol.md Normal file
View file

@ -0,0 +1,369 @@
# PM Live-Sync protocol (beta)
Bidirectional live mirror between the **PM_E1 editor** (web) and a **PM_K1 device**
(firmware). When armed, either side can edit a groove, change tempo/volume,
start/stop, or select a setlist item, and the other side reflects it in real
time.
It rides the **existing USBMIDI SysEx channel** (manufacturer `0x7D`) that the
device link already uses for RTC / version / programs / firmware — no new
transport, no new browser permission.
- **Editor side:** implemented in `src/livesync.js` + hooks in `editor-beta.html`.
- **Device side:** to be implemented in `pico-cp/app.py` (this document is the contract).
- **Browser support:** Web MIDI = Chrome / Edge / Firefox (no Safari), same as the existing "Device audio" feature.
---
## 1. Frames
Every message is one SysEx frame:
```
F0 7D <op> <payload ASCII bytes, each 0x000x7F> F7
```
`<op>` lives in the free `0x40` block (existing ops: `0x01` RTC, `0x02/0x03`
version, `0x10` programs, `0x21/22/23` firmware, `0x7E/0x7F` NAK/ACK):
| op | name | direction | payload |
|------|---------|------------------|-------------------------------------------|
| 0x40 | HELLO | either → either | `<origin>` |
| 0x41 | FULL | either → either | `<origin>;<seq>;<running>;<sl>;<item>;<patch>` |
| 0x42 | DELTA | either → either | `<origin>;<seq>;<evt>` |
| 0x43 | BYE | either → either | `<origin>` |
| 0x44 | SLSYNC | either → either | `<origin>;<seq>;<json>` — live set-list **content** merge (§8) |
| 0x45 | LOGSYNC | either → either | `<origin>;<seq>;<json>` — practice-log entry merge (§9) |
- **Payload is 7bit ASCII** — never emit a byte > `0x7F` (it corrupts the SysEx
stream and, per `build.sh`, would also break the firmwareupdate path). All
sharelanguage patch strings are already ASCII.
- `<origin>` — a short persession id (the editor uses e.g. `e1a2b3c`). Used to
drop your own echoes (see §4).
- `<seq>` — a monotonically increasing integer per sender. Informational /
duplicatedrop; ordering is guaranteed by USBMIDI so no reordering logic is
required.
- `<running>``0` or `1`.
- `<sl>` / `<item>` — setlist and item index of the loaded program, or `-1`.
- `<patch>` — a sharelanguage patch string (see §3). It contains `;` and `/`,
so it is **always the tail**: parse the first 5 `;`fields, then rejoin the
rest as the patch.
---
## 2. DELTA event grammar (`<evt>`)
One mutation, no `;` inside. Reuses the sharelanguage tokens (see
`src/engine.js` / README "Share language").
| evt | meaning |
|------------------------------|----------------------------------------------------|
| `play` | start transport |
| `stop` | stop transport |
| `bpm=<n>` | set tempo (clamped to the firmware's BPM range) |
| `vol=<pct>` | master volume, 0100 |
| `sel=<sl>/<item>` | cue/load a setlist item |
| `beat=<lane>/<step>/<level>` | perstep dynamics; level `0/1/2/3` = mute/normal/accent/ghost |
| `lane=<lane>/<field>/<value>`| lane field edit (see below) |
`<lane>` and `<step>` are **0based** indices into the current program's lane
list / that lane's step list (same order both sides).
`lane=` fields and values:
| field | value |
|-----------|------------------------------------------------|
| `sound` | voice name (`kick`, `snare`, `hatClosed`, …) |
| `groups` | grouping string, e.g. `2+2+3` |
| `sub` | subdivision int: 1 / 2 / 3 / 4 / 6 |
| `swing` | `0` or `1` |
| `gain` | dB int, e.g. `-3` |
| `poly` | `0` or `1` |
| `enabled` | `0` or `1` (0 = silenced lane) |
> Structural changes that reshape the lane list (add lane, remove lane,
> reorder) are **not** sent as deltas. Send a fresh **`0x41` FULL** instead — it
> is simpler and selfhealing. The editor does exactly this (a coalesced
> fullstate push ~150 ms after the last structural/practice edit).
---
## 3. What each side emits vs. applies
The two halves are **asymmetric in what they emit but symmetric in what they
apply** — each must apply *every* op/evt listed above.
**Editor emits:**
- fine `0x42` deltas for `play`/`stop`, `bpm`, `vol`, `sel`, `beat`
- a coalesced `0x41` FULL for any lanefield / add / remove / practice (trainer,
ramp, segment bars, countdown) edit
- `0x41` FULL on connect and in reply to a received `0x40`
- a coalesced `0x44` SLSYNC on any setlist **content** change (and on connect) — §8
- a `0x45` LOGSYNC after each logged session (and a full batch on connect) — §9
**Device should emit** (from its ondevice input handlers):
- `play`/`stop` when button A toggles transport
- `bpm=<n>` when the joystick / tap changes tempo (throttle to ≤ ~10/s)
- `sel=<sl>/<item>` on setlist navigation
- `beat=<lane>/<step>/<level>` on a touch beat edit (`app.py` ~573625)
- a `0x41` FULL after any lane add/remove/reorder or multifield lane edit
- a periodic `0x41` FULL **heartbeat** (~every 35 s) — the device is the
convergence authority (see §4)
- `0x41` FULL in reply to a received `0x40`
The `patch` in a `0x41` is produced by the device's existing program serializer
(the inverse of `parse_program()` in `app.py`). It must roundtrip through the
editor's `patchToSetup()` — i.e. the same grammar already used for
`programs.json` `prog` strings, plus a leading `t<bpm>` and optional `vol<pct>`.
---
## 4. Echo / loop suppression and conflict policy
Two rules keep the mirror from oscillating:
1. **Applying a remote change never rebroadcasts.** Wrap every apply in an
"applying remote" flag (the editor uses `_applyingRemote`) and have all of
your broadcast hooks earlyout while it is set. This is the primary guard.
2. **Drop your own origin.** On receive, if `origin == myOrigin`, ignore the
frame. (Beltandsuspenders; also lets the editor's `?loopback=1` selftest
work by relabeling echoes as a peer.)
**Convergence:** the **device is authoritative**. Its periodic `0x41` heartbeat
is treated as ground truth, so if both sides edited the same field in the same
instant, they reconcile within one heartbeat. To avoid flicker, a receiver
should **diff the incoming `patch` against its current state and skip the
rebuild if they're equal** (the editor does this in `_applyFull`), only
reconciling transport.
This is singleuserfriendly (lastwriterwins per field). True simultaneous
multieditor use is out of scope for the beta.
---
## 5. Handshake & lifecycle
```
editor "Live sync" ON ─► 0x40 HELLO ─────────────► device
◄──────────── 0x41 FULL ◄── (device's current state)
editor 0x41 FULL ──────────────────────────────► (editor's current state)
… steady state: 0x42 deltas both ways, device 0x41 heartbeat …
editor "Live sync" OFF ─► 0x43 BYE ────────────► device
```
On connect the editor sends **both** a `0x40` (asking for the device's state)
and a `0x41` (offering its own), so whichever side the user considers "source of
truth" wins immediately. A device that boots with sync idle should simply answer
`0x40` with a `0x41` and start emitting deltas once it has heard from a peer.
---
## 6. Firmware checklist (`pico-cp/app.py`)
- [ ] **Dispatch** `0x40/0x41/0x42` in the SysEx handler (~`app.py:13611415`,
alongside `0x01/0x02/0x10/0x2123`). Ignore frames whose origin is your own.
- [ ] **HELLO (`0x40`)** → reply `0x41` FULL built from current `App` state
(running, sl/idx, serialized program).
- [ ] **FULL (`0x41`)** → diff vs. current program; if different, load it
(reuse `parse_program()` / the `programs.json` load path); then reconcile
`running` (start/stop). Wrap in your remoteapply flag.
- [ ] **DELTA (`0x42`)** → apply `play/stop/bpm/vol/sel/beat/lane` to `App`
state, wrapped in the remoteapply flag so the ondevice handlers don't
rebroadcast.
- [ ] **Broadcast** a `0x42` from each ondevice input handler (button A,
joystick tempo, touch beat edit, setlist nav, lane editor), guarded by
the remoteapply flag. Structural lane changes → `0x41` FULL.
- [ ] **Heartbeat:** emit `0x41` FULL every ~35 s while a peer is connected.
- [ ] **BYE (`0x43`)** → mark the peer gone (stop heartbeating/emitting until
the next HELLO).
- [ ] **SLSYNC (`0x44`)** → merge the JSON manifest of user lists by normalized
title (replaceperlist, append unknown, never delete), reusing
`load_user_setlists()`shaped parsing; `rebuild_setlists()` + reload.
Emit one after `_persist_user()` and in reply to `0x40`. (§8)
- [ ] **LOGSYNC (`0x45`)** → merge practice entries by (`at`,`name`); append +
cap + resort. Emit a oneentry batch after `_log_play()` and a full batch
in reply to `0x40`. Record `at` (epoch ms) on each play when the RTC is
set. (§9)
- [ ] **Throttle** highrate sources (joystick tempo) and keep frames small —
the RP2040 USBMIDI RX buffer is tiny (the firmware updater already chunks
at 64 bytes), and live traffic shares the bus with MIDI clock, noteout,
and the editor's ActiveSensing heartbeat. Don't let a flood stall a
concurrent firmware push.
### Builtin vs. user set lists (must match the editor)
The PM_K1's builtin playlists (Styles / Practice / Song) are **baked into
firmware and readonly**; ondevice edits **copyonwrite** into the user
"My edits" list. The editor follows the same rule (`userSetlists()` excludes the
seeded titles). So a **remote edit that targets a builtin must follow the same
copyonwrite semantics** on the receiving side, or the two halves will disagree
about where the edit landed. When in doubt, after such an edit send a `0x41`
FULL with the resulting (copied) program so both sides converge on the same
target.
### Out of scope for the beta
- Mirroring device `settings.json` (LED brightness, MIDI config, etc.).
- Multipeer / multieditor arbitration beyond lastwriterwins.
> **No longer out of scope** (now specced in §8 / §9): live setlist **content**
> sync (`0x44`) and streaming the device practice log up to the browser and back
> (`0x45`). The old `0x10` programs push (Save/Load to device) still exists as the
> explicit, fulloverwrite path; `0x44` is the *incremental, mergebytitle* live
> mirror that runs automatically while sync is armed.
---
## 7. Perdevice emit/apply matrix
Both targets implement the **full apply path** for every verb. They differ in what
they **emit**, because ondevice editing differs:
| Device | Emits | Applies |
|-------------|----------------------------------------------------|---------------------------------------------|
| **PM_K1** Kit (touchscreen + joystick) | `play` / `stop` / `bpm` / `sel` / `beat` / `lane` (FULL on structural lane edits) | all of the above |
| **PM_X1** Explorer (6 buttons, readonly beats) | `play` / `stop` / `bpm` / `sel` only (no ondevice beat/lane editing) | all of the above |
| **PM_G1** Grid (17×7 LED matrix, 4 buttons, readonly beats) | `play` / `stop` / `bpm` / `sel` only (no ondevice beat/lane editing) | all of the above |
Editors don't need to specialcase the source — both DELTA streams look identical on
the wire, and the **device id is only exposed on the version query** (SysEx `0x02`
`0x03` reply, `<id>;<version>`; pre0.0.23 firmware sends bare version → assume
`K`).
---
## 8. Setlist content sync (`0x44` SLSYNC)
The `0x41` FULL only carries the *one loaded program* (`<patch>`) plus the
*selection* indices. `0x44` carries **setlist content** — titles + every item's
name + program string — so the two halves converge on the same library while
sync is armed, without the user pressing "Save to device".
**Frame:** `F0 7D 44 <origin>;<seq>;<json> F7`
- `<origin>` / `<seq>` — same as the other ops (echodrop + duplicate info).
- `<json>` — a **7bitsafe JSON** manifest of the sender's **user** set lists,
in the *exact same shape `0x10` already uses* so the firmware can reuse its
`programs.json` parser:
```json
{"setlists":[{"title":"My set list","programs":[{"name":"Funk","prog":"t120;..."}]}]}
```
NonASCII in titles/names is escaped `\uXXXX` (the editor's existing
`programsJSON()` 7bitsafe path); the firmware stores it verbatim. The whole
manifest rides **one SysEx frame** (same as `0x10` — user libraries are a few
KB and the RX assembler holds 60 000 bytes). It is **never chunked**; if it
ever grew past the buffer, fall back to the explicit `0x10` push.
### What's included
- **Only user set lists.** Builtin / seeded lists (firmware `BUILTIN_SETLISTS`;
editor `SEED_SETLISTS` titles) are readonly on both halves and **never
transmitted** — both sides already have identical copies baked in. (Same filter
as `userSetlists()` / `load_user_setlists()`.)
### Identity & merge rule
- **Set lists match by title** (normalized: lowercase, alphanumerics only — the
firmware's `_slkey()`), independent of index. Indices diverge freely between
halves (the device prepends builtins; the web orders differently), so a
positional match is wrong — **title is the key.**
- **Items match by name** within a list (casesensitive, as both UIs key
practice history by exact name).
- Merge is a **perlist replace**: a received user list **replaces** the local
user list of the same normalized title wholesale (its items become the
received items, in the received order). Lists present locally but absent from
the message are **left untouched** (additive — sync never deletes a list the
peer simply didn't send). A received list with no matching local title is
**appended** as a new user list.
- This is **lastwriterwins per list** (consistent with the rest of the
protocol). The receiver applies under its remoteapply guard and does **not**
rebroadcast a `0x44` in response (no echo storm); the next heartbeat / FULL
still reconciles the loaded program.
### Copyonwrite for builtins
A `0x44` never targets a builtin: it only carries user lists, and the receiver
only ever writes user lists. If a user **edits a builtin item** on either half,
that edit must first be **forked into a user list** (the firmware's `_save_edit`
already forks builtin edits into the "My edits" user list; the editor keeps a
separate user list). The fork then rides `0x44` as an ordinary user list. So
copyonwrite happens **before** the sync, and the wire only ever sees user
content — the builtins on both sides stay pristine and identical.
### When it's emitted
- **Editor:** coalesced ~150 ms after any setlist structural edit (add/rename/
reorder list, add/remove/rename item, capture/update an item), and once on
connect right after the first FULL. Reuses `syncPatchSoon()`style debouncing.
- **Device:** after `_persist_user()` succeeds (a save that wrote
`programs.json`), guarded by the remoteapply flag, and once in reply to a
`0x40` HELLO. The device's peritem *program* edits still ride `0x41` FULL;
`0x44` is specifically for **library shape** (which lists/items exist).
> The device is the **convergence authority** for the *loaded program* (§4), but
> setlist content is lastwriterwins per list — there is no periodic `0x44`
> heartbeat (it would clobber concurrent edits on the other half). Send it only
> on an actual content change or on connect.
---
## 9. Practicelog sync (`0x45` LOGSYNC)
Both halves keep a practice history (web: `localStorage` `metronome.logs`;
device: `/history.json`). `0x45` streams entries between them and **merges by a
stable key**, so a session played on the device shows up in the editor's history
graph and viceversa.
**Frame:** `F0 7D 45 <origin>;<seq>;<json> F7`
`<json>` is a 7bitsafe JSON batch of **normalized** entries:
```json
{"log":[{"at":1733059200000,"name":"Funk","dur":92,"bpm":120}]}
```
| field | type | meaning |
|--------|-------------|------------------------------------------------------------|
| `at` | int (ms) | session start, **Unix epoch milliseconds** — the dedup key |
| `name` | string | setlist item name the session was logged against |
| `dur` | int (sec) | session duration in **whole seconds** |
| `bpm` | int | tempo at the end of the session |
This is the **intersection** of the two native schemas (the web's
`{at,name,durationSec,bpm,lanes}` and the device's `{t,bpm,dur,bars,name}`):
`at``at`, `dur``round(durationSec)` / `dur`, `bpm``bpm`, `name`
`name`. Fields each side keeps privately (web `lanes`; device `t`/`bars`) are
**not** transmitted; the receiver fills them from what it has (`t` from `at` via
the RTC, `bars`/`lanes` left absent).
### Timestamps & the device clock
The dedup key is `at` (epoch ms). The editor already pushes the RTC over `0x01`
on connect / heartbeat, so the device **can** produce a real epoch. The firmware
therefore records an `at` (epoch **seconds** × 1000 → ms) on each logged play
*in addition to* its existing `t:"HH:MM"` field, computed from `time.time()`
when the RTC is set; if the RTC is unset (no editor ever connected) it falls
back to `at = 0`, which the merge treats as "no stable key" (see dedup).
### Direction & dedup/merge
- **Bidirectional.** Each half emits its **own** local entries; each applies the
peer's.
- **Dedup key = `at` + `name`.** On receive, an entry whose (`at`,`name`) already
exists locally is dropped. Entries with `at == 0` (device logged before any RTC
sync) are **always appended** (can't be deduped — better a possible duplicate
than a dropped session); these are rare and the user can delete them.
- Merge is **additive only**`0x45` never deletes history. (Deleting a stale
entry on one half does not propagate; out of scope, matches the
lastwriterwins philosophy and avoids a delete echoing into a readd.)
- The receiver **caps** its merged log (web keeps all; device keeps newest 200,
its existing cap) and resorts newestfirst by `at`.
### When it's emitted
- **Editor:** after `logFinalize()` writes a new session (oneentry batch), and
a **full batch** once on connect (after the first FULL) so the device catches
up on everything it missed. Guarded so an *applied* remote entry doesn't
rebroadcast.
- **Device:** after `_log_play()` appends a session (oneentry batch), guarded by
the remoteapply flag, and a full batch once in reply to a `0x40` HELLO.
> **Batch size caution (firmware):** a fullhistory batch (up to 200 entries) is
> small JSON but still allocates; the device sends its on connect/HELLO only, and
> the editor's onconnect batch is bounded by the SysEx buffer (60 KB ≈ a few
> thousand minimal entries). If a log ever exceeded that, the editor truncates to
> the newest entries that fit. Persession emits are a single entry — negligible.

View file

@ -0,0 +1,46 @@
# Per-track playback flow — on-device test checklist
The parsing/serialization and the `_end_plan`/`_goto_target` decision logic are covered by
`tests/run.mjs` and unit tests. What still needs a real device (PM_K-1 / PM_X-1) is the
**runtime**: that the gapless seam fires stop / advance / relative-goto at the right bar.
## Setup
1. Flash the new firmware: copy `dist/app.mpy` (PM_K) or `dist/explorer-app.mpy` (PM_X) onto the
device as `/app.mpy` (or use the editor's one-click push — it serves this build).
2. Author the test tracks in the web editor: paste each program string into the program-string
field, **Save** it as a set-list item, then **Save to device**. (Multi-item cases: create the
items in order in one set-list.)
3. On the device, select the set-list and press play (Button A).
`b<n>` sets the cycle length in bars; a cycle = `b<bars>`, else one master bar. The action fires
after `rep × cycle` bars (rep defaults to 1).
## Cases
| # | Track(s) | Expect | ✓ |
|---|----------|--------|---|
| 1 | `t120;kick:4` | **Loops forever.** Joystick-right advances manually. | ☐ |
| 2 | `t120;b2;kick:4;end=stop` | Plays **2 bars, then stops** by itself (LED → green, transport stops). | ☐ |
| 3 | `t120;b2;kick:4;rep=3;end=stop` | Plays **6 bars (3×2), then stops.** | ☐ |
| 4 | A=`t120;b2;kick:4;end=next` B=`t100;b2;snare:4` | A plays 2 bars then **gaplessly advances to B** (no gap, no count-in, tempo jumps to 100). | ☐ |
| 5 | A=`t120;b2;kick:4;rep=2;end=next` B=`t100;b2;snare:4` | A plays **4 bars** (2×2) then advances to B. | ☐ |
| 6 | 1=`t120;b2;kick:4` 2=`t120;b2;snare:4;end=next` 3=`t120;b2;clap:4;end=-2` | 3 jumps **back 2 → track 1** after its cycle (a looping verse→chorus section). | ☐ |
| 7 | 1=`t120;b2;kick:4;end=-2` (first item) | `end=-2` before the start **clamps to track 1** → it just loops itself. | ☐ |
| 8 | last item `…;end=next` | Advancing past the last item **wraps to the first** (set-list loops). | ☐ |
| 9 | any of the above, mid-flow | **Manual joystick-right / footswitch always advances immediately,** regardless of `end=`. | ☐ |
| 10 | global **Continue ON** + an item `t120;b2;kick:4;end=stop` | The item **still stops** — explicit `end=` overrides the global Continue default. | ☐ |
| 11 | global **Continue ON** + `t120;b2;kick:4` (no `end=`) | **Advances** after 2 bars — legacy behavior preserved (Continue = default `end=next`). | ☐ |
## What to watch for
- **Seam quality** (cases 46): the swap should be click-on-the-beat with no audible gap, dropout,
or double-trigger at the boundary. This is the highest-risk part of the change.
- **Stop cleanliness** (cases 23): no extra click after the stop; speaker goes quiet; the play
session is logged.
- **Ramp/trainer interaction**: a track with `rmp…`/`tr…` *and* an `end=` should ramp/gap normally
and still fire the end-action at `rep × cycle`.
- **Memory**: relative-goto and advance both go through `_prepare_next` (which `gc.collect()`s
before parsing). Watch for any MemoryError fallback (it leaves the track looping instead).
Report any case that misbehaves with its number and what happened.

283
docs/rust-port.md Normal file
View file

@ -0,0 +1,283 @@
# Rust port — staged plan
This is the plan for the native-Rust direction first discussed alongside the A/B-bootloader
idea. **What changed since then:** the track format is now formally specced
(`docs/track-format.md`) with a golden-vector conformance suite (`tests/run.mjs`). That suite is
the thing that makes a port *safe* — any Rust engine has a precise, executable definition of
"correct" to validate against, the same one `engine.js` and `app.py` already pass.
## The core idea
Port from the **inside out**, lowest-risk first. The pure logic (track codec, then the
scheduler) is host-testable with zero hardware and is gated by the existing golden vectors. Only
once that's proven do we touch drivers, A/B, and the actual firmware. We do **not** rip out
CircuitPython until the Rust engine passes the vectors *and* the drivers are proven on hardware.
## Architecture: one firmware core, modular drivers per form factor
Trying many form factors (Kit, Explorer, **Grid**/Scroll Pack, …) is how we *discover the line
between core and driver*. In Rust that line is enforced by the type system instead of copied by
hand — today each CircuitPython form factor is its own ~1,500-line `app.py` clone; the Rust build
is one core crate plus a thin per-board binary.
**`pm-core` — the core (`no_std`, zero hardware):**
- the track-format codec (`rust/track-format`, Stage 1) and the scheduler/clock (Stage 2, already
`no_std` and building for RP2350),
- playback-flow (rep/end/continue, segment seams), app state, set-list model,
- the **USB-MIDI / live-sync / firmware-update protocol** logic (the SysEx opcode handling, which
is form-factor-independent).
It is host-testable and gated by the golden vectors — the same suite `engine.js` and `app.py`
pass. **This is "core."**
**Driver traits — what the core is generic over (the swappable part):** define small project
traits — `Display` (or render straight to an `embedded-graphics` `DrawTarget`), `Inputs` (yields
button / touch events), `Clicker` (audio out), `Indicator` (RGB) — and write each concrete driver
against **`embedded-hal`** bus traits (`I2c`, `SpiBus`, `OutputPin`, `DelayNs`). The core's UI code
then doesn't care whether the target is a 17×7 mono matrix or a 320×480 colour TFT.
**Per-board binary crates — `pm-kit`, `pm-explorer`, `pm-grid`:** a thin `main.rs` BSP that
instantiates the right concrete drivers and hands them to the generic core:
- **Grid** (Scroll Pack): IS31FL3731 over I²C (a `DrawTarget` for a 17×7 mono frame) + 4 GPIO buttons.
- **Kit:** ST7796 320×480 over **SPI** — driven by a **custom `St7796` struct** (direct port of
`pico/main.py`), **not** mipidsi, which fought the panel's geometry/CS (see [[rust-st7796-cs-gotcha]]);
UI still renders through an `embedded-graphics` framebuffer. GT911 touch over I²C; WS2812 via
`ws2812-pio`; I²S to the PCM5102A via PIO.
- **Explorer:** ST7789V 320×240 over an **8-bit parallel (8080) bus** via `mipidsi`'s
`ParallelInterface` — a different driver story from the Kit's SPI (see the matrix below).
### Display driver matrix (researched 2026-06-03)
Displays are not the gate — every controller has a real Rust path; the buses differ:
| Form factor | Controller | Bus | Rust driver | Status |
|---|---|---|---|---|
| Kit (`pm-kit`) | ST7796 320×480 | SPI | **custom `St7796`** (port of `pico/main.py`) + `embedded-graphics` framebuffer; **mipidsi dropped** | ✅ on hardware — **but tearing** (see below) |
| Explorer (`pm-explorer`) | ST7789V 320×240 | 8-bit parallel 8080 | `mipidsi` `ParallelInterface` *(start here; be ready to port directly if geometry fights, as on the Kit)* | path proven upstream; not yet built |
| Grid (`pm-grid`) | IS31FL3731 17×7 mono | I²C | **vendored bulk-framebuffer driver** (port of `pico-scroll/app.py`'s `Matrix`); `is31fl3731` crate *not* used | ✅ **built + compiles** (LED-first milestone) — see below |
| Kit touch | GT911 | I²C | `gt911` or `gt9x` crate | ✅ mature (blocking + async, 5-point) |
**ST7796 (Kit) — only *partially* working: tearing.** Pixels are correct and the panel boots, but the
image tears badly. The cause is structural, not a bug: `mipidsi` has **no TE-pin / vsync / partial-update
/ double-buffer support** (confirmed against the upstream repo `github.com/almindor/mipidsi` — it offers
only optional draw *batching*), and this ST7796 module doesn't break out the TE (tearing-effect) line to
sync writes against scan-out. So writes race the panel's refresh. Mitigations available to us, none from
the crate: (a) redraw only changed full-width row-bands to shrink the tear window — already done; (b) DMA
each band as one tight burst; (c) sync to a TE GPIO *only if* a module that exposes that pin is sourced.
Treat tearing as an **open hardware/firmware item**, not "display done." See [[rust-st7796-cs-gotcha]].
**Explorer parallel bus — correctness and performance are decoupled.** `mipidsi`'s `ParallelInterface`
drives the data pins through an `OutputBus` *trait*. The shipped `Generic8BitBus` is plain GPIO bit-bang
(`embedded-hal` `OutputPin`s) — works immediately, just CPU-bound. For speed, implement `OutputBus` over
**RP2350 PIO** (the PIO supports 8080/6800 bus timing; the C/TFT_eSPI world hits ~4 ms for a full 320×480
clear this way) — a drop-in swap that leaves mipidsi + `embedded-graphics` + `pm-ui` untouched. Worst case
is "slow but functional," never "impossible," so the bit-bang fallback de-risks the whole Explorer bring-up.
**The honest caveat (what the Grid prototype is teaching us):** a 17×7 mono grid and a 320×480
touch TFT are too different for *one* pixel-identical UI. So the clean split is **core engine +
protocol + state = fully shared; the *view* = per-display-class.** The Grid is the most extreme,
minimal display in the lineup, which makes it the best forcing-function for finding exactly where
that boundary falls before we commit drivers to Rust. The CircuitPython `pico-scroll/` build exists
to nail that UI down on real hardware first.
## Stages
### Stage 0 — toolchain in a container
Add a Rust toolchain image (mirroring `hardware/eda/`): a `Containerfile` with `rustup`, the
`thumbv8m.main-none-eabihf` target (RP2350 is Cortex-M33), `flip-link`, `probe-rs`, `elf2uf2`.
Driven by a `run.sh` like the EDA one. **Never on the host.**
### Stage 1 — `track-format` crate ✅ DONE (`rust/track-format/`)
Implemented and **passing**: `./rust/run.sh` builds the container and runs `cargo test`, which
validates the crate against `tests/fixtures/track-format.json` (conformance + idempotency). The
Rust codec agrees with `engine.js` and `app.py` on every vector — and carries `vol`/`cd`, so it's
the most spec-complete of the three. Original scope below.
#### (original) Stage 1 — `track-format` crate ← the concrete first PR
A pure, `no_std`-compatible crate: `parse(&str) -> Track` and `serialize(&Track) -> String`,
plus a `normalize()` that emits the neutral structure from `docs/track-format.md` §5. Then a
`cargo test` that reads `tests/fixtures/track-format.json` and asserts each case's `norm` and
round-trip — i.e. a **third adapter alongside `js_adapter.mjs` / `py_adapter.py`**. When this is
green, the Rust engine provably agrees with web + device on every groove, euclid, swing, ghost,
polymeter, and the playback-flow tokens. No hardware, fully testable in the container.
This is the highest-value slice: small, gated by work already done, and it proves the toolchain.
### Stage 2 — scheduler/engine ✅ DONE (`rust/track-format/src/schedule.rs`)
Ported the look-ahead step scheduler (the `durs` math from `app.py` `tick`/`_prepare_next`).
`render(track, bars)` produces the deterministic click timeline; `tests/schedule.rs` asserts the
timings — quarter-note spacing, subdivisions, swing 2/3:1/3, polymeter 5:4, accents/ghosts, mute,
multi-bar looping. All green on the host, no hardware. The real-time firmware loop will just play
this timeline against the wall clock.
**Also done:** the crate is now `#![no_std]` + `alloc` and **builds for the RP2350 target**
(`cargo build --lib --target thumbv8m.main-none-eabihf`) — the codec + scheduler are firmware-ready.
### Stage 3 — drivers (hardware) 🔧 IN PROGRESS (`rust/pm-kit/`)
**✅ Milestone 1 (boot) — confirmed on Pico 2:** GP25 blink. Toolchain + RP2350 boot block + flash work.
**🟡 Milestone 2 (display) — draws correctly on Pico 2, but TEARS:** ST7796 320×480 over SPI0 via
`rp235x-hal`, drawing the shared `pm-ui` through an `embedded-graphics` framebuffer. Driver is a
**custom `St7796` struct** ported from `pico/main.py` — mipidsi was tried first and **dropped**: its
split-transaction CS and orientation/offset math mangled the geometry; the port uses correct
per-command CS framing (CS low → cmd → params → CS high) and full-width row bands (see
[[rust-st7796-cs-gotcha]]). **Not done:** the image tears badly — no TE/vsync sync is possible because
this module exposes no TE pin, so writes race scan-out (no crate fixes this; see the tearing note in
the display driver matrix above). Open item before the display can be called finished. Diagnosed off-bench with host tools in `rust/uisim`: `uisim` renders
pm-ui to PNG; `--bin panelsim` decodes mipidsi's real command/pixel stream into a PNG (proved the
protocol correct → bug was physical); `--bin initdump` dumps the init + CASET/RASET sequence.
**🟡 Milestone 3 (live metronome) — built, pending on-device check:** the firmware is now an actual
metronome. `embedded-alloc` heap → parses tracks with `track-format` on-device; 4 built-in grooves;
Timer-driven clock; **audio clicks** on the master lane's hits (GP13 PWM, short edge-triggered
pulses, accent louder); **controls** — A = play/stop, B = grid/notation view, joystick (rotated 90°
CCW) up/down = tempo, left/right = groove. Renders `pm-ui::draw_metronome` / `draw_notation`, with a
cheap `draw_progress` strip animating the bar position every frame (full redraw only on change → no
flicker). All loop input reads use `unwrap_or` (no panics) — addresses the self-test crash.
Compile + simulator verified; **needs a flash to confirm** audio timing, joystick directions, no crash.
**pm-ui views (sim-verified, PNGs):** metronome grid (accents/ghosts/polymeter), and **drum notation**
(5-line staff, time sig, hands stem-up / feet stem-down, shared stems, beamed eighths, ledger lines).
**Still to do:** GT911 touch (GP8/9), WS2812 RGB (GP12), USB-MIDI, set-lists from programs.json,
per-cell live playhead, the rest of the practice features. Then split `pm-core` out as its own crate
and add `pm-explorer`/`pm-grid` binaries. HAL stays `rp235x-hal` (embassy later if async earns it).
On `embassy` / `rp-hal`:
- ST7789 240×320 display → `mipidsi` + `embedded-graphics` (mature; the parts are well-supported).
- I²S to the PCM5102A → RP2350 PIO.
- WS2812 → `ws2812-pio`. USB-MIDI → `usbd-midi` / `embassy-usb`.
- GT911 touch (Kit) over I²C.
#### `pm-grid` — Scroll Pack firmware 🟢 BUILT (LED + USB-MIDI audio), pending on-device check
The Rust sibling of `pico-scroll/app.py` (`rust/pm-grid/`), and **the firmware the PM_G-1 ships now**
the CircuitPython build is dropped from the product (info-grid.html / build.sh / deploy.sh no longer
bundle or serve it; source stays as the reference port). Target is a **plain RP2040** (Cortex-M0+,
`thumbv6m-none-eabi`) — NOT the Pico 2 — so it has its own HAL (`rp2040-hal` 0.10 + `rp2040-boot2`),
`.cargo/config.toml`, `memory.x` (BOOT2 + flash + 264 KB RAM) and `build.sh`+`uf2.py` (RP2040 family
id `0xe48bff56`). `thumbv6m-none-eabi` added to `rust/Containerfile`. Compiles clean → **`pm-grid.uf2`**
(BOOTSEL drag-flash) + **`pm-grid.elf`** (probe-rs + defmt). Both served by deploy.sh; the info page
links the `.uf2`. Kept out of the host workspace like `pm-kit`. Debug build uses `defmt`/`defmt-rtt` +
`panic-probe` + `flip-link`, runner `probe-rs run --chip RP2040` (the user's Pi Debug Probe).
What's implemented (faithful port of `pico-scroll`): the **IS31FL3731 driver** (vendored bulk
144-byte framebuffer, one I²C block write per frame — the right architecture, mirrors the
CircuitPython `Matrix`; per-pixel I²C is too slow to animate), the **polymeter scheduler** driven by
`track-format::schedule::lane_durs` (the cross-impl contract) with per-lane step clocks + ramp +
gap-trainer, **4-button input** (A tap=play/stop, hold=cycle view; B tap=next track, hold=next set
list; X/Y=tempo ∓ with auto-repeat), the **built-in set lists**, and three LED views:
- **Ticker** (default): a **beat strip** on the top row (cols 010) — faint ticks at each beat + a
bright playhead at the master lane's current step; the track name infinite-scrolls below it (cols
010, rows 26); BPM is pinned right, **rotated 90° CCW** — a vertical hundreds **dot-bar** in col
11 (one dot per 100) + the last two digits rotated into cols 1216 (tens bottom, units top). So
`130` → 1 dot + rotated "30". This is the user-designed landscape readout. Layout/rotation verified
off-bench with an ASCII replica of `draw_ticker`. Whole matrix strobes white on the downbeat.
- **Grid** (lanes×steps + playhead) and **Pendulum** (bouncing arm + beat ticks) — ports of
`_render_grid` / `_render_pendulum`.
Boot splash scrolls "PM-G1 GRID" (liveness + pixel-map check).
**Audio — USB-MIDI ✅ DONE** (the Scroll Pack has NO speaker, so this is the real audio path):
`usb-device` 0.3 + `usbd-midi` 0.5 (the `rp2040-hal` `UsbBus`). Enumerates as a class-compliant MIDI
device ("PM_G-1 Grid"); `tick` emits a **GM note-on per lane hit on channel 10** (note from the ported
`SOUND_GM` map, velocity by level 120/90/45) via `UsbMidiClass::send_bytes([0x09,0x99,note,vel])`
raw 4-byte packets, sidestepping the named-`Note` enum so arbitrary GM drum notes work. USB is polled
every loop iteration **and during the boot splash** (1.5 ms cadence) so the host can enumerate. Play
through the editor's **Device audio**.
**Live-sync — ✅ DONE** (`docs/livesync-protocol.md`, ported from `pico-scroll`): reads the USB-MIDI
RX endpoint, reassembles SysEx from the 4-byte event packets (by Code Index Number), and dispatches
manufacturer `0x7D` frames. **Version query** `0x02`→`0x03 "G;0.1.0"` (so the editor identifies it).
**HELLO** `0x40`→reply FULL; **FULL** `0x41`→parse the patch (`track-format::parse`) + running and
adopt it; **DELTA** `0x42`→apply `play`/`stop`/`bpm`/`sel`/`beat`; **BYE** `0x43`→disarm. **Broadcasts**
a DELTA from each on-device input (A/B/X/Y → play/stop, sel, bpm) and a **FULL heartbeat every ~5 s**
(`track-format::serialize`). Echo-guarded by a boot-derived origin; an `sync_applying` flag stops
re-broadcast while applying. All TX (notes + SysEx) shares the one-per-poll `tx_q` drain. `info!` logs
every received op. Structural `lane=` edits aren't applied incrementally (they arrive as a fresh FULL).
**Playback-flow auto-advance — ✅ DONE** (`rep`/`end`): at each master-bar boundary, after `bars*rep`
cycles the end-action fires — `end=stop` stops, `end=next`/`end=+N` advances. The next track is
**preloaded one bar early** (parsed + durs) into `pending`, then swapped at the exact seam
(`seam_ns` = the master lane's bar boundary; all lanes restart there) for a gapless handoff. A
`continue_on` flag (default off, no UI yet) would make a `bars` track with no `end=` auto-`next`.
**MIDI clock out — ✅ DONE** (default on): 24-PPQN `0xF8` against the wall clock + `0xFA`/`0xFC`
Start/Stop on play/stop (button or live-sync), so a DAW can slave its tempo to the Grid. Queued on
`tx_q` like everything else (CIN `0xF` single-byte packets).
**MIDI clock in — ✅ DONE** (default on): `feed_midi` handles `0xF8` (EMA of the inter-tick interval →
derived BPM, 5300 clamp + jitter reject), `0xFA`/`0xFB` start, `0xFC` stop. While slaved, the ramp
and our own clock-out are suppressed (no feedback); the lock drops after a >1 s gap. Only engages
when a host actually sends clock (the editor doesn't), so it's inert in normal editor use.
**USB Mass Storage — ✅ DONE (drive enumerates; pending on-device test)**: composite **MIDI + MSC**
(`usbd-storage` 2.0, SCSI over Bulk-Only), adapted from the crate's RP2040 example. The host sees a
**1 MB removable drive** backed by the upper flash (a `.filesystem` region, `NOLOAD` so it's not in
the UF2 and survives reflashes). `scsi_command` serves the SCSI set (Inquiry/ReadCapacity/Read/Write/
ModeSense/RequestSense); reads come from flash via raw pointer, writes erase+program a 4 KB sector
with `rp2040-flash` (wrapped in `interrupt::free`). The host owns the FAT format (formats on first
use). **Required `rp2040-hal` 0.11** (0.10 + `rp2040-flash` 0.6 = duplicate `__aeabi_*`/`__addsf3`
ROM intrinsics) and **`lto = false`** (fat-LTO tripped the same intrinsic). This **unblocks
persistence** — practice log / `settings.json` / user set-lists can now live on the drive (next: the
device parses the FAT to read/write them).
**Drive named "PM_G-1" + reads set lists — ✅ DONE**: on boot (before USB setup, so the flash write
can't disrupt enumeration) the device mounts the FAT (`fatfs` 0.4 git — 0.3.6 needs `core_io` for
no_std; reads via a `FlashIo` over the `.filesystem` region, validated off-bench against a real
`mkfs.fat` image). If the root-dir volume label isn't "PM_G-1" (e.g. a leftover CircuitPython
volume), it writes an embedded blank **PM_G-1 FAT12 template** (`src/fat_template.bin`, the first 7
sectors of `mkfs.fat -F12 -S4096 -n PM_G-1`, sets *both* BPB + root-dir VOLUME_ID label) → the drive
shows as **PM_G-1**. Then it reads `programs.json` (LFN) and a tolerant scanner turns it into **user
set lists appended to the built-ins** — drop your `programs.json` on the drive, reboot, your grooves
appear (B-hold cycles set lists). Set lists are now a runtime `Vec<SetList>` (built-ins → owned +
drive).
**Live re-read — ✅ DONE**: the SCSI Write handler sets a `dirty` flag; when the drive has been idle
~1.5 s (host finished) **and** playback is stopped, the loop re-reads `programs.json` and rebuilds the
set lists (`reload_user`) — drop a file, it appears **without a reboot**. Read-only → no FAT
corruption. (NB the boot black-screen regression was the 24 KB heap being too small for `fatfs` +
owned set lists → alloc panic; heap is now 96 KB and the drive read runs *after* the splash.)
**Dual-access constraint (why the device doesn't *write* the drive):** while the host has the FAT
mounted, the device writing it corrupts the host's cached view (same reason CircuitPython makes the
drive one-direction-at-a-time). So device→drive writes (practice log, `settings.json`) are **not**
done; the practice log should instead go to the editor via **LOGSYNC** (`0x45`, its designed
channel), or behind a CircuitPython-style boot-mode toggle. `settings.json` *read* (config from a
file: brightness, MIDI channel, clock on/off) is safe (device read-only) and is a clean follow-up.
**Still deferred**: practice log via **LOGSYNC** + **SLSYNC** (`0x44`/`0x45`), `settings.json` read,
show the set-list title, the **on-device 808/909 synth → USB Audio input** (the standalone-audio
alternative, big), firmware push (intended: UF2 now), optional piezo. A/B bootloader **dropped**.
**Hardening pass — partly done**: panic-audit fixed a real brick risk — `sx_send` (live-sync
broadcasts + 5 s heartbeat) had no `tx_q` cap, so an editor that drops off without a BYE while
`sync_armed` (and nothing draining MIDI-IN) would grow the heap until OOM → panic → black; now capped
at 256 (notes/clock were already capped). Added a defensive `retain(non-empty)` in `build_setlists`
(no `% 0` in `load`/`next`). Other `unwrap`s are boot-time init; `lanes[0]`/`items[0]`/`step[0]` are
safe (`parse` substitutes `beep:4`; built-ins lead). Started the `main.rs` split (extracted
`fonts.rs`); further modularization (FAT/MSC `storage`, `views`) to continue incrementally. **Still
needs the bench:** composite-USB stress (drive writes *while* live-syncing + clocking) and the
flash-write-vs-metronome-timing interaction — only verifiable on hardware.
### Stage 4 — native A/B + secure boot
Replace the `.mpy`-level A/B hack (`code.py` loads `app.mpy`, rolls back to `app.bak`) with the
**RP2350 bootrom's native** partition-table A/B + signed boot, configured via `picotool` (the
chip already provides this — see the earlier hardware discussion). The Rust app is the image in
the slot; rollback and version selection move into silicon.
## What you keep / lose
- **Gain:** memory safety, native A/B + secure boot, performance headroom, one typed model instead
of three hand-written parsers.
- **Lose:** the live one-click `.mpy` push (Rust is compile→flash→reboot). The editor's *data*
live-sync (tempo/pattern/setlist mirroring) still works — it's a data protocol. Only live
*logic* edits go away, and an embedded `wasm3`/script module could buy those back if wanted.
## Acceptance gate
Every codec/engine change must pass `tests/fixtures/track-format.json`. The Rust crate joins
`js`/`py` as a runner adapter, so "same groove on web, device, and the Rust build" is enforced,
not hoped for.
## Recommendation
Do **Stage 1 in a container next** — it's small, testable today (given a toolchain), reuses the
suite, and produces a real artifact to judge the Rust path on before committing to drivers or a
firmware rewrite. Defer Stages 34 until Stage 12 are green and you've decided the live-push
tradeoff is acceptable.

235
docs/track-format.md Normal file
View file

@ -0,0 +1,235 @@
# PM track format — specification
This is the **single source of truth** for the metronome's track ("program" / "patch")
format. The grammar is currently implemented by hand in three places that have quietly
drifted:
- **Web**`src/engine.js` (`patchToSetup` / `laneStrToCfg` / `setupToPatch` / `laneCfgToStr`)
- **Firmware**`pico-cp/app.py` (`parse_program` / `_parse_lane` / `lane_to_str` / `_prog_str`)
- (`editor.html` inlines `engine.js` at build time)
`tests/fixtures/track-format.json` holds golden vectors that pin every feature to a single
expected meaning. `tests/run.mjs` runs both the JS and Python implementations against them
and reports divergences. **Any new implementation (e.g. a Rust engine) must pass the same
vectors** — that is what keeps "the same groove on the device and in the browser" true.
Status legend used below and in the fixtures:
- **stable** — implemented and (intended to be) identical on web + firmware.
- **divergence** — a real cross-implementation disagreement that exists *today*; the vector
encodes the spec's intended behavior and the runner flags the side that is wrong.
- **new** — defined here but not yet implemented anywhere (the playback-flow model below);
the vector is the acceptance test for building it.
---
## 1. Container: `programs.json`
The device reads set-lists from `/programs.json` (pushed by the editor over USB-MIDI SysEx,
or dragged onto the CIRCUITPY drive). Built-in set-lists are baked into firmware and are *not*
in this file.
```jsonc
{
"format": 2, // NEW. absent ⇒ treat as format 1 (legacy)
"setlists": [
{
"title": "My set",
"description": "optional",
"onEnd": "stop", // NEW: stop | nextList | loop (default: stop)
"defaultEnd": "next", // NEW: items without their own end= inherit this
"programs": [
{ "name": "Intro", "prog": "t88;b8;kick:4=X.x.;end=next" },
{ "name": "Groove", "prog": "t88;kick:4;snare:4=.X.X" }
]
}
]
}
```
**Legacy (format 1):** `{ "setlists":[...] }` with no `format`, or the older flat
`{ "programs":[...] }` (a single list). Loaders MUST still accept both. Migration to format 2:
set `format: 2`; a previously "continuous" set-list gets `onEnd` per the author's intent and
`defaultEnd: "next"` (or `end=next` on each item).
---
## 2. Patch string grammar
A patch is `;`-separated tokens. **Rule that prevents token collisions:** a token containing
`:` is a **lane**; every other token is a **keyword directive** matched against the reserved
set `{ v1, t, vol, cd, b, tr, rmp, rep, end }`. Unknown tokens are ignored (see §6).
```ebnf
patch = [ "v1" ";" ] directive *( ";" directive ) ;
directive = tempo | volume | countin | bars | trainer | ramp | rep | end | lane ;
tempo = "t" int ; (* beats per minute, clamped to 5..300 *)
volume = "vol" int ; (* 0..100; web-authoring only — device has a hardware volume knob *)
countin = "cd" int ; (* count-in seconds; web-authoring only — device has no count-in *)
bars = "b" int ; (* cycle length in bars; drives Continue/rep *)
trainer = "tr" int "/" int ; (* playBars "/" muteBars (gap trainer) *)
ramp = "rmp" int "/" signed "/" int ; (* startBpm "/" amount "/" everyBars *)
rep = "rep=" int ; (* cycles before end fires; default 1 *)
end = "end" "=" ( "stop" | "next" | signed ) ; (* NEW: see §3 *)
lane = sound ":" groups [ "/" sub [ "s" ] ] [ euclid ] [ "=" pattern ]
[ "@" signed ] [ "~" ] [ "!" ] ;
sound = name | int ; (* int = GM percussion note number alias *)
groups = int *( "+" int ) ; (* "4" or "2+2+3" → beats per bar *)
sub = int ; (* subdivision; trailing "s" = swing *)
euclid = "(" int [ "," int [ "," int ] ] ")" ; (* k [, n [, rot ]] — even distribution *)
pattern = *( cell ) ; (* one char per step: dynamics + ornament *)
cell = "X" | "x" | "1" | "g" (* dynamics: accent / normal / normal / ghost *)
| "f" | "F" | "d" | "D" | "z" | "Z" (* ornament hits (see below): flam / drag / roll *)
| "." | "-" | "_" ; (* rest *)
```
### Lane semantics
- **groups**`2+2+3` means a 7-beat bar grouped 2+2+3; the first step of each group is
accented by default. `beatsPerBar = sum(groups)`.
- **sub** — steps per beat (`/2` eighths, `/3` triplets, `/4` sixteenths). `steps = beats*sub`.
- **swing**`/2s` lays the off-beat on the last triplet (≈ 2/3).
- **pattern** — one char per step: `X`=accent (level 2), `x`/`1`=normal (1), `g`=ghost (3),
`.`/`-`/`_`/anything else = rest (0). Short patterns are right-padded with rests to `steps`.
- **ornaments** — three extra hit letters add a per-step *ornament* on top of the dynamic, in a
channel parallel to the dynamic levels (see `orns` in §5): `f`/`F`=flam (one grace note),
`d`/`D`=drag/ruff (two grace notes), `z`/`Z`=roll/buzz. The **case carries the dynamic** so the
two stay orthogonal: **lower-case = normal hit (level 1), UPPER-case = accented hit (level 2)**.
So `snare:4=F.fz` is an accented-flam, rest, normal-flam, normal-roll. Ghosted ornaments aren't
expressible (a `g`-style ghost ornament has no letter); ornament + rest is just a rest.
**With no pattern (the default):** every step sounds at normal level and accents fall **only
on group starts** — the grouping *is* the accent map. So `4` accents beat 1; `2+2` accents
beats 1 & 3; `4/2` is a steady 8th lane with an accent on beat 1. To accent every beat,
write the grouping (`1+1+1+1`) rather than relying on the default.
- **euclid** `(k,n,rot)** — `k` hits spread as evenly as possible over `n` steps (rotated by
`rot`), first hit accented. Replaces an explicit `=pattern`.
- **`@<db>`** — per-lane gain in dB. (Host-optional: applied in the browser; see §6.)
- **`~`** — polymeter: this lane keeps its own bar length independent of the master lane.
- **`!`** — lane present but silenced/disabled.
### Top-level directives
- **`t`** tempo, clamped to `[5, 300]`.
- **`b<n>`** the segment's **cycle length** in bars — used for Continue/`rep` accounting and
song auto-advance. Absent ⇒ the cycle is one bar of the **master lane** (the first lane).
- **`rmp<start>/<amt>/<every>`** tempo ramp: every `<every>` bars, change tempo by `<amt>`
(may be negative), starting from `<start>`.
- **`tr<play>/<mute>`** gap trainer: play `<play>` bars, silence `<mute>` bars, repeat.
- **`vol`, `cd`** web-authoring only — the device has a hardware volume knob and no count-in, so
it parses past these and does not carry them (see §6).
---
## 3. Playback flow
Per-track playback behavior (parsed + serialized by both engines; firmware runtime implemented).
**Implementation note:** the device keeps the global `Continue` toggle as a *default* — a track
with an explicit `end=` governs itself; a track without one falls back to `end=next` while
Continue is on (and still needs `b<bars>`), else it loops. So per-track `end=` overrides the
global toggle rather than replacing the UI.
**Default (no `end=` token) — loop forever**, exactly like a metronome. Manual advance
(joystick / footswitch) always moves to the next track. "Vamp until cue" is therefore the
default and needs no special token.
`end=` exists only to make flow **automatic** after a finite number of cycles:
| Token | Meaning |
|------------------|---------------------------------------------------------------------|
| *(absent)* | Loop forever. Manual advance → next track. |
| `end=stop` | After `rep` cycles, stop. |
| `end=next` | After `rep` cycles, auto-advance one track. (Sugar for `end=+1`.) |
| `end=<±N>` | **Relative goto**: after `rep` cycles, jump `N` tracks (e.g. `end=-2` repeats a section, D.S.). |
| `rep=<N>` | Cycles to play before `end` fires. Default `1`. Only meaningful with `end`. |
A **cycle** = `b<bars>` bars if `b` is present, else one bar of the master lane.
**Normalization** (what the golden vectors compare): `end` normalizes to `"stop"`, or an
integer offset (`next` ⇒ `1`); absent ⇒ `null`. `rep` ⇒ the integer (defaults to `1` when
`end` is present), else `null`.
### Resolution & boundaries
- **Manual override always wins.** `end=` is only the hands-off behavior.
- **Seam is gapless.** An automatic `next`/goto does **not** re-trigger count-in and inserts
no gap (honors the existing `_seam_t` seam). Count-in (`cd`) fires only on manual/initial
start. Tempo jumps to the destination track's `t` at the seam.
- **Goto past the last item** (or `end=next` on the last item) triggers the set-list's
`onEnd` policy: `stop` | `nextList` | `loop`.
- **Goto before the first item** clamps to the first item.
- **`defaultEnd`** on a set-list is inherited by any item whose patch has no `end=`.
---
## 4. Canonical form & round-tripping
Serializing a parsed patch MUST be **idempotent**: `serialize(parse(serialize(parse(x))))`
equals `serialize(parse(x))`. Host-optional fields that a host does not act on
(`vol`, `cd`, `@db`) MUST still survive the round-trip rather than being dropped — silent
data loss is the failure mode this spec exists to prevent.
The runner checks **semantic equality** (the normalized structure — what actually plays)
across implementations, plus per-implementation idempotency. It does **not** require the two
serializers to emit byte-identical strings (they legitimately differ on the optional `v1`
prefix and on whether a default pattern is written out).
---
## 5. Normalized structure (the comparison target)
Each implementation's adapter parses a patch and emits this neutral shape; vectors store the
expected value in `norm`:
```jsonc
{
"bpm": 120, "bars": 0, "volume": null, "countMs": 0,
"ramp": null, // or { "start": 80, "amt": 4, "every": 4 }
"trainer": null, // or { "play": 2, "mute": 2 }
"rep": null, // NEW
"end": null, // NEW: "stop" | <int> | null
"lanes": [
{ "sound": "kick", "groups": [4], "sub": 1, "swing": false,
"poly": false, "mute": false, "gainDb": 0, "levels": [2,1,1,1] }
]
}
```
`levels` is the resolved per-step dynamics array (0 rest / 1 normal / 2 accent / 3 ghost) —
the real audible payload, and the most important thing two implementations must agree on.
`orns` is the resolved per-step **ornament** array, parallel to `levels`
(`0` none / `1` flam / `2` drag / `3` roll). It **defaults to all-zeros**, so a lane with no
ornaments omits it entirely — an implementation MAY always emit it or omit-when-all-zero, and the
conformance runner treats a missing `orns` as all-zeros. Example with ornaments
(`snare:4=F.fz`): `"levels": [2,0,1,1], "orns": [1,0,1,3]`.
---
## 6. Divergences — status
Surfaced by the runner. **Resolved** (now identical on web + firmware, verified by the suite):
- **Default (no-pattern) groove** — every subdivision sounds; accent only on group starts.
- **Euclid `(k,n,rot)`** — now parsed by both engines (`kick:4(3,8)` etc.).
- **Unknown sound name** — falls back to `beep` on both.
- **GM note-number aliases**`36:4` resolves to the voice name on both.
**Intentional / permanent host differences** (not bugs — the device is a host that lacks these):
- **`vol` (master volume) / `cd` (count-in)** — web-authoring fields. The device has a hardware
volume knob and no count-in, so it parses past them and does not carry them. (Contrast `@db`
gain, which the device *does* round-trip as a per-lane field even though it doesn't apply it.)
**Resolved on the web side** (`engine.js` now matches the firmware):
- **Tempo clamp**`patchToSetup` clamps `t` to `[5,300]`.
- **Empty patch**`patchToSetup` defaults to a `beep:4` lane when no lanes are given. (The
editor still shows its "no lanes" hint by checking the *raw* input for a `:` token.)
The capability/version handshake (the device already replies with its firmware version over
SysEx) should gate features so the editor can warn when a track uses something the connected
firmware is too old to play, instead of letting it degrade silently.

1737
editor-beta.html Normal file

File diff suppressed because it is too large Load diff

1754
editor.html Normal file

File diff suppressed because it is too large Load diff

138
embed.html Normal file
View file

@ -0,0 +1,138 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Embed a PolyMeter widget — VARASYS</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<!-- Docs for the embeddable PolyMeter widget. Dogfoods embed.js with a live example. -->
<script>
(function(){ try{
var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
--panel-bg:#161b22; --panel-bd:#2a313c; --field-bg:#0e1116; }
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
--panel-bg:#ffffff; --panel-bd:#d2dae4; --field-bg:#f1f4f8; }
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt);
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); }
a{ color:var(--link); }
main{ width:100%; max-width:760px; margin:26px auto 0; }
h1{ font-size:24px; margin:0 0 4px; } h2{ font-size:16px; margin:26px 0 8px; }
p{ color:var(--muted); font-size:14px; line-height:1.6; } p.lead{ max-width:60ch; }
pre{ background:var(--field-bg); border:1px solid var(--panel-bd); border-radius:9px; padding:12px 14px;
overflow:auto; font-size:12.5px; line-height:1.5; color:var(--txt); }
code{ background:var(--field-bg); border:1px solid var(--panel-bd); border-radius:4px; padding:1px 5px; font-size:12px; }
table{ border-collapse:collapse; font-size:13px; width:100%; }
th,td{ text-align:left; padding:5px 8px; border-bottom:1px solid var(--panel-bd); vertical-align:top; }
th{ color:var(--muted); font-weight:600; font-size:11px; text-transform:uppercase; letter-spacing:.04em; }
td.k{ white-space:nowrap; color:var(--cyan); font-family:"Courier New",monospace; }
.demo{ background:var(--panel-bg); border:1px solid var(--panel-bd); border-radius:14px; padding:14px; margin-top:8px; }
.pick{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-top:14px; }
.pick label{ font-size:13px; color:var(--txt); }
.pick select{ background:var(--field-bg); color:var(--txt); border:1px solid var(--panel-bd); border-radius:8px; padding:7px 10px; font-size:13px; }
.ff-name{ color:var(--cyan); font-weight:600; font-size:13px; }
.site-foot{ max-width:760px; margin:40px auto 0; font-size:12px; color:var(--muted); }
</style>
</head>
<body>
/*@BUILD:include:src/header.html@*/
<main>
<h1>Embed a PolyMeter widget</h1>
<p class="lead">Every PolyMeter form factor doubles as an embeddable widget. Drop one into any page with
a placeholder + one script tag — no build step, no dependencies. It loads in an iframe and is preloaded
with whatever <b>program / settings string</b> you give it.</p>
<p class="pick"><label for="ffSel">Show snippets for:</label>
<select id="ffSel">
<option value="editor">PM_E1 Editor</option>
<option value="pme2">PM_E2 Editor (Notation)</option>
<option value="teacher">PM_T1 Teacher</option>
<option value="stage">PM_S1 Stage</option>
<option value="micro" selected>PM_P1 Practice</option>
<option value="showcase">PM_D1 Display</option>
<option value="initial">PM_C1 Concept</option>
</select>
<span class="ff-name"></span></p>
<h2>Drop-in (recommended)</h2>
<pre id="snipDrop"></pre>
<p>The script replaces the <code>&lt;div&gt;</code> with an auto-sizing iframe. Here's the <span class="ff-name"></span>, live on this page:</p>
<div class="demo"><iframe id="demoFrame" title="live embed preview" allow="autoplay" style="border:0;display:block;width:100%;height:300px"></iframe></div>
<h2>Or a plain iframe</h2>
<pre id="snipIframe"></pre>
<h2>Form factors — <code>data-varasys-metronome</code></h2>
<table>
<thead><tr><th>value</th><th>widget</th></tr></thead>
<tbody>
<tr><td class="k">editor</td><td>PM_E1 PolyMeter Editor (full web app)</td></tr>
<tr><td class="k">teacher</td><td>PM_T1 Teacher (studio / lesson console)</td></tr>
<tr><td class="k">stage</td><td>PM_S1 Stage (footpedal stompbox)</td></tr>
<tr><td class="k">micro</td><td>PM_P1 Practice (inline practice bar)</td></tr>
<tr><td class="k">showcase</td><td>PM_D1 Display (RGB pendulum showpiece)</td></tr>
<tr><td class="k">initial</td><td>PM_C1 Concept (idealized render)</td></tr>
</tbody>
</table>
<h2>Configuration / settings string</h2>
<table>
<thead><tr><th>attribute</th><th>what</th></tr></thead>
<tbody>
<tr><td class="k">data-patch</td><td>A PolyMeter program string, e.g. <code>v1;t120;kick:4;snare:4=.X.X;hatClosed:4/2</code>. Copy it from the editor's program field.</td></tr>
<tr><td class="k">data-setlist</td><td>A base64url setlist code (a whole set list) — share it from the editor.</td></tr>
<tr><td class="k">data-width / data-height</td><td>iframe size (default <code>100% × 300</code>; height autogrows to the widget).</td></tr>
</tbody>
</table>
<p>Under the hood the loader builds <code>&lt;page&gt;?embed=1#p=&lt;patch&gt;</code>; the page's <code>?embed=1</code> mode strips the
site chrome so only the widget shows. That's the same way our own Concept &amp; Info pages embed it.</p>
</main>
<script>
const APP_VERSION = "v0.0.1-dev";
const $ = (id)=>document.getElementById(id);
/* Form-factor picker: updates every snippet + the live demo for the chosen version. */
const ORIGIN = "https://metronome.varasys.io";
const DEMO_PATCH = "v1;t120;kick:4;snare:4=.X.X;hatClosed:4/2";
const FF = [
{ k:"editor", name:"PM_E1 Editor", file:"editor.html", h:560 },
{ k:"pme2", name:"PM_E2 Editor", file:"pm_e-2.html", h:640 },
{ k:"kit", name:"PM_K1 Kit", file:"kit.html", h:560 },
{ k:"teacher", name:"PM_T1 Teacher", file:"teacher.html", h:440 },
{ k:"stage", name:"PM_S1 Stage", file:"stage.html", h:430 },
{ k:"micro", name:"PM_P1 Practice", file:"micro.html", h:240 },
{ k:"showcase", name:"PM_D1 Display", file:"showcase.html",h:540 },
{ k:"grid", name:"PM_G1 Grid", file:"grid.html", h:470 },
{ k:"initial", name:"PM_C1 Concept", file:"player.html", h:440 },
];
function updateFF(k){
const v = FF.find(x => x.k === k) || FF[0];
$("snipDrop").textContent =
'<div data-varasys-metronome="' + v.k + '"\n data-patch="' + DEMO_PATCH + '"></div>\n' +
'<script src="' + ORIGIN + '/embed.js"><\/script>';
$("snipIframe").textContent =
'<iframe src="' + ORIGIN + '/' + v.file + '?embed=1#p=' + DEMO_PATCH + '"\n' +
' width="360" height="' + v.h + '" style="border:0"><\/iframe>';
const f = $("demoFrame"); f.style.height = v.h + "px"; f.src = "/" + v.file + "?embed=1#p=" + encodeURIComponent(DEMO_PATCH);
document.querySelectorAll(".ff-name").forEach(el => el.textContent = v.name);
}
$("ffSel").addEventListener("change", (e) => updateFF(e.target.value));
addEventListener("message", (e) => {
if (e.data && e.data.type === "varasys-h" && typeof e.data.h === "number" && e.source === $("demoFrame").contentWindow)
$("demoFrame").style.height = e.data.h + "px";
});
updateFF($("ffSel").value || "micro");
/*@BUILD:include:src/chrome.js@*/
</script>
/*@BUILD:include:src/footer.html@*/
</body>
</html>

55
embed.js Normal file
View file

@ -0,0 +1,55 @@
/* VARASYS PolyMeter embed loader (zero-dependency, ~1 KB).
*
* Drop a placeholder + this script into any page:
* <div data-varasys-metronome="micro" data-patch="v1;t120;kick:4;snare:4=.X.X"></div>
* <script src="https://metronome.varasys.io/embed.js"></script>
*
* Attributes:
* data-varasys-metronome editor | kit | initial | teacher | stage | micro | showcase (which form factor)
* data-patch a PolyMeter program/settings string (preloads it)
* data-setlist a base64url set-list code (alternative to data-patch)
* data-width / data-height iframe size (default 100% × 300; height auto-grows)
*
* Each placeholder becomes an <iframe src=".../<page>?embed=1#p=<patch>"> (the page's
* own ?embed=1 mode strips the site chrome) and auto-resizes to the widget's content.
*/
(function () {
var PAGES = { editor: "editor.html", kit: "kit.html", initial: "player.html", teacher: "teacher.html",
stage: "stage.html", micro: "micro.html", showcase: "showcase.html", grid: "grid.html" };
var me = document.currentScript;
var ORIGIN = me ? me.src.replace(/\/embed\.js(\?.*)?$/, "") : location.origin;
function build(el) {
var v = (el.getAttribute("data-varasys-metronome") || "micro").toLowerCase();
var page = PAGES[v] || "micro.html";
var patch = el.getAttribute("data-patch");
var sl = el.getAttribute("data-setlist");
var hash = patch ? "#p=" + encodeURIComponent(patch)
: sl ? "#sl=" + encodeURIComponent(sl) : "";
var f = document.createElement("iframe");
f.src = ORIGIN + "/" + page + "?embed=1" + hash;
f.title = "VARASYS PolyMeter — " + v;
f.loading = "lazy";
f.setAttribute("allow", "autoplay");
f.setAttribute("data-vmeter", v);
f.style.cssText = "border:0;display:block;max-width:100%;width:" +
(el.getAttribute("data-width") || "100%") + ";height:" +
(el.getAttribute("data-height") || "300") + "px";
(el.replaceWith ? el.replaceWith(f) : el.parentNode.replaceChild(f, el));
}
function init() {
var els = document.querySelectorAll("[data-varasys-metronome]");
for (var i = 0; i < els.length; i++) build(els[i]);
}
// auto-resize: the widget posts { type:'varasys-h', h } on load/resize
window.addEventListener("message", function (e) {
if (!e.data || e.data.type !== "varasys-h" || typeof e.data.h !== "number") return;
var f = document.querySelectorAll("iframe[data-vmeter]");
for (var i = 0; i < f.length; i++) if (f[i].contentWindow === e.source) f[i].style.height = e.data.h + "px";
});
if (document.readyState !== "loading") init();
else document.addEventListener("DOMContentLoaded", init);
})();

418
explorer.html Normal file
View file

@ -0,0 +1,418 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>VARASYS PM_X-1 - Explorer (Pimoroni PIM744 / RP2350)</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
/* ?embed=1 -> strip site chrome + auto-size to the host */
(function(){ if(!/[?&]embed=1/.test(location.search)) return;
document.documentElement.dataset.embed="1";
function ph(){ try{ parent.postMessage({type:"varasys-h",h:Math.ceil(document.documentElement.getBoundingClientRect().height)},"*"); }catch(e){} }
addEventListener("load",ph); addEventListener("resize",ph); setTimeout(ph,300); setTimeout(ph,1000);
})();
</script>
<!--
PM_X-1 "Explorer" - the off-the-shelf Pimoroni Explorer Kit (PIM744, RP2350) as a
button-driven polymeter metronome. Mirrors the firmware (../pico-explorer/app.py)
visually: 320x240 landscape ST7789V + 6 side buttons (A/B/C left, X/Y/Z right) +
piezo. No touchscreen, no joystick, no RGB - just buttons and an on-screen run dot.
Shares src/engine.js with every other device.
-->
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
--panel-bd:#2a313c; --device-bd:#33363c; --silk:#231b06; --cyan:#0AB3F7;
--panel-bg:#161b22; --field-bg:#0e1116; --field-bd:#2a313c; }
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
--panel-bd:#d2dae4; --panel-bg:#ffffff; --field-bg:#f1f4f8; --field-bd:#d2dae4 }
body{ margin:0; min-height:100vh; padding:26px 14px 46px;
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); color:var(--txt);
display:flex; flex-direction:column; align-items:center; gap:16px }
a{ color:var(--link) }
/* Pimoroni-Explorer-style yellow PCB. Wider than tall to fit the 320x240 LCD + side buttons. */
.device{ width:100%; max-width:420px; position:relative; border-radius:14px; padding:12px 12px 14px;
background:
radial-gradient(rgba(0,0,0,.045) .6px, transparent .7px) 0 0/3px 3px,
linear-gradient(180deg, #e6c64b, #c19b25);
border:1px solid #8b6e1f;
box-shadow:0 26px 52px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.18) }
.pcbtop{ display:flex; align-items:center; justify-content:space-between; margin:0 4px 8px }
.dev-logo{ height:16px; filter:invert(15%) sepia(80%) saturate(360%) hue-rotate(355deg) brightness(35%) } /* tint dark on yellow PCB */
:root[data-theme="light"] .dev-logo{ filter:invert(8%) sepia(60%) saturate(0%) brightness(45%) }
.silk{ display:flex; align-items:center; gap:7px; color:var(--silk) }
.silk .model{ font-size:8.5px; text-transform:uppercase; letter-spacing:.16em; opacity:.85 }
.pin{ font-size:7.5px; color:var(--silk); letter-spacing:.12em; text-transform:uppercase; opacity:.7 }
/* main body: side button columns flanking a landscape LCD */
.body{ display:grid; grid-template-columns:auto 1fr auto; gap:9px; align-items:stretch }
.lcol, .rcol{ display:flex; flex-direction:column; justify-content:space-between; gap:6px; padding:6px 0 }
/* the buttons are coloured caps Pimoroni-style: A red, B amber, C teal, X violet, Y yellow, Z blue */
.ebtn{ width:46px; padding:10px 0 6px; border-radius:10px; cursor:pointer;
border:1px solid rgba(0,0,0,.35); color:#0b0e12; font-size:13px; font-weight:900; letter-spacing:.04em; text-transform:uppercase;
box-shadow:0 3px 5px rgba(0,0,0,.35), inset 0 1px 0 rgba(255,255,255,.4) }
.ebtn small{ display:block; margin-top:2px; font-size:7.5px; font-weight:700; opacity:.7; letter-spacing:.06em }
.ebtn:active{ transform:translateY(2px); box-shadow:0 1px 2px rgba(0,0,0,.3) }
#btnA{ background:radial-gradient(circle at 40% 30%, #ff8a8a, #db3838 70%, #9c1d1d) }
#btnB{ background:radial-gradient(circle at 40% 30%, #ffd084, #ec8a18 70%, #a55b06) }
#btnC{ background:radial-gradient(circle at 40% 30%, #8be3c8, #1faa86 70%, #0a6a52) }
#btnX{ background:radial-gradient(circle at 40% 30%, #c1aaff, #794ee0 70%, #4a23a6) }
#btnY{ background:radial-gradient(circle at 40% 30%, #fff19a, #e6c91a 70%, #9b8505); color:#1a1500 }
#btnZ{ background:radial-gradient(circle at 40% 30%, #98c9ff, #2a83f0 70%, #134a98) }
/* the ST7789V LCD: 320x240 landscape, deep black bezel */
.screen-wrap{ padding:6px; border-radius:8px;
background:linear-gradient(180deg,#05070a,#020406); border:1px solid #04060a;
box-shadow:inset 0 2px 8px rgba(0,0,0,.85), 0 1px 0 rgba(255,255,255,.06) }
#screen{ display:block; width:100%; height:auto; border-radius:4px; background:#06080c; image-rendering:pixelated }
/* PCB footer: piezo + breadboard suggestion + silk */
.pcbbot{ display:flex; align-items:center; justify-content:space-between; margin:10px 4px 0; padding:8px 4px 2px;
border-top:1px dashed rgba(0,0,0,.18); color:var(--silk) }
.piezo{ width:22px; height:22px; border-radius:50%; background:radial-gradient(circle at 50% 40%, #3a3a3a, #0c0c0c); border:2px solid #5b4a14; position:relative }
.piezo::after{ content:""; position:absolute; left:50%; top:50%; width:5px; height:5px; margin:-2.5px 0 0 -2.5px; border-radius:50%; background:#05070a }
.breadboard{ flex:1; height:14px; margin:0 12px; border-radius:3px; background:
repeating-linear-gradient(0deg, rgba(0,0,0,.18) 0 2px, transparent 2px 4px),
linear-gradient(180deg, #f6e8a3, #d8be57);
border:1px solid rgba(0,0,0,.22) }
.pcbbot .silk-tag{ font-size:8px; letter-spacing:.14em; text-transform:uppercase; opacity:.8 }
.hint{ max-width:420px; text-align:center; font-size:11px; color:var(--muted); line-height:1.55 }
[data-embed] .hint{ display:none !important }
</style>
</head>
<body>
/*@BUILD:include:src/header.html@*/
<h1 class="ff-title">PM_X1 Explorer</h1>
<p class="ff-sum">Offtheshelf — the Pimoroni Explorer Kit (RP2350, 2.8″ LCD, 6 buttons, piezo) as a buttondriven sibling to the PM_K1 Kit. Edit grooves on the web with <b>Live sync</b>; the device mirrors play/stop/tempo/track changes both ways.</p>
<div class="device">
<div class="pcbtop">
<div class="silk"><img class="dev-logo" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS — Simplifying Complexity" /><span class="model">PM_X1 Explorer</span></div>
<span class="pin">RP2350 · USBC</span>
</div>
<div class="body">
<div class="lcol">
<button class="ebtn" id="btnA" type="button">A<small>play</small></button>
<button class="ebtn" id="btnB" type="button">B<small>tap</small></button>
<button class="ebtn" id="btnC" type="button">C<small>list</small></button>
</div>
<div class="screen-wrap"><canvas id="screen" width="320" height="240" aria-label="metronome display"></canvas></div>
<div class="rcol">
<button class="ebtn" id="btnX" type="button">X<small>prev</small></button>
<button class="ebtn" id="btnY" type="button">Y<small>-bpm</small></button>
<button class="ebtn" id="btnZ" type="button">Z<small>next</small></button>
</div>
</div>
<div class="pcbbot">
<div class="piezo" title="Piezo speaker"></div>
<div class="breadboard" title="Mini breadboard (sensors / I/O)"></div>
<span class="silk-tag">Pimoroni Explorer · PIM744</span>
</div>
</div>
<div class="hint">All hardware, no touch: <b>A</b> = play/stop, <b>B</b> = tap, <b>C</b> = switch playlist.
<b>X</b> / <b>Z</b> = prev / next track; <b>Y</b> = tempo 1 (hold for 5). <b>X+Z</b> chord = tempo +1.
Hold buttons to repeat. Keyboard: A / B / C / X / Y / Z, space = play.</div>
/*@BUILD:include:src/progbox.html@*/
<p class="ff-link pageonly"><a href="/info-explorer.html">Wiring, parts &amp; firmware to flash →</a></p>
<script>
const APP_VERSION = "v0.0.1-dev";
const $ = (id) => document.getElementById(id);
/* ========================= ENGINE (shared; synth voices only) ================= */
const SAMPLES = {};
/*@BUILD:include:src/engine.js@*/
/*@BUILD:include:src/setlists.js@*/
const state = { bpm:120, volume:0.85, running:false };
let meters = [], muteWindows = [];
function setBpm(v){ state.bpm = Math.max(5, Math.min(300, Math.round(v))); if(window.progRefresh) progRefresh(); }
function scheduler(){
const ahead = audioCtx.currentTime + SCHEDULE_AHEAD;
for(const m of meters){ while(m.nextTime < ahead){ scheduleMeterTick(m, m.nextTime); m.nextTime += laneStepDur(m, m.tick); m.tick++; } }
}
function buildMeters(lanes){
return (lanes||[]).map(c=>{ const p=parseGroups(c.groupsStr);
return {groupsStr:c.groupsStr,groups:p.groups,beatsPerBar:p.beatsPerBar,groupStarts:p.groupStarts,
stepsPerBeat:c.stepsPerBeat||1,sound:c.sound,beatsOn:(c.beatsOn||[]).slice(),poly:!!c.poly,swing:!!c.swing,enabled:c.enabled!==false,gainDb:c.gainDb||0,
tick:0,nextTime:0,vq:[],vqPtr:0,currentStep:-1,currentBar:0}; });
}
function startAudio(){
ensureAudio(); audioCtx.resume(); state.running=true;
const t0=audioCtx.currentTime+0.08;
for(const m of meters){ m.tick=0; m.nextTime=t0; m.vq=[]; m.vqPtr=0; m.currentStep=-1; }
muteWindows=[];
schedulerTimer=setInterval(scheduler,LOOKAHEAD_MS); scheduler();
}
function stopAudio(){ state.running=false; clearInterval(schedulerTimer); schedulerTimer=null; for(const m of meters) m.currentStep=-1; }
function toggle(){ state.running ? stopAudio() : startAudio(); }
/* ========================= TRACKS (seed grooves, with set-list grouping for C) === */
let tracks = SEED_SETLISTS.flatMap(sl => sl.items.map(([n,p]) => ({ name:n, sl: sl.title, ...patchToSetup(p) })));
let trackIdx = 0;
function tracksFromHash(){
const m=(location.hash||"").match(/[#&](p|sl)=([^&]+)/); if(!m) return null;
let payload=m[2]; try{ payload=decodeURIComponent(payload); }catch(e){}
try{
if(m[1]==="sl"){ const sl=codeToSetlist(payload); return sl.items.length ? sl.items.map(it=>({...it, sl: sl.title})) : null; }
const s=patchToSetup(payload); return s.lanes.length ? [{name:"Patch", sl:"Patch", ...s}] : null;
}catch(e){ return null; }
}
function loadTrack(i){
const n=tracks.length; if(!n) return; trackIdx=((i%n)+n)%n;
const t=tracks[trackIdx]; setBpm(t.bpm||120); meters=buildMeters(t.lanes);
const was=state.running; if(was){ clearInterval(schedulerTimer); schedulerTimer=null; state.running=false; }
if(was) startAudio();
}
// C cycles to the FIRST item of the next setlist (matches the firmware's set-list-tab swap)
function nextSetlist(){
const cur = tracks[trackIdx].sl;
for(let i=1; i<=tracks.length; i++){
const j = (trackIdx + i) % tracks.length;
if(tracks[j].sl !== cur){ loadTrack(j); return; }
}
}
/* ========================= SCREEN (canvas mirrors the firmware UI at 320x240) ==== */
const cv=$("screen"), g=cv.getContext("2d"), SW=320, SH=240;
(function(){ const dpr=Math.max(1,Math.min(3,window.devicePixelRatio||1)); cv.width=SW*dpr; cv.height=SH*dpr; g.scale(dpr,dpr); })();
const COL={ bg:"#06090e", txt:"#c7d0db", mute:"#788494", cyan:"#0AB3F7", amber:"#ff9b2e",
violet:"#967bff", green:"#2fe07a", dim:"#243240", btn:"#1c222c", panel:"#1C222C",
runIdle:"#2fe07a", runGo:"#ff5a5a", runPulse:"#ffec78", grid:"#1a2330" };
const PRIO={2:3,1:2,3:1};
let runPulse=0, beatIdx=-1, segStart=0;
function drawScreen(){
g.fillStyle=COL.bg; g.fillRect(0,0,SW,SH);
// ----- header (y 0..28) -----
g.textBaseline="alphabetic"; g.textAlign="left";
g.fillStyle=COL.cyan; g.font="700 14px 'Segoe UI',Roboto,Arial,sans-serif"; g.fillText("VARASYS",10,20);
g.fillStyle=COL.mute; g.font="600 9px 'Segoe UI',Roboto,Arial,sans-serif"; g.fillText("v"+(window.APP_VERSION||"0.0.1"),74,20);
// run dot (top-right corner; replaces the Kit's WS2812 RGB LED)
const dotX = SW-14, dotY = 13;
let dotCol = state.running ? COL.runGo : COL.runIdle;
if(runPulse > 0.02){
// 3-channel blend dotCol -> runPulse weighted by runPulse value
const t = runPulse;
const lerp = (a, b) => Math.round(a*(1-t) + b*t);
const rA = parseInt(dotCol.slice(1,3),16), gA = parseInt(dotCol.slice(3,5),16), bA = parseInt(dotCol.slice(5,7),16);
const rB = parseInt(COL.runPulse.slice(1,3),16), gB = parseInt(COL.runPulse.slice(3,5),16), bB = parseInt(COL.runPulse.slice(5,7),16);
dotCol = "#" + lerp(rA,rB).toString(16).padStart(2,"0") + lerp(gA,gB).toString(16).padStart(2,"0") + lerp(bA,bB).toString(16).padStart(2,"0");
}
g.fillStyle = dotCol;
g.beginPath(); g.arc(dotX, dotY, 4, 0, Math.PI*2); g.fill();
// MIDI / USB badges (small markers; mirror the firmware's icon spots)
g.fillStyle = COL.dim;
g.fillRect(dotX-22, dotY-5, 10, 10);
g.fillRect(dotX-38, dotY-5, 10, 10);
// divider
g.fillStyle=COL.panel; g.fillRect(0,28,SW,1);
// ----- setlist tab + CONT (y ~ 32..44) -----
const t = tracks[trackIdx] || {};
const slLabel = (t.sl || "Set list").slice(0,18) + " " + (trackIdx+1) + "/" + tracks.length;
g.fillStyle=COL.mute; g.font="600 11px 'Segoe UI',Roboto,Arial,sans-serif"; g.fillText(slLabel,10,42);
g.textAlign="right"; g.fillStyle=COL.dim; g.fillText("CONT",SW-10,42);
// track name (y 48..60)
g.textAlign="left"; g.fillStyle=COL.txt; g.font="700 14px 'Segoe UI',Roboto,Arial,sans-serif";
g.fillText((t.name||"-").slice(0,22),10,60);
// ----- BPM big (right) + time/bar (left) (y 56..96) -----
g.textAlign="right"; g.fillStyle=COL.txt; g.font="800 38px 'Segoe UI',Roboto,Arial,sans-serif";
g.fillText(String(state.bpm),SW-10,86);
g.textAlign="left"; g.fillStyle=COL.txt; g.font="600 11px 'Segoe UI',Roboto,Arial,sans-serif";
const seg = state.running ? Math.max(0, (audioCtx ? audioCtx.currentTime - audioLatency() : 0) - segStart) : 0;
const fmt = s => { s = Math.floor(s); return Math.floor(s/60) + ":" + (s%60).toString().padStart(2,"0"); };
g.fillText(fmt(seg), 10, 72);
const m0 = meters[0]; const mlen = m0 ? m0.beatsPerBar*(m0.stepsPerBeat||1) : 1;
const bar = state.running && m0 ? Math.max(1, Math.floor((m0.tick-1)/mlen) + 1) : "-";
g.fillStyle=COL.mute; g.fillText("bar " + bar, 10, 88);
// ----- pad grid (y 100..240) -----
const top = 100, gridH = SH - top - 6;
const n = Math.min(meters.length, 6);
if(n === 0) return;
const rowh = Math.min(22, Math.floor(gridH / n));
const px0 = 58, usable = SW - 8 - px0 - 8;
// vertical gridlines at the master lane's beats
const M = meters[0];
const mbeats = Math.max(1, Math.floor(M.beatsPerBar));
g.fillStyle = COL.grid;
for(let bc=0; bc<mbeats; bc++){
const xc = px0 + 6 + Math.floor((bc * usable) / mbeats);
g.fillRect(xc, top, 1, n * rowh);
}
for(let li=0; li<n; li++){
const L = meters[li];
const y = top + li * rowh, cy = y + Math.floor(rowh / 2);
g.fillStyle = COL.mute; g.font="600 10px 'Segoe UI',Roboto,Arial,sans-serif"; g.textAlign="left";
g.fillText((L.sound||"?").slice(0,7), 6, cy + 3);
const steps = (L.beatsOn || []).length || L.beatsPerBar * L.stepsPerBeat;
const stepw = Math.max(1, Math.floor(usable / steps));
const side = Math.max(4, Math.min(12, stepw - 1, rowh - 6));
const rad = Math.max(2, Math.min(Math.floor(side/2), Math.floor(stepw/2) - 1));
const sub = L.stepsPerBeat || 1;
for(let s=0; s<steps; s++){
const cxp = px0 + 6 + Math.floor((s * usable) / steps);
const lvl = (L.beatsOn[s]|0); // 0=mute 1=normal 2=accent 3=ghost
const lit = state.running && (L.currentStep === s);
let col;
if(lvl === 0) col = lit ? "#39414D" : "#10161E";
else if(lvl === 2) col = lit ? COL.amber : "#4A3010";
else if(lvl === 3) col = lit ? COL.violet : "#2A1D4A";
else col = lit ? COL.cyan : "#0A3A52";
g.fillStyle = col;
if(s % sub === 0){
g.fillRect(cxp - Math.floor(side/2), cy - Math.floor(side/2), side, side);
} else {
g.beginPath(); g.arc(cxp, cy, rad, 0, Math.PI*2); g.fill();
}
}
}
}
function audioLatency(){ return audioCtx ? (audioCtx.outputLatency || audioCtx.baseLatency || 0) : 0; }
function frame(){
const now = audioCtx ? audioCtx.currentTime - audioLatency() : 0;
if(audioCtx && state.running){
let fired=[];
for(const m of meters){
while(m.vqPtr<m.vq.length && m.vq[m.vqPtr].time<=now){
const e=m.vq[m.vqPtr]; m.currentStep=e.step; m.currentBar=e.bar;
const lvl=m.beatsOn[e.step]|0; if(lvl>0) fired.push(lvl);
if(m===meters[0] && e.step % m.stepsPerBeat===0) beatIdx = e.step/m.stepsPerBeat;
m.vqPtr++;
}
if(m.vqPtr>512){ m.vq=m.vq.slice(m.vqPtr); m.vqPtr=0; }
}
if(fired.length){ runPulse=1; }
}
runPulse=Math.max(0, runPulse - 0.08);
drawScreen();
requestAnimationFrame(frame);
}
/* ========================= INPUTS (6 buttons + hold-repeat + X+Z chord) =========== */
let taps=[];
function tapTempo(){ const now=performance.now(); taps.push(now); taps=taps.filter(x=>now-x<2400);
if(taps.length>=2){ let s=0; for(let i=1;i<taps.length;i++) s+=taps[i]-taps[i-1];
const bpm=Math.round(60000/(s/(taps.length-1))); if(bpm>=5&&bpm<=300) setBpm(bpm); } }
const held = { X:0, Y:0, Z:0 }; // press start time (ms); 0 = released
const repNext = { X:0, Y:0, Z:0 }; // next auto-repeat deadline
const REPEAT_FIRST = 350, REPEAT_NEXT = 120, FAST_AFTER = 1500; // ms thresholds
let chordXZ = 0; // 0 = not in chord; else press start (ms)
function doX(){ loadTrack(trackIdx - 1); }
function doZ(){ loadTrack(trackIdx + 1); }
function doY(){
const fast = held.Y && (performance.now() - held.Y) > FAST_AFTER;
setBpm(state.bpm + (fast ? -5 : -1));
}
function doChordUp(){
const fast = chordXZ && (performance.now() - chordXZ) > FAST_AFTER;
setBpm(state.bpm + (fast ? 5 : 1));
}
function bindBtn(id, downFn, upFn){
const b = $(id);
b.addEventListener("pointerdown", (e) => { e.preventDefault(); b.setPointerCapture?.(e.pointerId); downFn(); });
b.addEventListener("pointerup", (e) => { e.preventDefault(); upFn(); });
b.addEventListener("pointercancel", () => upFn());
b.addEventListener("pointerleave", () => { if (b.hasPointerCapture?.(0)) upFn(); });
}
bindBtn("btnA", () => toggle(), () => {});
bindBtn("btnB", () => tapTempo(), () => {});
bindBtn("btnC", () => nextSetlist(), () => {});
function pressX(){
const now = performance.now();
if(held.Z && (now - held.Z) < 100){ chordXZ = Math.min(held.Z, now); doChordUp(); }
else { doX(); }
held.X = now; repNext.X = now + REPEAT_FIRST;
}
function pressZ(){
const now = performance.now();
if(held.X && (now - held.X) < 100){ chordXZ = Math.min(held.X, now); doChordUp(); }
else { doZ(); }
held.Z = now; repNext.Z = now + REPEAT_FIRST;
}
function pressY(){
const now = performance.now(); held.Y = now; repNext.Y = now + REPEAT_FIRST; doY();
}
function releaseX(){ held.X = 0; if(!held.Z) chordXZ = 0; }
function releaseZ(){ held.Z = 0; if(!held.X) chordXZ = 0; }
function releaseY(){ held.Y = 0; }
bindBtn("btnX", pressX, releaseX);
bindBtn("btnY", pressY, releaseY);
bindBtn("btnZ", pressZ, releaseZ);
// hold-repeat loop for X / Y / Z
setInterval(() => {
const now = performance.now();
if(held.X && !held.Z && now >= repNext.X){ repNext.X = now + REPEAT_NEXT; doX(); }
if(held.Z && !held.X && now >= repNext.Z){ repNext.Z = now + REPEAT_NEXT; doZ(); }
if(held.X && held.Z && now >= Math.max(repNext.X, repNext.Z)){
repNext.X = repNext.Z = now + REPEAT_NEXT; doChordUp();
}
if(held.Y && now >= repNext.Y){ repNext.Y = now + REPEAT_NEXT; doY(); }
}, 30);
/* ========================= KEYBOARD ============================================ */
addEventListener("keydown", (e) => {
const tag = e.target ? e.target.tagName : ""; if(tag==="INPUT"||tag==="TEXTAREA"||tag==="SELECT") return;
const k = e.key.toLowerCase();
if(e.key === " "){ e.preventDefault(); toggle(); }
else if(k === "a"){ toggle(); }
else if(k === "b"){ tapTempo(); }
else if(k === "c"){ nextSetlist(); }
else if(k === "x"){ pressX(); }
else if(k === "y"){ pressY(); }
else if(k === "z"){ pressZ(); }
});
addEventListener("keyup", (e) => {
const k = e.key.toLowerCase();
if(k === "x") releaseX();
else if(k === "y") releaseY();
else if(k === "z") releaseZ();
});
/* theme toggle + version */
/*@BUILD:include:src/chrome.js@*/
/* ========================= INIT ============================================== */
{ const ht = tracksFromHash(); if(ht) tracks = ht; }
loadTrack(0);
requestAnimationFrame(frame);
// reset segment timer on play; rolls into draw_meters' "X of TOTAL"
const _origStart = startAudio;
startAudio = function(){ segStart = audioCtx ? audioCtx.currentTime + 0.08 : 0; _origStart(); };
window.currentProgramString = function(){ var t = tracks[trackIdx] || {}; return setupToPatch({bpm:state.bpm, volume:state.volume, lanes:t.lanes||[]}); };
window.loadProgramString = function(plain){ var s = patchToSetup(plain); tracks = [{name:"Program", sl:"Program", ...s}]; trackIdx = 0; loadTrack(0); };
/*@BUILD:include:src/progbox.js@*/
</script>
/*@BUILD:include:src/footer.html@*/
</body>
</html>

326
grid.html Normal file
View file

@ -0,0 +1,326 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>VARASYS PM_G-1 - Grid (Pimoroni Pico Scroll Pack / RP2040)</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
/* ?embed=1 -> strip site chrome + auto-size to the host */
(function(){ if(!/[?&]embed=1/.test(location.search)) return;
document.documentElement.dataset.embed="1";
function ph(){ try{ parent.postMessage({type:"varasys-h",h:Math.ceil(document.documentElement.getBoundingClientRect().height)},"*"); }catch(e){} }
addEventListener("load",ph); addEventListener("resize",ph); setTimeout(ph,300); setTimeout(ph,1000);
})();
</script>
<!--
PM_G-1 "Grid" - the off-the-shelf Pimoroni Pico Scroll Pack (PIM545) on a plain Raspberry Pi
Pico (RP2040) as a button-driven polymeter metronome. Mirrors the firmware
(../pico-scroll/app.py) visually: a 17x7 single-colour white LED matrix + 4 buttons (A/B/X/Y).
The 7-row x 17-column matrix IS the editor's lane x step pad grid in miniature. Three views
(button B-hold or the on-screen toggle cycles): Grid, Pendulum, BPM. Shares src/engine.js.
-->
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
--panel-bd:#2a313c; --cyan:#0AB3F7; --panel-bg:#161b22; --field-bg:#0e1116; --field-bd:#2a313c; }
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
--panel-bd:#d2dae4; --panel-bg:#ffffff; --field-bg:#f1f4f8; --field-bd:#d2dae4 }
body{ margin:0; min-height:100vh; padding:26px 14px 46px;
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); color:var(--txt);
display:flex; flex-direction:column; align-items:center; gap:16px }
a{ color:var(--link) }
/* the green Raspberry Pi Pico PCB carrying the Scroll Pack */
.device{ width:100%; max-width:380px; position:relative; border-radius:14px; padding:14px 14px 16px;
background:
radial-gradient(rgba(255,255,255,.03) .6px, transparent .7px) 0 0/3px 3px,
linear-gradient(180deg, #0c5a3a, #073f29);
border:1px solid #04301f;
box-shadow:0 26px 52px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.10) }
.pcbtop{ display:flex; align-items:center; justify-content:space-between; margin:0 4px 10px }
.dev-logo{ height:15px; filter:brightness(0) invert(1); opacity:.82 }
.silk{ display:flex; align-items:center; gap:7px; color:#bfe6d3 }
.silk .model{ font-size:8.5px; text-transform:uppercase; letter-spacing:.16em; opacity:.85 }
.pin{ font-size:7.5px; color:#bfe6d3; letter-spacing:.12em; text-transform:uppercase; opacity:.65 }
/* the matrix panel: black solder mask, the 119 LEDs rendered on a canvas */
.screen-wrap{ padding:10px 12px; border-radius:8px;
background:linear-gradient(180deg,#0a0c0f,#040506); border:1px solid #02030a;
box-shadow:inset 0 2px 10px rgba(0,0,0,.85), 0 1px 0 rgba(255,255,255,.05) }
#screen{ display:block; width:100%; height:auto; border-radius:3px; image-rendering:pixelated }
/* 4 buttons in a row below the matrix (Pimoroni Pico-pack layout: A B on the left, X Y on the right) */
.btnrow{ display:grid; grid-template-columns:repeat(4,1fr); gap:8px; margin:12px 4px 0 }
.ebtn{ padding:9px 0 6px; border-radius:9px; cursor:pointer; text-align:center;
border:1px solid rgba(0,0,0,.4); background:linear-gradient(180deg,#1b2330,#0e141d); color:#dfe7f1;
font-size:13px; font-weight:800; letter-spacing:.04em;
box-shadow:0 3px 5px rgba(0,0,0,.35), inset 0 1px 0 rgba(255,255,255,.08) }
.ebtn small{ display:block; margin-top:2px; font-size:7.5px; font-weight:700; opacity:.6; letter-spacing:.04em; text-transform:uppercase }
.ebtn:active{ transform:translateY(2px); box-shadow:0 1px 2px rgba(0,0,0,.3) }
.hint{ max-width:380px; text-align:center; font-size:11px; color:var(--muted); line-height:1.55 }
[data-embed] .hint{ display:none !important }
</style>
</head>
<body>
/*@BUILD:include:src/header.html@*/
<h1 class="ff-title">PM_G1 Grid</h1>
<p class="ff-sum">Offtheshelf — the Pimoroni <b>Pico Scroll Pack</b> (a 17×7 white LED matrix + 4 buttons) on a plain Raspberry Pi Pico. The matrix <i>is</i> the editor's lane × step pad grid in miniature: rows are lanes, columns are steps, brightness is accent / normal / ghost. Edit on the web with <b>Live sync</b>; the device mirrors play/stop/tempo/track both ways.</p>
<div class="device">
<div class="pcbtop">
<div class="silk"><img class="dev-logo" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS — Simplifying Complexity" /><span class="model">PM_G1 Grid</span></div>
<span class="pin">RP2040 · 17×7</span>
</div>
<div class="screen-wrap"><canvas id="screen" width="306" height="126" aria-label="17 by 7 LED metronome display"></canvas></div>
<div class="btnrow">
<button class="ebtn" id="btnA" type="button">A<small>play</small></button>
<button class="ebtn" id="btnB" type="button">B<small>track</small></button>
<button class="ebtn" id="btnX" type="button">X<small>bpm</small></button>
<button class="ebtn" id="btnY" type="button">Y<small>+bpm</small></button>
</div>
</div>
<div class="hint">Four buttons: <b>A</b> = play/stop (hold = cycle view: Grid → Pendulum → BPM);
<b>B</b> = next track (hold = next set list); <b>X</b> / <b>Y</b> = tempo /+ (hold to repeat, ±5 after ~1.5 s).
Keyboard: A / B / X / Y, space = play, V = cycle view.</div>
/*@BUILD:include:src/progbox.html@*/
<p class="ff-link pageonly"><a href="/info-grid.html">Wiring, parts &amp; firmware to flash →</a></p>
<script>
const APP_VERSION = "v0.0.1-dev";
const $ = (id) => document.getElementById(id);
/* ========================= ENGINE (shared; synth voices only) ================= */
const SAMPLES = {};
/*@BUILD:include:src/engine.js@*/
/*@BUILD:include:src/setlists.js@*/
const state = { bpm:120, volume:0.85, running:false };
let meters = [], muteWindows = [];
function setBpm(v){ state.bpm = Math.max(5, Math.min(300, Math.round(v))); if(window.progRefresh) progRefresh(); }
function scheduler(){
const ahead = audioCtx.currentTime + SCHEDULE_AHEAD;
for(const m of meters){ while(m.nextTime < ahead){ scheduleMeterTick(m, m.nextTime); m.nextTime += laneStepDur(m, m.tick); m.tick++; } }
}
function buildMeters(lanes){
return (lanes||[]).map(c=>{ const p=parseGroups(c.groupsStr);
return {groupsStr:c.groupsStr,groups:p.groups,beatsPerBar:p.beatsPerBar,groupStarts:p.groupStarts,
stepsPerBeat:c.stepsPerBeat||1,sound:c.sound,beatsOn:(c.beatsOn||[]).slice(),poly:!!c.poly,swing:!!c.swing,enabled:c.enabled!==false,gainDb:c.gainDb||0,
tick:0,nextTime:0,vq:[],vqPtr:0,currentStep:-1,currentBar:0}; });
}
function startAudio(){
ensureAudio(); audioCtx.resume(); state.running=true;
const t0=audioCtx.currentTime+0.08;
for(const m of meters){ m.tick=0; m.nextTime=t0; m.vq=[]; m.vqPtr=0; m.currentStep=-1; }
muteWindows=[];
schedulerTimer=setInterval(scheduler,LOOKAHEAD_MS); scheduler();
}
function stopAudio(){ state.running=false; clearInterval(schedulerTimer); schedulerTimer=null; for(const m of meters) m.currentStep=-1; }
function toggle(){ state.running ? stopAudio() : startAudio(); }
/* ========================= TRACKS (seed grooves, with set-list grouping) ========= */
let tracks = SEED_SETLISTS.flatMap(sl => sl.items.map(([n,p]) => ({ name:n, sl: sl.title, ...patchToSetup(p) })));
let trackIdx = 0;
function tracksFromHash(){
const m=(location.hash||"").match(/[#&](p|sl)=([^&]+)/); if(!m) return null;
let payload=m[2]; try{ payload=decodeURIComponent(payload); }catch(e){}
try{
if(m[1]==="sl"){ const sl=codeToSetlist(payload); return sl.items.length ? sl.items.map(it=>({...it, sl: sl.title})) : null; }
const s=patchToSetup(payload); return s.lanes.length ? [{name:"Patch", sl:"Patch", ...s}] : null;
}catch(e){ return null; }
}
function loadTrack(i){
const n=tracks.length; if(!n) return; trackIdx=((i%n)+n)%n;
const t=tracks[trackIdx]; setBpm(t.bpm||120); meters=buildMeters(t.lanes);
const was=state.running; if(was){ clearInterval(schedulerTimer); schedulerTimer=null; state.running=false; }
if(was) startAudio();
}
// B-hold jumps to the FIRST item of the next set list (matches the firmware's switch_setlist)
function nextSetlist(){
const cur = tracks[trackIdx].sl;
for(let i=1; i<=tracks.length; i++){
const j = (trackIdx + i) % tracks.length;
if(tracks[j].sl !== cur){ loadTrack(j); return; }
}
}
/* ========================= 17x7 MATRIX (canvas mirrors the firmware's LED views) === */
const NX = 17, NY = 7;
const cv=$("screen"), g=cv.getContext("2d");
const CW = cv.width, CH = cv.height; // logical px before DPR scaling
(function(){ const dpr=Math.max(1,Math.min(3,window.devicePixelRatio||1)); cv.width=CW*dpr; cv.height=CH*dpr; g.scale(dpr,dpr); })();
const cellX = CW / NX, cellY = CH / NY, rad = Math.min(cellX, cellY) * 0.34;
let view = 0; // 0 Grid, 1 Pendulum, 2 BPM
let beatFlash = 0; // decays each frame; 1 on a fresh beat
const VIEW_NAMES = ["Grid","Pendulum","BPM"];
// 3x5 digit glyphs (bit2 = leftmost column) - same shapes the firmware draws
const DIGITS = { '0':[7,5,5,5,7],'1':[2,6,2,2,7],'2':[7,1,7,4,7],'3':[7,1,7,1,7],'4':[5,5,7,1,1],
'5':[7,4,7,1,7],'6':[7,4,7,5,7],'7':[7,1,2,2,2],'8':[7,5,7,5,7],'9':[7,5,7,1,7] };
function lvlBright(lvl){ return lvl===2 ? 1.0 : lvl===1 ? 0.30 : lvl===3 ? 0.09 : 0; }
function drawMatrix(bright){
g.fillStyle = "#06080b"; g.fillRect(0,0,CW,CH);
for(let y=0; y<NY; y++) for(let x=0; x<NX; x++){
const cx = (x+0.5)*cellX, cy = (y+0.5)*cellY;
const v = Math.max(0, Math.min(1, bright[y][x]||0));
// an "off" LED is a faint dark dot so the whole 17x7 grid stays visible
g.beginPath(); g.arc(cx, cy, rad, 0, Math.PI*2);
g.fillStyle = "rgba(255,255,255,0.05)"; g.fill();
if(v > 0.01){
if(v > 0.5){ g.save(); g.shadowColor="rgba(255,255,255,0.7)"; g.shadowBlur=rad*1.6; }
g.beginPath(); g.arc(cx, cy, rad, 0, Math.PI*2);
g.fillStyle = "rgba(255,255,255," + v.toFixed(3) + ")"; g.fill();
if(v > 0.5) g.restore();
}
}
}
function blankBright(){ const b=[]; for(let y=0;y<NY;y++){ b.push(new Array(NX).fill(0)); } return b; }
function renderGrid(){
const b = blankBright();
const n = Math.min(meters.length, NY);
const y0 = (NY - n) >> 1;
for(let li=0; li<n; li++){
const L = meters[li], y = y0 + li;
const steps = (L.beatsOn||[]).length || L.beatsPerBar*L.stepsPerBeat;
const off = steps <= NX ? ((NX - steps) >> 1) : 0;
const lit = state.running ? L.currentStep : -1;
for(let s=0; s<steps; s++){
const col = steps <= NX ? (s + off) : Math.floor(s*NX/steps);
const lvl = L.beatsOn[s]|0;
let v = (s === lit) ? (lvl ? 1.0 : 0.28) : lvlBright(lvl);
if(v > b[y][col]) b[y][col] = v;
}
}
drawMatrix(b);
}
function renderPendulum(){
const b = blankBright();
const M = meters[0];
if(M){
const steps = (M.beatsOn||[]).length || M.beatsPerBar*M.stepsPerBeat;
const beats = Math.max(1, Math.round(M.beatsPerBar));
let frac = 0;
if(state.running && M.currentStep >= 0) frac = (M.currentStep % steps) / steps;
const tri = frac < 0.5 ? frac*2 : 2*(1-frac);
const col = Math.round(tri * (NX-1));
const v = beatFlash > 0.5 ? 1.0 : (beatFlash > 0.05 ? 0.6 : 0.35);
for(let y=0; y<NY; y++) b[y][col] = v;
for(let bi=0; bi<beats; bi++){ const bc = Math.floor(bi*NX/beats); if(b[NY-1][bc] < 0.1) b[NY-1][bc] = 0.1; }
}
drawMatrix(b);
}
function renderBpm(){
const b = blankBright();
const s = String(state.bpm).slice(-3);
const w = s.length*4 - 1, x0 = (NX - w) >> 1, y0 = 1;
const v = state.running ? 1.0 : 0.5;
for(let i=0;i<s.length;i++){
const gph = DIGITS[s[i]]; if(!gph) continue;
const bx = x0 + i*4;
for(let ry=0; ry<5; ry++) for(let rx=0; rx<3; rx++)
if(gph[ry] & (1 << (2-rx))) b[y0+ry][bx+rx] = v;
}
drawMatrix(b);
}
function cycleView(){ view = (view+1) % 3; }
/* the engine queues voice events with timestamps; mirror the firmware's playhead off that queue */
function audioLatency(){ return audioCtx ? (audioCtx.outputLatency || audioCtx.baseLatency || 0) : 0; }
function frame(){
const now = audioCtx ? audioCtx.currentTime - audioLatency() : 0;
if(audioCtx && state.running){
let fired=false;
for(const m of meters){
while(m.vqPtr<m.vq.length && m.vq[m.vqPtr].time<=now){
const e=m.vq[m.vqPtr]; m.currentStep=e.step; m.currentBar=e.bar;
if((m.beatsOn[e.step]|0) > 0) fired=true;
m.vqPtr++;
}
if(m.vqPtr>512){ m.vq=m.vq.slice(m.vqPtr); m.vqPtr=0; }
}
if(fired) beatFlash = 1;
}
beatFlash = Math.max(0, beatFlash - 0.08);
if(view===2) renderBpm(); else if(view===1) renderPendulum(); else renderGrid();
requestAnimationFrame(frame);
}
/* ========================= INPUTS (4 buttons; tap vs hold like the firmware) ====== */
const HOLD_MS = 600, REPEAT_FIRST = 350, REPEAT_NEXT = 120, FAST_AFTER = 1500;
const pressT = { A:0, B:0 }; // press-start (ms) for A/B tap-vs-hold
const held = { X:0, Y:0 }; // press-start (ms) for X/Y repeat; 0 = released
const repNext = { X:0, Y:0 };
function nudge(dir){ const fast = held[dir>0?"Y":"X"] && (performance.now() - held[dir>0?"Y":"X"]) > FAST_AFTER;
setBpm(state.bpm + dir*(fast?5:1)); }
function bindBtn(id, downFn, upFn){
const b = $(id);
b.addEventListener("pointerdown", (e) => { e.preventDefault(); b.setPointerCapture?.(e.pointerId); downFn(); });
b.addEventListener("pointerup", (e) => { e.preventDefault(); upFn(); });
b.addEventListener("pointercancel", () => upFn());
b.addEventListener("pointerleave", () => { if (b.hasPointerCapture?.(0)) upFn(); });
}
// A: tap = play/stop, hold = cycle view. B: tap = next track, hold = next set list.
bindBtn("btnA", () => { pressT.A = performance.now(); },
() => { (performance.now()-pressT.A >= HOLD_MS) ? cycleView() : toggle(); });
bindBtn("btnB", () => { pressT.B = performance.now(); },
() => { (performance.now()-pressT.B >= HOLD_MS) ? nextSetlist() : loadTrack(trackIdx+1); });
bindBtn("btnX", () => { held.X = performance.now(); repNext.X = held.X + REPEAT_FIRST; nudge(-1); }, () => { held.X = 0; });
bindBtn("btnY", () => { held.Y = performance.now(); repNext.Y = held.Y + REPEAT_FIRST; nudge(1); }, () => { held.Y = 0; });
setInterval(() => { // hold-repeat for X / Y
const now = performance.now();
if(held.X && now >= repNext.X){ repNext.X = now + REPEAT_NEXT; nudge(-1); }
if(held.Y && now >= repNext.Y){ repNext.Y = now + REPEAT_NEXT; nudge(1); }
}, 30);
/* ========================= KEYBOARD ============================================ */
addEventListener("keydown", (e) => {
const tag = e.target ? e.target.tagName : ""; if(tag==="INPUT"||tag==="TEXTAREA"||tag==="SELECT") return;
const k = e.key.toLowerCase();
if(e.key === " "){ e.preventDefault(); toggle(); }
else if(k === "a"){ toggle(); }
else if(k === "v"){ cycleView(); }
else if(k === "b"){ loadTrack(trackIdx+1); }
else if(k === "x"){ if(!held.X){ held.X = performance.now(); repNext.X = held.X + REPEAT_FIRST; nudge(-1); } }
else if(k === "y"){ if(!held.Y){ held.Y = performance.now(); repNext.Y = held.Y + REPEAT_FIRST; nudge(1); } }
});
addEventListener("keyup", (e) => {
const k = e.key.toLowerCase();
if(k === "x") held.X = 0; else if(k === "y") held.Y = 0;
});
/* theme toggle + version */
/*@BUILD:include:src/chrome.js@*/
/* ========================= INIT ============================================== */
{ const ht = tracksFromHash(); if(ht) tracks = ht; }
loadTrack(0);
requestAnimationFrame(frame);
window.currentProgramString = function(){ var t = tracks[trackIdx] || {}; return setupToPatch({bpm:state.bpm, volume:state.volume, lanes:t.lanes||[]}); };
window.loadProgramString = function(plain){ var s = patchToSetup(plain); tracks = [{name:"Program", sl:"Program", ...s}]; trackIdx = 0; loadTrack(0); };
/*@BUILD:include:src/progbox.js@*/
</script>
/*@BUILD:include:src/footer.html@*/
</body>
</html>

36
hardware/BOM.csv Normal file
View file

@ -0,0 +1,36 @@
Ref,Block,Part,Manufacturer,MPN,Qty,Approx_USD_ea,Notes
U1,MCU,RP2350A microcontroller,Raspberry Pi,RP2350A,1,1.10,QFN-60; mind E9 input-latch erratum (external pulldowns)
U2,MCU,16MB QSPI flash,Winbond,W25Q128JVSIQ,1,1.20,genuine part; firmware wear-levels history.json
Y1,MCU,12MHz crystal,Abracon,ABM8-272-12.000MHZ,1,0.30,+/-30ppm
U3,Power,3V3 IO LDO,Diodes Inc,AP2112K-3.3TRG1,1,0.25,digital domain
L1,Power,RP2350 core SMPS inductor,TDK,VLS3012,1,0.15,per RP2350 reference
U4,Power,Dual boost/inverter +/-18V,Texas Instruments,TPS65131RGER,1,2.50,raw +/-18V from 5V; guarded corner
U5,Power,Ultra-low-noise +15V LDO,Texas Instruments,TPS7A4901DGNR,1,1.80,post-regulates +18 to clean +15
U6,Power,Ultra-low-noise -15V LDO,Texas Instruments,TPS7A3001DGNR,1,1.90,post-regulates -18 to clean -15
J1,Power/USB,USB-C receptacle (TH-anchored),GCT,USB4085-GF-A,1,0.60,through-hole anchor tabs for strain
U7,Power/USB,USB ESD protection array,STMicroelectronics,USBLC6-2SC6,1,0.30,D+/D-/CC/VBUS
FL1,Power/USB,USB common-mode choke,Wurth,744232090,1,0.35,data-pair EMI
U8,Click,I2S audio DAC,Texas Instruments,PCM5102APWR,1,2.20,Burr-Brown; reliability-first
X1,Click,Low-jitter audio oscillator,Abracon,ASEM1-24.576MHZ-LR-T,1,1.40,dedicated MCLK; not PIO-jittered
U9,Audio-in,Balanced line receiver,THAT Corp,THAT1240S08-U,1,3.20,0dB unity; pinout verified doc600035 rev05; 2nd-src INA134/SSM2141
U10,Audio-in,JFET Hi-Z instrument buffer,Texas Instruments,OPA1641AID,1,1.60,>=1Mohm DI buffer + gain
U11,Audio-mix,Dual audio op-amp (sum/filter),Texas Instruments,OPA1612AIDR,1,2.40,signal-path low noise
U12,Audio-out,Balanced line driver,THAT Corp,THAT1646S08-U,1,3.30,near-zero source; 47ohm build-out per leg; pinout TBV at output stage
RV1,Audio-out,Output level cal trimmer 25-turn,Bourns,3296W-1-103LF,1,0.70,factory-set DAC FS -> +4dBu
U13,Indicator,Dual comparator (sig/clip),Texas Instruments,LM393DR,1,0.15,peak-detect -> RP2350 GPIO + LED lines
K1,Audio-in,Signal relay line/inst (DPDT gold),Panasonic,TQ2SA-5V,1,1.30,sealed gold bifurcated contacts
K2,Audio-out,Mute relay (fail-safe DPDT gold),Panasonic,TQ2SA-5V,1,1.30,de-energized=muted
K3,Audio-out,Ground-lift relay (gold),Panasonic,TQ2SA-5V,1,1.30,series with face panel switch; soft-lift 100R||10nF
U14,Control,Relay driver array,Texas Instruments,ULN2003ADR,1,0.20,drives K1-K3 with flyback
U15,RTC,Real-time clock (integrated xtal),Micro Crystal,RV-8803-C7,1,1.50,I2C on touch bus; drift irrelevant
BT1,RTC,CR2032 holder (socketed),Keystone,1066,1,0.30,user-replaceable
U16,MIDI(DNP),Opto-isolator MIDI IN,Vishay,H11L1M,1,0.55,DNP populate-option
U17,MIDI(DNP),Hex Schmitt buffer MIDI OUT/THRU,Nexperia,74LVC14APW,1,0.20,DNP populate-option
U18,Speaker(DNP),Class-D mono amp,Diodes Inc,PAM8302AASCR,1,0.35,DNP monitor option per face
J2,Interconnect,Digital ribbon header 2x13 shrouded keyed,Wurth,61303421821,1,0.45,Pico-pinout-compatible
J3,Interconnect,Analog audio header 2x5 shrouded keyed,Wurth,61301021821,1,0.35,twisted/shielded; away from digital
J4,Interconnect,MIDI header 1x6,Wurth,61300611121,1,0.20,used only if DNP MIDI populated
J5,Debug,SWD Cortex-Debug 2x5 1.27mm,Samtec,FTSH-105-01-L-DV-K,1,0.50,service header
D-arr,ESD,Interconnect ESD clamp arrays,Texas Instruments,TPD2E2U06DCKR,3,0.10,on user-touchable lines
PASS,Passives,Film caps / 0.1% thin-film R / clamp diodes / ferrites,various,various,1,4.00,WIMA film signal caps; Panasonic ERA 0.1%; no electrolytics in signal path
PCB,Fabrication,4-layer PCB ENIG finish,JLCPCB/PCBWay,custom,1,3.00,gold finish; qty-dependent
1 Ref Block Part Manufacturer MPN Qty Approx_USD_ea Notes
2 U1 MCU RP2350A microcontroller Raspberry Pi RP2350A 1 1.10 QFN-60; mind E9 input-latch erratum (external pulldowns)
3 U2 MCU 16MB QSPI flash Winbond W25Q128JVSIQ 1 1.20 genuine part; firmware wear-levels history.json
4 Y1 MCU 12MHz crystal Abracon ABM8-272-12.000MHZ 1 0.30 +/-30ppm
5 U3 Power 3V3 IO LDO Diodes Inc AP2112K-3.3TRG1 1 0.25 digital domain
6 L1 Power RP2350 core SMPS inductor TDK VLS3012 1 0.15 per RP2350 reference
7 U4 Power Dual boost/inverter +/-18V Texas Instruments TPS65131RGER 1 2.50 raw +/-18V from 5V; guarded corner
8 U5 Power Ultra-low-noise +15V LDO Texas Instruments TPS7A4901DGNR 1 1.80 post-regulates +18 to clean +15
9 U6 Power Ultra-low-noise -15V LDO Texas Instruments TPS7A3001DGNR 1 1.90 post-regulates -18 to clean -15
10 J1 Power/USB USB-C receptacle (TH-anchored) GCT USB4085-GF-A 1 0.60 through-hole anchor tabs for strain
11 U7 Power/USB USB ESD protection array STMicroelectronics USBLC6-2SC6 1 0.30 D+/D-/CC/VBUS
12 FL1 Power/USB USB common-mode choke Wurth 744232090 1 0.35 data-pair EMI
13 U8 Click I2S audio DAC Texas Instruments PCM5102APWR 1 2.20 Burr-Brown; reliability-first
14 X1 Click Low-jitter audio oscillator Abracon ASEM1-24.576MHZ-LR-T 1 1.40 dedicated MCLK; not PIO-jittered
15 U9 Audio-in Balanced line receiver THAT Corp THAT1240S08-U 1 3.20 0dB unity; pinout verified doc600035 rev05; 2nd-src INA134/SSM2141
16 U10 Audio-in JFET Hi-Z instrument buffer Texas Instruments OPA1641AID 1 1.60 >=1Mohm DI buffer + gain
17 U11 Audio-mix Dual audio op-amp (sum/filter) Texas Instruments OPA1612AIDR 1 2.40 signal-path low noise
18 U12 Audio-out Balanced line driver THAT Corp THAT1646S08-U 1 3.30 near-zero source; 47ohm build-out per leg; pinout TBV at output stage
19 RV1 Audio-out Output level cal trimmer 25-turn Bourns 3296W-1-103LF 1 0.70 factory-set DAC FS -> +4dBu
20 U13 Indicator Dual comparator (sig/clip) Texas Instruments LM393DR 1 0.15 peak-detect -> RP2350 GPIO + LED lines
21 K1 Audio-in Signal relay line/inst (DPDT gold) Panasonic TQ2SA-5V 1 1.30 sealed gold bifurcated contacts
22 K2 Audio-out Mute relay (fail-safe DPDT gold) Panasonic TQ2SA-5V 1 1.30 de-energized=muted
23 K3 Audio-out Ground-lift relay (gold) Panasonic TQ2SA-5V 1 1.30 series with face panel switch; soft-lift 100R||10nF
24 U14 Control Relay driver array Texas Instruments ULN2003ADR 1 0.20 drives K1-K3 with flyback
25 U15 RTC Real-time clock (integrated xtal) Micro Crystal RV-8803-C7 1 1.50 I2C on touch bus; drift irrelevant
26 BT1 RTC CR2032 holder (socketed) Keystone 1066 1 0.30 user-replaceable
27 U16 MIDI(DNP) Opto-isolator MIDI IN Vishay H11L1M 1 0.55 DNP populate-option
28 U17 MIDI(DNP) Hex Schmitt buffer MIDI OUT/THRU Nexperia 74LVC14APW 1 0.20 DNP populate-option
29 U18 Speaker(DNP) Class-D mono amp Diodes Inc PAM8302AASCR 1 0.35 DNP monitor option per face
30 J2 Interconnect Digital ribbon header 2x13 shrouded keyed Wurth 61303421821 1 0.45 Pico-pinout-compatible
31 J3 Interconnect Analog audio header 2x5 shrouded keyed Wurth 61301021821 1 0.35 twisted/shielded; away from digital
32 J4 Interconnect MIDI header 1x6 Wurth 61300611121 1 0.20 used only if DNP MIDI populated
33 J5 Debug SWD Cortex-Debug 2x5 1.27mm Samtec FTSH-105-01-L-DV-K 1 0.50 service header
34 D-arr ESD Interconnect ESD clamp arrays Texas Instruments TPD2E2U06DCKR 3 0.10 on user-touchable lines
35 PASS Passives Film caps / 0.1% thin-film R / clamp diodes / ferrites various various 1 4.00 WIMA film signal caps; Panasonic ERA 0.1%; no electrolytics in signal path
36 PCB Fabrication 4-layer PCB ENIG finish JLCPCB/PCBWay custom 1 3.00 gold finish; qty-dependent

71
hardware/BOM_board.csv Normal file
View file

@ -0,0 +1,71 @@
Qty,Refs,Value,Footprint,Manufacturer,MPN,Populate,Notes
30,C6 C23 C24 C25 C26 C27 C28 C29 C30 C31 C34 C35 C38 C39 C41 C42 C43 C44 C45 C52 C53 C54 C55 C57 C58 C59 C60 C61 C62 C64,100nF,Capacitor_SMD:C_0402_1005Metric,,,,
12,R7 R9 R13 R23 R27 R28 R29 R35 R37 R44 R48 R49,10k,Resistor_SMD:R_0402_1005Metric,,,,
9,C12 C15 C18 C19 C20 C49 C63 C65 C66,1uF,Capacitor_SMD:C_0402_1005Metric,,,,
7,D3 D4 D5 D6 D7 D8 D11,1N4148WS,Diode_SMD:D_SOD-323,onsemi,1N4148WS,,fast diode (input clamps / MIDI protect)
6,R2 R5 R42 R45 R46 R50,100k,Resistor_SMD:R_0402_1005Metric,,,,
5,R10 R38 R39 R40 R41,33,Resistor_SMD:R_0402_1005Metric,,,,
4,C11 C14 C17 C56,10nF,Capacitor_SMD:C_0402_1005Metric,,,,
4,R17 R19 R20 R43,1Meg,Resistor_SMD:R_0402_1005Metric,,,,
4,R12 R16 R18 R22,1k,Resistor_SMD:R_0402_1005Metric,,,,
4,C13 C16 C47 C48,2.2uF,Capacitor_SMD:C_0402_1005Metric,,,,
4,C2 C3 C21 C22,4.7uF,Capacitor_SMD:C_0402_1005Metric,,,,
4,D9 D10 D12 D13,BAT54,Diode_SMD:D_SOD-323,onsemi,BAT54,,Schottky (RTC diode-OR / peak-detect)
3,K1 K2 K3,TQ2SA-5V,Relay_SMD:Relay_DPDT_Panasonic_TQ2-SA,Panasonic,TQ2SA-5V,,"DPDT signal relay, gold; K1 select/K2 mute/K3 gnd-lift"
2,R25 R26,1.5k,Resistor_SMD:R_0402_1005Metric,,,,
2,R3 R32,100,Resistor_SMD:R_0402_1005Metric,,,,
2,C1 C46,10uF,Capacitor_SMD:C_1206_3216Metric,,,,
2,C32 C33,15pF,Capacitor_SMD:C_0402_1005Metric,,,,
2,C36 C37,2.2uF,Capacitor_SMD:C_1206_3216Metric,,,,
2,C4 C7,22uF,Capacitor_SMD:C_1206_3216Metric,,,,
2,R14 R15,27,Resistor_SMD:R_0402_1005Metric,,,,
2,R33 R34,4.7k,Resistor_SMD:R_0402_1005Metric,,,,
2,L2 L3,4.7uH,Inductor_SMD:L_0806_2016Metric,Wurth/EPCOS,7447789004 / B82462-G4472,,switcher inductor
2,R30 R31,47,Resistor_SMD:R_0402_1005Metric,,,,
2,R52 R53,5.1k,Resistor_SMD:R_0402_1005Metric,,,,
2,R47 R51,68k,Resistor_SMD:R_0402_1005Metric,,,,
2,D1 D2,MBRM120,Diode_SMD:D_SOD-323,onsemi,MBRM120ET3G,,Schottky rectifier (switcher)
2,SW1 SW2,SW_Push,Button_Switch_SMD:SW_SPST_SKQG_WithStem,,,,
1,R11,0,Resistor_SMD:R_0402_1005Metric,,,,
1,R1,1.4M,Resistor_SMD:R_0402_1005Metric,,,,
1,R4,1.5M,Resistor_SMD:R_0402_1005Metric,,,,
1,C40,100nF,Capacitor_SMD:C_1206_3216Metric,,,,
1,RV1,10k,Potentiometer_THT:Potentiometer_Bourns_3296W_Vertical,Bourns,3296W-1-103LF,,output level cal trim (25-turn)
1,R6,116k,Resistor_SMD:R_0402_1005Metric,,,,
1,R8,117k,Resistor_SMD:R_0402_1005Metric,,,,
1,Y1,12MHz,Crystal:Crystal_SMD_3225-4Pin_3.2x2.5mm,Abracon,ABM8-272-12.000MHZ-T3,,RP2350 crystal; confirm load caps
1,C51,1nF,Capacitor_SMD:C_0402_1005Metric,,,,
1,R24,2.2k,Resistor_SMD:R_0402_1005Metric,,,,
1,C50,2.2nF,Capacitor_SMD:C_0402_1005Metric,,,,
1,R36,220,Resistor_SMD:R_0402_1005Metric,,,,
1,C9,220nF,Capacitor_SMD:C_0402_1005Metric,,,,
1,L4,3.3uH,Inductor_SMD:L_0806_2016Metric,Abracon,AOTA-B201610S3R3-101-T,,RP2350 core SMPS inductor
1,R21,3k,Resistor_SMD:R_0402_1005Metric,,,,
1,C10,4.7nF,Capacitor_SMD:C_0402_1005Metric,,,,
1,C5,6.8pF,Capacitor_SMD:C_0402_1005Metric,,,,
1,L1,600R,Inductor_SMD:L_0806_2016Metric,Murata,BLM18KG..,,ferrite bead (USB VBUS input)
1,C8,7.5pF,Capacitor_SMD:C_0402_1005Metric,,,,
1,U4,AP2112K-3.3,Package_TO_SOT_SMD:SOT-23-5,Diodes,AP2112K-3.3TRG1,,3V3 LDO; confirm SOT-23-5 pinout
1,BT1,CR2032,Battery:BatteryHolder_Keystone_1066_1x2032,Keystone,1066,,coin-cell holder (RTC backup)
1,J1,Conn_01x04,Connector_PinHeader_1.27mm:PinHeader_1x04_P1.27mm_Vertical,,,,
1,J4,Conn_01x08,Connector_PinHeader_2.54mm:PinHeader_1x08_P2.54mm_Vertical,,,,
1,J3,Conn_02x05_Odd_Even,Connector_PinHeader_2.54mm:PinHeader_2x05_P2.54mm_Vertical,,,,
1,J2,Conn_02x13_Odd_Even,Connector_PinHeader_2.54mm:PinHeader_2x13_P2.54mm_Vertical,,,,
1,U10,OPA1612,Package_SO:SOIC-8_3.9x4.9mm_P1.27mm,TI,OPA1612AIDR,,dual: recon filter + summer
1,U8,OPA1641,Package_SO:SOIC-8_3.9x4.9mm_P1.27mm,TI,OPA1641AID,,JFET Hi-Z DI buffer
1,U9,PCM5102A,Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm,TI,PCM5102APWR,,I2S DAC; SCK->GND (MCLK-less)
1,U5,RP2350A,Package_DFN_QFN:QFN-60-1EP_7x7mm_P0.4mm_EP3.6x3.6mm,Raspberry Pi,RP2350A,,MCU (QFN-60); via KiCad lib symbol
1,U13,RV-8803-C7,RTC_MicroCrystal:RV-8803-C7,Micro Crystal,RV-8803-C7,,I2C RTC; confirm footprint
1,U7,THAT1240,Package_SO:SOIC-8_3.9x4.9mm_P1.27mm,THAT Corp,THAT1240S08-U,,0dB balanced line receiver; 2nd-src INA134/SSM2141
1,U11,THAT1646,Package_SO:SOIC-8_3.9x4.9mm_P1.27mm,THAT Corp,THAT1646S08-U,,"balanced line driver, +6dB; 2nd-src DRV134/SSM2142"
1,U1,TPS65131,Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm,TI,TPS65131RGER,,dual boost/inverter -> +/-18V
1,U3,TPS7A3001,Package_SO:HVSSOP-8-1EP_3x3mm_P0.65mm,TI,TPS7A3001DGNR,,-15V ultra-low-noise LDO; confirm Vfb
1,U2,TPS7A4901,Package_SO:HVSSOP-8-1EP_3x3mm_P0.65mm,TI,TPS7A4901DGNR,,+15V ultra-low-noise LDO; confirm Vfb
1,U12,ULN2003A,Package_SO:SOIC-16_3.9x9.9mm_P1.27mm,TI,ULN2003ADR,,shared relay driver (3 of 7 ch used)
1,U18,USBLC6-2SC6,Package_TO_SOT_SMD:SOT-23-6,STMicro,USBLC6-2SC6,,USB ESD
1,J5,USB_C_Receptacle,Connector_USB:USB_C_Receptacle_HRO_TYPE-C-31-M-12,GCT,USB4085-GF-A,,USB-C; 24-pin sym vs 16-pin part - resolve at layout
1,U6,W25Q128JVS,Package_SO:SOIC-8_5.23x5.23mm_P1.27mm,Winbond,W25Q128JVSIQ,,16MB QSPI flash
1,U15,74LVC14,Package_SO:TSSOP-14_4.4x5mm_P0.65mm,Nexperia,74LVC14APW,DNP,DNP - MIDI OUT/THRU buffer
1,U14,H11L1,Package_DIP:DIP-6_W7.62mm,Vishay,H11L1M,DNP,DNP - MIDI opto IN; confirm pinout
1,U16,LM393,Package_SO:SOIC-8_3.9x4.9mm_P1.27mm,TI,LM393DR,DNP,DNP - SIG/CLIP comparator
1,U17,PAM8302A,Package_SO:SOIC-8_3.9x4.9mm_P1.27mm,Diodes,PAM8302AASCR,DNP,DNP - monitor speaker amp
1 Qty Refs Value Footprint Manufacturer MPN Populate Notes
2 30 C6 C23 C24 C25 C26 C27 C28 C29 C30 C31 C34 C35 C38 C39 C41 C42 C43 C44 C45 C52 C53 C54 C55 C57 C58 C59 C60 C61 C62 C64 100nF Capacitor_SMD:C_0402_1005Metric
3 12 R7 R9 R13 R23 R27 R28 R29 R35 R37 R44 R48 R49 10k Resistor_SMD:R_0402_1005Metric
4 9 C12 C15 C18 C19 C20 C49 C63 C65 C66 1uF Capacitor_SMD:C_0402_1005Metric
5 7 D3 D4 D5 D6 D7 D8 D11 1N4148WS Diode_SMD:D_SOD-323 onsemi 1N4148WS fast diode (input clamps / MIDI protect)
6 6 R2 R5 R42 R45 R46 R50 100k Resistor_SMD:R_0402_1005Metric
7 5 R10 R38 R39 R40 R41 33 Resistor_SMD:R_0402_1005Metric
8 4 C11 C14 C17 C56 10nF Capacitor_SMD:C_0402_1005Metric
9 4 R17 R19 R20 R43 1Meg Resistor_SMD:R_0402_1005Metric
10 4 R12 R16 R18 R22 1k Resistor_SMD:R_0402_1005Metric
11 4 C13 C16 C47 C48 2.2uF Capacitor_SMD:C_0402_1005Metric
12 4 C2 C3 C21 C22 4.7uF Capacitor_SMD:C_0402_1005Metric
13 4 D9 D10 D12 D13 BAT54 Diode_SMD:D_SOD-323 onsemi BAT54 Schottky (RTC diode-OR / peak-detect)
14 3 K1 K2 K3 TQ2SA-5V Relay_SMD:Relay_DPDT_Panasonic_TQ2-SA Panasonic TQ2SA-5V DPDT signal relay, gold; K1 select/K2 mute/K3 gnd-lift
15 2 R25 R26 1.5k Resistor_SMD:R_0402_1005Metric
16 2 R3 R32 100 Resistor_SMD:R_0402_1005Metric
17 2 C1 C46 10uF Capacitor_SMD:C_1206_3216Metric
18 2 C32 C33 15pF Capacitor_SMD:C_0402_1005Metric
19 2 C36 C37 2.2uF Capacitor_SMD:C_1206_3216Metric
20 2 C4 C7 22uF Capacitor_SMD:C_1206_3216Metric
21 2 R14 R15 27 Resistor_SMD:R_0402_1005Metric
22 2 R33 R34 4.7k Resistor_SMD:R_0402_1005Metric
23 2 L2 L3 4.7uH Inductor_SMD:L_0806_2016Metric Wurth/EPCOS 7447789004 / B82462-G4472 switcher inductor
24 2 R30 R31 47 Resistor_SMD:R_0402_1005Metric
25 2 R52 R53 5.1k Resistor_SMD:R_0402_1005Metric
26 2 R47 R51 68k Resistor_SMD:R_0402_1005Metric
27 2 D1 D2 MBRM120 Diode_SMD:D_SOD-323 onsemi MBRM120ET3G Schottky rectifier (switcher)
28 2 SW1 SW2 SW_Push Button_Switch_SMD:SW_SPST_SKQG_WithStem
29 1 R11 0 Resistor_SMD:R_0402_1005Metric
30 1 R1 1.4M Resistor_SMD:R_0402_1005Metric
31 1 R4 1.5M Resistor_SMD:R_0402_1005Metric
32 1 C40 100nF Capacitor_SMD:C_1206_3216Metric
33 1 RV1 10k Potentiometer_THT:Potentiometer_Bourns_3296W_Vertical Bourns 3296W-1-103LF output level cal trim (25-turn)
34 1 R6 116k Resistor_SMD:R_0402_1005Metric
35 1 R8 117k Resistor_SMD:R_0402_1005Metric
36 1 Y1 12MHz Crystal:Crystal_SMD_3225-4Pin_3.2x2.5mm Abracon ABM8-272-12.000MHZ-T3 RP2350 crystal; confirm load caps
37 1 C51 1nF Capacitor_SMD:C_0402_1005Metric
38 1 R24 2.2k Resistor_SMD:R_0402_1005Metric
39 1 C50 2.2nF Capacitor_SMD:C_0402_1005Metric
40 1 R36 220 Resistor_SMD:R_0402_1005Metric
41 1 C9 220nF Capacitor_SMD:C_0402_1005Metric
42 1 L4 3.3uH Inductor_SMD:L_0806_2016Metric Abracon AOTA-B201610S3R3-101-T RP2350 core SMPS inductor
43 1 R21 3k Resistor_SMD:R_0402_1005Metric
44 1 C10 4.7nF Capacitor_SMD:C_0402_1005Metric
45 1 C5 6.8pF Capacitor_SMD:C_0402_1005Metric
46 1 L1 600R Inductor_SMD:L_0806_2016Metric Murata BLM18KG.. ferrite bead (USB VBUS input)
47 1 C8 7.5pF Capacitor_SMD:C_0402_1005Metric
48 1 U4 AP2112K-3.3 Package_TO_SOT_SMD:SOT-23-5 Diodes AP2112K-3.3TRG1 3V3 LDO; confirm SOT-23-5 pinout
49 1 BT1 CR2032 Battery:BatteryHolder_Keystone_1066_1x2032 Keystone 1066 coin-cell holder (RTC backup)
50 1 J1 Conn_01x04 Connector_PinHeader_1.27mm:PinHeader_1x04_P1.27mm_Vertical
51 1 J4 Conn_01x08 Connector_PinHeader_2.54mm:PinHeader_1x08_P2.54mm_Vertical
52 1 J3 Conn_02x05_Odd_Even Connector_PinHeader_2.54mm:PinHeader_2x05_P2.54mm_Vertical
53 1 J2 Conn_02x13_Odd_Even Connector_PinHeader_2.54mm:PinHeader_2x13_P2.54mm_Vertical
54 1 U10 OPA1612 Package_SO:SOIC-8_3.9x4.9mm_P1.27mm TI OPA1612AIDR dual: recon filter + summer
55 1 U8 OPA1641 Package_SO:SOIC-8_3.9x4.9mm_P1.27mm TI OPA1641AID JFET Hi-Z DI buffer
56 1 U9 PCM5102A Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm TI PCM5102APWR I2S DAC; SCK->GND (MCLK-less)
57 1 U5 RP2350A Package_DFN_QFN:QFN-60-1EP_7x7mm_P0.4mm_EP3.6x3.6mm Raspberry Pi RP2350A MCU (QFN-60); via KiCad lib symbol
58 1 U13 RV-8803-C7 RTC_MicroCrystal:RV-8803-C7 Micro Crystal RV-8803-C7 I2C RTC; confirm footprint
59 1 U7 THAT1240 Package_SO:SOIC-8_3.9x4.9mm_P1.27mm THAT Corp THAT1240S08-U 0dB balanced line receiver; 2nd-src INA134/SSM2141
60 1 U11 THAT1646 Package_SO:SOIC-8_3.9x4.9mm_P1.27mm THAT Corp THAT1646S08-U balanced line driver, +6dB; 2nd-src DRV134/SSM2142
61 1 U1 TPS65131 Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm TI TPS65131RGER dual boost/inverter -> +/-18V
62 1 U3 TPS7A3001 Package_SO:HVSSOP-8-1EP_3x3mm_P0.65mm TI TPS7A3001DGNR -15V ultra-low-noise LDO; confirm Vfb
63 1 U2 TPS7A4901 Package_SO:HVSSOP-8-1EP_3x3mm_P0.65mm TI TPS7A4901DGNR +15V ultra-low-noise LDO; confirm Vfb
64 1 U12 ULN2003A Package_SO:SOIC-16_3.9x9.9mm_P1.27mm TI ULN2003ADR shared relay driver (3 of 7 ch used)
65 1 U18 USBLC6-2SC6 Package_TO_SOT_SMD:SOT-23-6 STMicro USBLC6-2SC6 USB ESD
66 1 J5 USB_C_Receptacle Connector_USB:USB_C_Receptacle_HRO_TYPE-C-31-M-12 GCT USB4085-GF-A USB-C; 24-pin sym vs 16-pin part - resolve at layout
67 1 U6 W25Q128JVS Package_SO:SOIC-8_5.23x5.23mm_P1.27mm Winbond W25Q128JVSIQ 16MB QSPI flash
68 1 U15 74LVC14 Package_SO:TSSOP-14_4.4x5mm_P0.65mm Nexperia 74LVC14APW DNP DNP - MIDI OUT/THRU buffer
69 1 U14 H11L1 Package_DIP:DIP-6_W7.62mm Vishay H11L1M DNP DNP - MIDI opto IN; confirm pinout
70 1 U16 LM393 Package_SO:SOIC-8_3.9x4.9mm_P1.27mm TI LM393DR DNP DNP - SIG/CLIP comparator
71 1 U17 PAM8302A Package_SO:SOIC-8_3.9x4.9mm_P1.27mm Diodes PAM8302AASCR DNP DNP - monitor speaker amp

221
hardware/DESIGN.md Normal file
View file

@ -0,0 +1,221 @@
# PM_K-1 Core Board ("brain") — design-of-record
VARASYS PolyMeter · heirloom pro-audio · modular brain/face architecture
Status: **design-of-record / pre-layout.** Component selection complete; PCB routing is the
remaining interactive step (see [Open items](#open-items)).
---
## 1. Philosophy
This is meant to be a device people hand down to their great-grandkids. The core board carries
**all the active electronics** and is specced to pro/audiophile tier with longevity and
serviceability as first-class goals. We do **not** value-engineer the audio path.
The user selects the face/enclosure/connector components; this document specs only the **core**.
## 2. Architecture — modular brain/face
- **Core ("brain"):** RP2350 + power + RTC + the full pro-audio analog chain + control logic.
One core design is reused across every form factor.
- **Face ("form factor"):** display, touch, joystick, buttons, LED, speaker, the physical
audio/MIDI connectors, panel switches, enclosure. **The core never decides connector type.**
- **Two interconnects, deliberately separate** (§7):
1. a **digital ribbon** whose pinout mirrors the Raspberry Pi Pico, so a stock Pico/Pico 2 on a
test adapter can drive any face board for bring-up;
2. an **analog interconnect** (+ a small MIDI interconnect) kept physically away from the fast
digital ribbon, because a balanced audio signal must never run parallel to the 24 MHz display SPI.
## 3. Block diagram
```
USB-C 5V ─┬─► 3V3 LDO ──────────────────────► RP2350 (core reg + ext L) + W25Q128 16MB + RV-8803 RTC(+CR2032)
│ │ I²S + low-jitter MCLK
└─► TPS65131 ±18V ─► TPS7A49/30 LDO ─► clean ±15V ▼
PCM5102A DAC ──► [summing: click + input]
bal IN ─[ESD/DC-block/clamp/series-R]─► [LINE/INST relay] ─┬─ THAT1240 receiver ──┘
(analog interconnect) └─ OPA1641 Hi-Z DI buffer (+gain)
THAT1646 balanced driver ─[47Ω build-out]─► [MUTE relay]─► bal OUT
shield ─[panel SW]─[GND-LIFT relay]─ gnd
sig/clip peak detect ─► LM393 ─► RP2350 GPIO (UI) + LED lines (interconnect)
RP2350 UART ─► [DNP: H11L1 opto IN + 74LVC14 buffer OUT] ─► MIDI interconnect (USB-MIDI = default)
```
## 4. Functional blocks
### 4.1 MCU + digital
- **RP2350A** (QFN-60, 30 GPIO). Chosen over RP2040 as the newest in-house part: dual M33+RISC-V,
520 KB RAM, secure boot, longest production runway. Firmware runs unchanged.
- **Erratum E9:** high-impedance inputs can latch — every input we read (face switches, buttons)
gets an **external pulldown**, never relies on the internal pad alone.
- Core supply via the RP2350 on-chip switched-mode regulator (external inductor); 3V3 IO from an
external LDO (AP2112K-3.3 / TLV75533).
- **Flash:** Winbond **W25Q128JV** (16 MB) — genuine part; the CircuitPython appliance bundles the
editor + tracks on a USB drive. Firmware does wear-leveling for `history.json`.
- **Crystal:** 12 MHz ±30 ppm.
- **Debug/service:** SWD 2×5 Cortex-Debug header + labeled test points (rails, I²S, audio nodes).
### 4.2 Click source (DAC)
- **TI/Burr-Brown PCM5102A** I²S DAC — reliability-first, widely stocked for future repair.
- Fed by a **dedicated low-jitter audio oscillator** (22.5792/24.576 MHz MEMS XO), **not** an MCLK
jittered out of the RP2350 PIO — jitter is audible as a raised noise floor.
### 4.3 Analog audio chain (the heart)
- **Input (balanced, switchable line/instrument):**
- **Line mode:** THAT1240 laser-trimmed balanced receiver (~high CMRR, no hand-matched resistors).
- **Instrument mode:** OPA1641 JFET Hi-Z buffer (≥1 MΩ) + ~+1015 dB gain (active DI).
- Selected by a **gold-contact signal relay** on the core (1 GPIO; touchscreen toggle, optional
face panel switch on a separate GPIO input).
- **Protection (non-negotiable):** series DC-blocking film cap (blocks +48 V phantom — the real
input-killer), clamp diodes/TVS to the rails, series current-limit resistor. A wrong-mode plug
then only *sounds* wrong; nothing is damaged.
- **Mix:** digital/firmware (touchscreen). Analog stage at unity; click level set via the DAC.
- **Output driver:** THAT1646 balanced line driver, **47 Ω build-out per leg** for
cable-capacitance stability and short-circuit tolerance (§5.1).
- **No electrolytics in the signal path** — film coupling caps (WIMA). 0.1 % thin-film resistors.
### 4.4 Output protection / conditioning
- **Power-up/down mute relay** — fail-safe, **de-energized = muted** (shorts hot+cold to gnd). A
hardware rail supervisor + RC turn-on delay un-mutes only after ±15 V settles; on power loss the
coil drops and mutes *faster than the rails can thump*. **Not MCU-dependent.** The MCU can *also*
assert mute (for clean line/inst flips and DAC reconfig).
- **Ground-lift** — both a **face panel switch** and a **core GPIO relay**, wired in **series** in
the shield-ground path: bonded only when both closed, either opening lifts it. **Soft lift =
100 Ω ∥ 10 nF** (not a hard open) so RF/safety keeps a path.
### 4.5 RTC
- **Micro Crystal RV-8803** (integrated 32.768 kHz crystal → no second crystal near the RP2350's
own) + **CR2032 in a socketed holder**. Shares the touch I²C bus (no extra GPIO). Drift is
irrelevant for a practice-log timestamp; reliability and zero-fuss layout win.
### 4.6 MIDI (default USB, hardware optional)
- **USB-MIDI is the default** and already in firmware — IN/OUT/THRU are software routing to a
computer/tablet host. Zero extra parts.
- **Dedicated DIN/TRS MIDI is a DNP populate-option:** the RP2350 UART lines route to the MIDI
interconnect with **H11L1 opto IN + 74LVC14 buffered OUT/THRU footprints left unpopulated**.
A "stage" face can populate them for laptop-free hardware sync. (USB-MIDI can't peer-to-peer with
standalone DIN gear — the device is a USB *peripheral*, not a host — which is why the hardware
option stays available.) Analog pulse/clock sync: **not included** (MIDI only).
### 4.7 Monitor speaker
- **DNP-optional** class-D amp (PAM8302) footprint on the core; speaker +/- routed on the analog
interconnect. Populated only for form factors that want a built-in monitor.
## 5. The five remaining pro details (decided)
### 5.1 Output impedance & level calibration
- THAT1646 source impedance is near-zero; **47 Ω build-out resistors** per leg give stable driving
into long/capacitive cables and survive a shorted output.
- **Level calibration:** a 25-turn precision trimmer (Bourns 3296W) in the driver gain network,
factory-set so DAC full-scale → **+4 dBu nominal**, leaving ~**+24 dBu** peak headroom on ±15 V.
Set-and-forget on the core; not a face control.
### 5.2 Signal / clip indication
- A peak detector (Schottky + hold cap) on the input (signal-present, ~40 dBu) and on the
driver-input/summing node (clip, within ~3 dB of rail) feeds an **LM393** dual comparator.
- Comparator outputs go to **RP2350 GPIOs** (clip/signal shown on the touchscreen) **and** are
mirrored to **SIG/CLIP LED drive lines on the digital interconnect** so a face can fit discrete LEDs.
### 5.3 ESD / EMI hardening
- **USB-C:** USBLC6-2SC6 (or TPD4E05U06) ESD array on D±/CC/VBUS; common-mode choke on the data
pair; shell bonded to chassis via RC; ferrite + TVS + bulk on VBUS.
- **Interconnects:** ~33 Ω series on fast SPI lines for edge-rate control; ESD clamp arrays on any
line reaching a user-touchable cable; interleaved ground pins; ferrite beads where 3V3/5V cross
into the analog domain.
- **Board:** full ground planes + stitching vias; the boost/inverter switcher lives in a guarded
corner away from the analog section; analog/digital grounds meet at a single **star point**.
- **Heirloom option:** conformal coating for humidity/longevity (build-time choice).
### 5.4 Chassis / strain-relief (core-side)
- 4× **M3 mounting holes** with keep-outs; a dedicated chassis-ground pad/pin.
- **Through-hole-anchored USB-C** jack (SMD-only tabs shear off with cable wiggle — unacceptable for
a 50-year device).
- **Shrouded, keyed, latching** interconnect headers (can't insert backward or vibrate loose).
- Panel strain-relief and connector mounting live on the face/enclosure.
### 5.5 Interconnect pinout — see §7.
## 6. Power tree
| Rail | Source | Part | Notes |
|---|---|---|---|
| +5 V | USB-C VBUS | — | ferrite + TVS + bulk |
| +3V3 (IO) | LDO from 5 V | AP2112K-3.3 / TLV75533 | digital domain |
| +1.1 V core | RP2350 internal SMPS | (external inductor) | per RP2350 ref |
| ±18 V (raw) | dual boost/inverter from 5 V | **TPS65131** | switcher, guarded corner |
| **±15 V (clean)** | ultra-low-noise LDO | **TPS7A4901 (+) / TPS7A3001 ()** | feeds all audio op-amps |
## 7. Interconnect pinouts
### 7.1 Digital ribbon — 2×13 (26-pin) IDC, Pico-pinout-compatible
Grounds interleaved around SPI. A Pico/Pico 2 test adapter maps these to the listed GP.
| Pin | Signal | GP | | Pin | Signal | GP |
|----|--------|----|--|----|--------|----|
| 1 | +5V | — | | 2 | GND | — |
| 3 | +3V3 | — | | 4 | GND | — |
| 5 | SPI_SCK | GP2 | | 6 | GND | — |
| 7 | SPI_MOSI | GP3 | | 8 | LCD_CS | GP5 |
| 9 | LCD_DC | GP6 | | 10 | LCD_RST | GP7 |
| 11 | GND | — | | 12 | I2C_SDA | GP8 |
| 13 | I2C_SCL | GP9 | | 14 | GND | — |
| 15 | JOY_X (ADC0) | GP26 | | 16 | JOY_Y (ADC1) | GP27 |
| 17 | BTN_A | GP15 | | 18 | BTN_B | GP14 |
| 19 | WS2812 | GP12 | | 20 | GND | — |
| 21 | GNDLIFT_SW (in) | GP21 | | 22 | LINEINST_SW (in) | GP22 |
| 23 | SIG_LED | GP19 | | 24 | CLIP_LED | GP20 |
| 25 | GND | — | | 26 | GND | — |
*I²S (BCK/LRCK/DOUT), the relays (line/inst route GP16, mute GP18, gnd-lift GP17), and MCLK stay
on-core — they are not on the ribbon. A Pico test brain drives the digital/face I/O above but
**cannot** drive the analog chain (DAC/op-amps are core-only).*
> **Display TE line — route it on the production face.** The table above mirrors the EP-0172
> prototype, which wires only SCK/MOSI/CS/DC/RST and **no TE (tearing-effect) output**. Per the
> ST7796S datasheet §10.8, the panel rescans its frame memory to the glass at 60 Hz and the *only*
> sanctioned way to avoid tearing is to feed the **TE output back to the MCU** and write during
> vblank (`tvdh`). Without it, large updates tear and only small sub-region writes hide it (this is
> why MicroPython/CircuitPython, which draw glyph-by-glyph, look clean and the Rust full-frame blits
> don't). **Add the LCD `TE` pin to a spare GPIO (e.g. GP4)** on the face interconnect — and
> optionally LCD `MISO`/`SDO` for `GSCAN`(45h)/status reads. Then firmware can do
> `TEON`(35h)+vblank-synced writes and be fully tear-free even on full repaints.
### 7.2 Analog audio interconnect — 2×5 (10-pin), twisted/shielded, away from digital
| Pin | Signal | | Pin | Signal |
|----|--------|--|----|--------|
| 1 | AOUT_HOT | | 2 | AGND |
| 3 | AOUT_COLD | | 4 | CHASSIS/SHIELD (face side of ground-lift) |
| 5 | AIN_HOT | | 6 | AGND |
| 7 | AIN_COLD | | 8 | SPK+ (DNP) |
| 9 | AGND | | 10 | SPK (DNP) |
### 7.3 MIDI interconnect — 1×6, only if DNP MIDI populated
| Pin | Signal |
|----|--------|
| 1 | MIDI_OUT_A (TRS-A tip/ring leg) |
| 2 | MIDI_OUT_B |
| 3 | MIDI_IN_A (to opto) |
| 4 | MIDI_IN_B |
| 5 | +5V (OUT drive) |
| 6 | GND / shield |
## 8. BOM
Full part list with manufacturer numbers and rough costs in **`hardware/BOM.csv`**. Headline parts:
RP2350A · W25Q128JV · PCM5102A · THAT1240 + THAT1646 · OPA1641 · OPA1612 · TPS65131 + TPS7A4901/3001 ·
RV-8803 · USBLC6-2SC6 · 3× Panasonic TQ2SA gold-contact relays · H11L1 (DNP).
## 9. Manufacturing
- **PCB:** ENIG (gold) finish — non-negotiable for decades of reliable contacts/solderability.
- **Assembly:** JLCPCB/PCBWay PCBA, ~5-board prototype minimum; ~$80200 first run. Core parts cost
~$2540/board one-off (pro op-amps + relays dominate), trending toward ~$1520 at qty 100.
- Most expensive items are the THAT audio ICs and the relays — that's where "heirloom" lives.
## 10. Open items
- **PCB layout/routing is the interactive next step** — placement, controlled-impedance USB pair,
star ground, switcher isolation, copper pours, DRC. The KiCad project under `hardware/kicad/`
is a documented schematic canvas + design-of-record; symbol placement and wiring happen in
Eeschema. `kicad-cli` is used here for ERC and PDF export verification.
- Confirm the exact 3.5" ST7796/GT911 panel connector (FPC vs header) before finalizing the face.
- Decide MIDI connector (TRS-MIDI Type-A vs DIN-5) per form factor — face decision.

108
hardware/LAYOUT.md Normal file
View file

@ -0,0 +1,108 @@
# PM_K-1 Core Board — PCB Layout Guide
The rulebook for turning `hardware/kicad/board.net` (167 components) into a routed board.
Read alongside `pcb_layout_tutorial.md` (how the tool works) and `DESIGN.md` (why the
circuit is shaped this way). This is a mixed-signal board — a switching supply, sensitive
±15 V analog, a fast MCU, and USB all on one PCB — so **layout discipline matters more than
on a simple digital board.** The schematic is done and verified; good layout is now what
makes it actually perform.
## 1. Stackup — use 4 layers
A 2-layer board will work electrically but will be noisy. Use **4 layers**:
```
L1 signal + components (top)
L2 GROUND plane (solid) <- the single most important thing for noise
L3 power (3V3 / +5 / ±15 split zones)
L4 signal + components (bottom)
```
A solid ground plane directly under the signal layer gives every fast/analog trace a clean
return path. **Do not cut the L2 ground plane up** (one exception: the star-point, below).
Finish: **ENIG (gold)** — heirloom requirement.
## 2. Grounding — the heart of a mixed-signal board
There are three "kinds" of ground current that must not share copper:
- **Switcher ground** — the TPS65131 boost/inverter pumps current in sharp pulses. Dirty.
- **Digital ground** — RP2350, flash, USB. Medium.
- **Analog ground** — the ±15 V audio section (THAT/OPA, DAC). Must stay quiet.
**Strategy:** one solid ground plane (L2), but *partition by placement* — put the switcher
in one corner, digital in another, analog in a third, so each section's return currents stay
local and don't flow under another section. Join them at a **single star point** near the
main ground entry. Keep the switcher's high-current loop entirely in its corner.
- `AGND` (analog signal ground) and `CHASSIS` (shield) meet **only** through the ground-lift
relay K3 + its 100 Ω∥10 nF soft-lift — keep those two nodes otherwise separate.
- The DAC datasheet rule: AGND/DGND/CPGND within 0.2 V of each other — tie them at the star.
## 3. Switching supply (TPS65131) — most layout-critical block
Switchers fail in *layout*, not schematic. Keep these loops physically tiny:
- **Input caps** (C1/C2, 4.7 µF) right at the INP/INN pins.
- **Inductors L1/L2 (4.7 µH)** and **Schottkys D1/D2 (MBRM120)** close to the switch nodes
(`SW_BOOST`, `SW_INV`) — these nodes are the noisiest copper on the board; keep them **small
and away from everything analog**. Don't run any audio or USB trace near them.
- **Output caps** (C4/C5, 22 µF) close to VPOS/VNEG.
- **Feedback dividers** (R1/R2, R3/R4) and `VREF` (C8) routed quietly, away from the switch nodes.
- Then the **±15 V LDOs** (TPS7A49/30) take the raw ±18 V to clean rails — place them between
the switcher and the analog section so the clean rails enter analog, not the raw ones.
## 4. Analog audio section — keep it quiet and far
- Place the whole audio chain (THAT1240/1646, OPA1641/1612, DAC, relays) **as a group**, far
from the switcher and the digital ribbon.
- **No analog audio trace runs parallel to the fast SPI or the switch nodes** (this is why
the analog interconnect J3 is physically separate from the digital ribbon J2).
- Film coupling caps and 0.1 % resistors in the signal path; keep signal traces short.
- The balanced in/out: route HOT/COLD as a **tight pair** so noise hits both equally (that's
what the receiver's CMRR rejects). The `MUTE` (K2) and `GND-LIFT` (K3) relays near the output.
- The level-cal trimmer **RV1** must be reachable with a screwdriver after assembly.
## 5. RP2350 core (per RP "Hardware design with RP2350")
- **QSPI flash** (U?) traces to the RP2350 **short and direct** — high-frequency bus.
- **12 MHz crystal** tight to XIN/XOUT, with its own local ground; a guard ring helps; keep
fast signals away from underneath it.
- **Core SMPS**: the 3.3 µH inductor loop (VREG_LX → DVDD) small; 100 nF per power pin, placed
right at each pin.
- **BOOTSEL** (R6 1 k + button) and the flash-CS resistors close to the flash.
## 6. USB
- `D+`/`D-` (`USB_DP_CONN`/`USB_DM_CONN`) routed as a **90 Ω differential pair**: short,
length-matched, same layer, reference the ground plane, no stubs.
- The **USBLC6-2 ESD** and the **27 Ω series resistors** right at the connector, before the pair.
- CC 5.1 k pulldowns near the connector. USB-C shell to chassis.
## 7. The two interconnects (the modular split)
- **J2 digital ribbon** (Pico-pinout) and **J3 analog** (balanced audio + speaker) are on
separate connectors *on purpose* — keep them physically apart on the board and in the
cabling. Interleave grounds on the digital ribbon for the SPI return.
- **J4 MIDI** only matters if the DNP MIDI block is fitted.
## 8. Mechanical & fab
- 4× M3 mounting holes with keep-outs; a chassis-ground pad.
- **USB-C connector with through-hole anchor tabs** (SMD-only tabs shear off — unacceptable
for a 50-year device). Connector strain relief lives on the face/enclosure.
- Shrouded/keyed/latching interconnect headers.
- **4-layer, ENIG**, controlled impedance on the USB pair. Optional conformal coat for humidity.
## 9. Thermal
- The ±15 V LDOs dissipate (VinVout)×I ≈ (1815)×~30 mA ≈ 90 mW each — small, but give them
copper pour / the PowerPAD to the plane.
- The TPS65131 thermal pad to the ground plane with vias.
## 10. Do-not-populate (DNP) blocks
Per form factor, these are optional — leave the footprints, populate per build:
- **MIDI** (U?/H11L1 opto + 74LVC14 buffer + its resistors)
- **SIG/CLIP indicator** (LM393 + peak-detect parts)
- **monitor speaker amp** (PAM8302A + its caps/resistors)
The BOM flags the DNP *ICs*; their surrounding passives are DNP too when the block is unfitted.
## 11. Pre-fab confirm list (flagged during capture)
- Footprints: RV-8803-C7, the QFN variants (RP2350A/TPS65131), USB-C (24-pin symbol vs the
16-pin GCT USB4085 part — pick one), the relay TQ2-SA.
- Values: LDO V_FB exact (used 1.194 V / 1.18 V for the dividers); crystal load caps (~15 pF
per the chosen crystal); 3.3 V-MIDI series-R values.
- Confirm: H11L1 pinout (standard; datasheet fetch had timed out); PCM5102A MCLK-less = SCK→GND.
## 12. Final checks before Gerbers
1. **DRC clean** (clearances, track widths, the diff-pair rules).
2. Re-import the netlist and confirm no unrouted nets (ratsnest empty).
3. Verify the switcher loops are tight and isolated; analog has no switch-node neighbors.
4. Confirm the ground star point and the analog/digital partition.
5. Generate Gerbers + drill + pick-and-place + the BOM (`BOM_board.csv`) for the fab.

View file

@ -0,0 +1,112 @@
# PM_K-1 — Per-Stage Layout References
`LAYOUT.md` has *our* rules (general principles for this board). This file points at the
**manufacturers' own reference layouts, EVMs, and layout app-notes** for each stage — so
whoever routes the board can follow *proven* examples instead of working from first
principles. Two of these are essentially our exact circuit.
⭐ = a reference design / EVM that closely matches what we built (highest value).
## Power — switching supply (TPS65131) — the most layout-critical stage
- ⭐ **TPS65131EVM-839** (the eval board) + its **User's Guide SLVUAW7**.
https://www.ti.com/tool/TPS65131EVM-839 · https://www.ti.com/lit/ug/slvuaw7/slvuaw7.pdf
*Takeaway:* 4-layer, all parts top side; **switching nodes isolated from the feedback
network**, careful high-frequency current routing, **separate analog & power grounds**,
feedback components small/closely-spaced. TI explicitly says "follow the EVM layout."
- Datasheet layout section: TPS65131 (SLVS493E) — already in `datasheets/`.
## Power — clean ±15 V LDOs (TPS7A4901 / TPS7A3001)
- ⭐ **TPS7A30-49EVM-567** — a dual EVM with **15 V and +15 V outputs** = *our exact rails*.
https://www.ti.com/tool/TPS7A30-49EVM-567 (search TI for the user's guide)
*Takeaway:* the reference layout for the exact LDO pair we use.
- Datasheet rule (TPS7A49/TPS7A30): **separate IN and OUT ground planes joined only at the
GND pin**; keep the NR/SS cap and feedback divider tight; ~2 dB PSRR from good grounding.
https://www.ti.com/lit/ds/symlink/tps7a49.pdf · https://www.ti.com/lit/ds/symlink/tps7a30.pdf
E2E thread on layout + resistor selection: https://e2e.ti.com/support/power-management-group/power-management/f/power-management-forum/238266/
## MCU (RP2350)
- ⭐ **Official RP2350 "Minimal" KiCad design files** (schematic + symbols + footprints +
the minimal layout): https://datasheets.raspberrypi.com/rp2350/Minimal-KiCAD.zip
*Takeaway:* fork this — it's the authoritative layout for the core SMPS inductor loop,
decoupling, crystal, QSPI, and USB. (Also a clean source for the RP2350 + QFN footprints.)
- **Hardware design with RP2350** (RP-008280) — in `datasheets/` (decoupling-per-pin,
crystal, QSPI-short, USB 27 Ω series, VREG_AVDD filter).
- Third-party open board: **Olimex RP2350-PICO2-BB48** (full open KiCad).
https://www.olimex.com/Products/RaspberryPi/PICO/RP2350-PICO2-BB48/open-source-hardware
## DAC (PCM5102A)
- Datasheet layout section: PCM5102A (SLAS859C) — in `datasheets/`.
- Widely-cloned reference: the **GY-PCM5102 module** + community layouts.
diyAudio: https://www.diyaudio.com/community/threads/dac-design-based-on-pcm5102a.410231/ ·
KiCad forum: https://forum.kicad.info/t/how-do-i-design-a-pcm5102a-dac/56441
*Takeaway:* **one solid ground plane** (well-segmented), decoupling (10 nF + 100 nF) within
~5 mm of VDD, full copper pour under the IC, ground-via grid, short I²S traces.
## Balanced audio (THAT1240 receiver / THAT1646 driver)
- THAT product pages (have app guidance + reference circuits):
https://thatcorp.com/that-1606-1646-balanced-line-driver-ics/ · audio design resources:
https://thatcorp.com/Audio_Design_Resources.php
- Community reference layouts: theslowdiyer THAT1646 board
https://theslowdiyer.wordpress.com/tag/that1646/ · Elektor "Balanced Audio Line Driver"
https://www.elektormagazine.com/labs/balanced-audio-line-driver
*Takeaway:* 22 Ω + decoupling caps right at the ±15 V pins; DC-block "link" caps; RFI
filter (series R/ferrite + small cap) at the connector; keep input and output apart;
route HOT/COLD as a tight pair.
## Op-amps (OPA1641 / OPA1612 — filter, summer, DI buffer)
- **TI SBOA092B***Handbook of Operational Amplifier Applications*:
https://www.ti.com/lit/an/sboa092b/sboa092b.pdf
- **TI SLOA046 / SLOA102** — amplifier layout app-notes:
https://www.ti.com/lit/an/sloa046/sloa046.pdf
- TI Precision Hub "**The basics: how to layout a PCB for an op amp**":
https://e2e.ti.com/blogs_/archives/b/precisionhub/posts/the-basics-how-to-layout-a-pcb-for-an-op-amp
*Takeaway:* decoupling < 0.1 in from the supply pins; **feedback resistor right at the
inverting input** (minimal loop area / parasitic C); short input traces; ground plane under;
keep input and output apart for channel isolation.
## RTC (RV-8803-C7)
- **RV-8803-C7 Application Manual** (rev 1.6) — §"Dimensions and Solder Pad Layout" +
"Recommended Thermal Relief" (≈ p6566):
https://www.microcrystal.com/fileadmin/Media/Products/RTC/App.Manual/RV-8803-C7_App-Manual.pdf
(EM Microelectronic mirror: https://www.emmicroelectronic.com/sites/default/files/products/datasheets/rv-8803-c7-mn01.pdf)
- Ready footprint (verify against ours): SnapEDA RV-8803-C7-TA-QC.
*Takeaway:* confirms the SON-8 pad land pattern we built + thermal relief.
## USB (USB-C receptacle + USBLC6-2SC6 ESD)
- USB-C routing guides: PCBWay https://www.pcbway.com/blog/PCB_Design_Layout/USB_Type_C_PCB_Design_Guidelines_Layout_and_Routing_Best_Practices_55bc0c39.html ·
Cadence https://resources.pcb.cadence.com/blog/2024-impedance-matching-for-usb-interfaces-in-pcbs ·
Altium (2-layer USB) https://resources.altium.com/p/routing-requirements-usb-20-2-layer-pcb
*Takeaway:* **D± = 90 Ω ±10 % differential pair**, skew < 15 ps (~100 mil), ground on both
sides (gap ≤ 3× trace width). Place the **USBLC6-2 within ~100200 mil of the connector,
before any other component, no vias** between connector and diode (vias slow the clamp).
## Class-D speaker amp (PAM8302A, DNP)
- TI Class-D layout app-note (applies to filterless BTL output): **SLAA896 / SLAA902**
https://www.ti.com/lit/an/slaa896/slaa896.pdf
*Takeaway:* keep the BTL output traces short and symmetric; add the EMI ferrite/cap near the
output if cabling is long; the switching output is noisy — keep it away from the analog input.
## Relay (TQ2SA), ULN2003, LM393
- Straightforward digital/contact layout: relay contact traces wide enough for the switched
current; ULN2003 flyback (COM to the coil supply) close; LM393 standard comparator layout.
Panasonic TQ-SMD datasheet (in `datasheets/`) has the recommended mounting pad.
---
### The three to prioritize (the hard parts)
1. **TPS65131EVM-839 / SLVUAW7** — copy the switcher layout almost verbatim.
2. **TPS7A30-49EVM-567** — it's literally the ±15 V dual-LDO layout we need.
3. **RP2350 Minimal-KiCAD.zip** — fork the MCU/USB/crystal/QSPI layout.
Hand these (plus `LAYOUT.md`) to whoever routes the board; they cover every stage where
layout — not schematic — determines whether it works.
## Saved locally (in `hardware/datasheets/`, git-ignored)
The three priority references are downloaded for offline use:
- **`TPS65131EVM_SLVUAW7.pdf`** — switcher EVM, includes the full **4-layer PCB plots**
(Top / Inner 1 / Inner 2 / Bottom) + BOM (which matches our TPS65131RGER / 4.7 µH / MBRM120).
*Copy this layout for the switcher block.*
- **`TPS7A30-49EVM_SLVU405.pdf`** — the ±15 V dual-LDO EVM (schematic + layout + thermal + BOM).
- **`RP2350-Minimal-KiCAD.zip`** — official RP2350 reference. **Nested:** unzip it, then extract
`RP-006440-...RP2350A Minimal Board Kicad archive.zip` (RP2350A / QFN-60 = our part) for the
schematic + PCB + footprints; the `RP-006442` archive is the QFN-80 (RP2350B) variant.

70
hardware/PROTOTYPE.md Normal file
View file

@ -0,0 +1,70 @@
# PM_K-1 — Phase-1 Bench Prototype
Prove the **click → codec → transformer-isolated XLR** chain (and the all-important *no-buzz*
test) on bought boards **before** committing any custom PCB. Keeps the RP2350 firmware. This is
Phase 1 of the approved plan (`deep-honking-lemon`). Firmware lives in `pico-wm8960/code.py`.
## Why this shape (recap)
- The brain (MCU + codec + Bluetooth) is a commodity → buy it.
- The only genuinely-custom hardware is the **Pro Out** stage (line driver → output transformer
→ XLR) + the face → build only that, later.
- The past "buzz" was a **ground loop**; the cure is **galvanic isolation** (a transformer), not
±15 V. ±15 V was only studio headroom and is gone.
## Shopping list (~$4080)
| Item | Why | ~$ |
|---|---|---|
| **Raspberry Pi Pico 2** (RP2350) | Keeps the existing RP2350 firmware/toolchain | 5 |
| **SparkFun WM8960 Audio Codec Breakout** | Stereo codec + HP amp + 1 W class-D speaker; **has an onboard 24 MHz oscillator → supplies the WM8960 MCLK** (resolves Risk R1), and the `adafruit_wm8960` driver is tested against exactly this board | 15 |
| **Output transformer** | The buzz-killer: galvanic isolation + balancing. Bench-start with a budget 1:1 600:600 Ω line transformer (Triad/Würth/Bourns); evaluate **Jensen JT-11** / Lundahl for the keeper. **Pick for full-level low-frequency handling** — a punchy click is transient-rich and small cores saturate/dull the thump | 1050 |
| **Male XLR chassis connector** (Neutrik) + optional ¼" TRS | Stadium-standard, locking, unambiguous output | 5 |
| **Audio op-amp** (OPA1612 etc.) + breadboard/protoboard, jumpers | Low-rail line driver into the transformer primary | 5 |
| *(later)* **Microchip BM83 dev board** | Bluetooth leg (A2DP sink+source) for M5 | 3040 |
| Measurement: USB audio interface + REW (free), or a phone analyzer app | Check output level + LF response (M3) | — |
## Wiring
**Pico 2 ↔ WM8960 breakout (digital):** match `pico-wm8960/code.py`.
| Pico 2 | WM8960 | Note |
|---|---|---|
| GP0 | BCLK | bit clock |
| GP1 | DACLRC (+ tie ADCLRC) | word/LR clock — must be GP(bit_clock)+1 |
| GP2 | DACDAT | Pico transmits the click |
| GP4 | SDA | Qwiic I²C |
| GP5 | SCL | Qwiic I²C |
| 3V3 / GND | 3V3 / GND | power the breakout |
**Analog out (Pro Out, breadboard):** WM8960 line/HP out → op-amp line driver (low rail) →
**transformer primary**; transformer **secondary** → XLR pin 2 (hot) / pin 3 (cold) / pin 1
(shield, tied to chassis). Optionally also a ¼" TRS off the same secondary (tip/ring/sleeve).
The transformer's secondary floats — that floating, isolated output is what cannot form a ground
loop.
## Firmware
1. Flash **CircuitPython** (9.x+) onto the Pico 2.
2. `circup install adafruit_wm8960` (or copy `adafruit_wm8960/` into `CIRCUITPY/lib`).
3. Copy `pico-wm8960/code.py` to `CIRCUITPY/code.py`. It boots a steady 120 BPM click for M1.
## Bench validation milestones
- **M1 — click out:** the synthesized click plays cleanly via the WM8960 on headphones / speaker.
- **M2 — mix:** your guitar on the line input is heard *with* the click (enable the codec's
analog input→output bypass for ~0 ms monitor latency — see the M2 hook in `code.py`).
- **M3 — pro output:** the transformer-isolated XLR drives a real console/interface at usable
level, and **the click keeps its punch** — measure the low-frequency response through the
transformer (this is where a too-small transformer shows up as a thin, gutless click).
- **M4 — the acid test (NO BUZZ):** power the rig from a **mains** USB charger **and** connect the
XLR to a **mains-powered** console/interface. Confirm **no hum**. A/B against a non-isolated
output (tap before the transformer) to prove the transformer is what kills the loop. **This is
the acceptance gate for the whole approach.**
- **M5 (later):** add the BM83 — phone audio in (A2DP sink) mixes with the click; click out to a
Bluetooth speaker (A2DP source).
## Then → Phase 2 (only if M1M4 pass)
Design the **Pro Out module** PCB (op-amp → transformer → XLR) and optionally a custom RP2350 +
WM8960 brain, using the existing SKiDL designs as reference. Not before the bench proves it.
## Open risks to watch
- **Transformer LF/level** (M3) — the "rock the house" punch lives or dies here.
- **WM8960 MCLK** — relying on the SparkFun breakout's onboard 24 MHz osc; a bare WM8960 needs
you to supply MCLK.
- **CircuitPython I²S/mixer throughput** on the Pico 2 — if it can't keep up at 44.1 k, fall back
to the C/Rust path.

95
hardware/REFERENCES.md Normal file
View file

@ -0,0 +1,95 @@
# PM_K-1 Core Board — Reference Materials
Every datasheet and reference behind the design, with the **verified facts** extracted during
capture (pinouts, key specs) so this doc stands alone. PDFs are copyrighted, so they are **not
committed** — they live git-ignored in `hardware/datasheets/` (regenerate by downloading from
the URLs below). Pinouts here were confirmed against these sources during schematic capture.
**Download status:** ✓ = saved in `hardware/datasheets/` · ⛔ = manufacturer site blocks
automated download (open the URL in a browser) · 🔁 = host dropped the connection, retry.
> Datasheet hosts that allow direct download (used here): TI `ti.com/lit`, `thatcorp.com`,
> `datasheets.raspberrypi.com`, octopart's CDN, Adafruit's CDN. Diodes/ST/onsemi/Winbond/Micro
> Crystal block automated fetches — their URLs are below for manual download.
---
## MCU & digital
| Part | Role | Doc | URL | Local | Verified |
|---|---|---|---|---|---|
| **RP2350A** | MCU (QFN-60) | Hardware Design w/ RP2350 (RP-008280) | https://datasheets.raspberrypi.com/rp2350/hardware-design-with-rp2350.pdf | ✓ `RP2350_hardware-design.pdf` | minimal design: VREG_VIN→VREG_LX→**3.3µH**→DVDD, VREG_AVDD via **33Ω+4.7µF**, 100nF/pin |
| RP2350A | MCU full datasheet | RP2350 Datasheet | https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf | — | pinout via KiCad `MCU_RaspberryPi:RP2350A` symbol |
| **W25Q128JV** | 16MB QSPI flash | Winbond W25Q128JV | https://www.winbond.com/hq/product/code-storage-flash-memory/serial-nor-flash/?__locale=en&partNo=W25Q128JV | ⛔ | SO-8: 1=CS 2=IO1 3=IO2 4=GND 5=IO0 6=CLK 7=IO3 8=VCC (per RP2350 hw-design Fig 8) |
## Power
| Part | Role | Doc | URL | Local | Verified |
|---|---|---|---|---|---|
| **TPS65131** | dual boost/inverter → ±18V | TI SLVS493E | https://www.ti.com/lit/ds/symlink/tps65131.pdf | ✓ `TPS65131.pdf` | 24-VQFN pinout; Vref=1.213V; Vpos=Vref(1+R1/R2), Vneg=Vref·R3/R4; L=4.7µH; D=MBRM120 |
| **TPS7A4901** | +15V ultra-low-noise LDO | TI SBVS121E | https://www.ti.com/lit/ds/symlink/tps7a49.pdf | ✓ `TPS7A4901.pdf` | 8-pin: 1=OUT 2=FB 3=NC 4=GND 5=EN 6=NR/SS 7=DNC 8=IN; Vout=Vfb(1+R1/R2) |
| **TPS7A3001** | 15V ultra-low-noise LDO | TI SBVS125D | https://www.ti.com/lit/ds/symlink/tps7a30.pdf | ✓ `TPS7A3001.pdf` | same 8-pin pinout (negative) |
| **AP2112K-3.3** | 3V3 LDO | Diodes AP2112 | https://www.diodes.com/assets/Datasheets/AP2112.pdf | ⛔ | SOT-23-5: 1=VIN 2=GND 3=EN 4=NC 5=VOUT (confirm) |
## Audio chain
| Part | Role | Doc | URL | Local | Verified |
|---|---|---|---|---|---|
| **THAT1240** | balanced line receiver (0 dB) | THAT 600035 rev05 | https://thatcorp.com/datashts/THAT_1240-Series_Datasheet.pdf | ✓ `THAT1240-43-46_receiver.pdf` | SO-8: 1=Ref 2=In 3=In+ 4=Vee 5=Sense 6=Vout 7=Vcc 8=NC; gains 1240=0dB/1243=3/1246=6; 2nd-src INA134/SSM2141 |
| **THAT1646** | balanced line driver (+6 dB) | THAT 600078 rev07 | https://thatcorp.com/datashts/THAT_1606-1646_Datasheet.pdf | ✓ `THAT1606-1646_driver.pdf` | SO-8: 1=Out 2=Sns 3=Gnd 4=In 5=Vee 6=Vcc 7=Sns+ 8=Out+; 418V; 2nd-src DRV134/SSM2142 |
| **OPA1641** | JFET Hi-Z DI buffer | TI SBOS484D | https://www.ti.com/lit/ds/symlink/opa1641.pdf | ✓ `OPA1641-42-44.pdf` | SO-8: 1=NC 2=IN 3=+IN 4=V 5=NC 6=OUT 7=V+ 8=NC; ±2.2518V |
| **OPA1612** | dual op-amp (filter + summer) | TI SBOS450 | https://www.ti.com/lit/ds/symlink/opa1612.pdf | ⛔ (TI) | standard dual pinout: 1=OUTA 2=INA 3=+INA 4=V 5=+INB 6=INB 7=OUTB 8=V+ (confirmed via OPA164x sheet) |
| **PCM5102A** | I²S DAC (click) | TI SLAS859C | https://www.ti.com/lit/ds/symlink/pcm5102a.pdf | ✓ `PCM5102A.pdf` | TSSOP-20 pinout; 2.1Vrms GND-centered; **MCLK-less: SCK→GND** (internal PLL) |
## Indicators / MIDI / speaker (DNP)
| Part | Role | Doc | URL | Local | Verified |
|---|---|---|---|---|---|
| **LM393** | SIG/CLIP comparator | TI | https://www.ti.com/lit/ds/symlink/lm393.pdf | ⛔ (TI) | standard 8-pin: 1=OUT1 2=IN1 3=+IN1 4=V 5=+IN2 6=IN2 7=OUT2 8=V+ |
| **H11L1** | MIDI opto-isolator IN | onsemi H11L1M | https://www.onsemi.com/pdf/datasheet/h11l1m-d.pdf | ⛔ | 6-pin: 1=A 2=C 3=NC 4=GND 5=VO 6=VCC (standard; reconfirm) |
| **74LVC14** | MIDI OUT/THRU buffer | TI SN74LVC14A | https://www.ti.com/lit/ds/symlink/sn74lvc14a.pdf | ⛔ (TI) | hex Schmitt inverter, standard 14-pin (1A/1Y…6A/6Y, 7=GND, 14=VCC) |
| **PAM8302A** | monitor speaker amp | Diodes DS41333 | https://www.diodes.com/assets/Datasheets/PAM8302A.pdf | ✓ `PAM8302A.pdf` (via Adafruit CDN) | SO-8: 1=SD 2=NC 3=IN+ 4=IN 5=VO+ 6=VDD 7=GND 8=VO; gain=20·log(150k/(10k+RIN)) |
## RTC / relays / connectors / protection
| Part | Role | Doc | URL | Local | Verified |
|---|---|---|---|---|---|
| **RV-8803-C7** | I²C RTC | Micro Crystal (App Manual rev 1.6) | https://www.microcrystal.com/fileadmin/Media/Products/RTC/Datasheet/RV-8803-C7.pdf · App: https://www.microcrystal.com/fileadmin/Media/Products/RTC/App.Manual/RV-8803-C7_App-Manual.pdf | 🔁 | 8-WCDFN: 1=SDA 2=CLKOUT 3=VDD 4=CLKOE 5=VSS 6=/INT 7=EVI 8=SCL; single VDD; ~240nA |
| **ULN2003A** | relay driver | TI SLRS027T | https://www.ti.com/lit/ds/symlink/uln2003a.pdf | ✓ `ULN2003A.pdf` | in 1B7B=17, **GND=8, COM=9**, out 7C1C=1016 |
| **TQ2SA-5V** | DPDT signal relay (×3) | Panasonic TQ-SMD | octopart CDN: https://datasheet.octopart.com/TQ2SA-5V-Panasonic-datasheet-18347.pdf · mfr: https://industrial.panasonic.com/ww/products/pt/tq | ✓ `TQ2SA_TQ-SMD_relay.pdf` | coil 1/10; pole1 COM=3 NC=4 NO=2; pole2 COM=8 NC=7 NO=9; single-side-stable, gold |
| **USBLC6-2SC6** | USB ESD | ST | https://www.st.com/resource/en/datasheet/usblc6-2.pdf | ⛔ | SOT-23-6: I/O1=1&6, GND=2, I/O2=3&4, VBUS=5 |
| **USB-C receptacle** | USB2.0 conn (GCT USB4085) | GCT | https://gct.co/files/drawings/usb4085.pdf | ⛔ | USB2.0 subset: VBUS, GND, CC1/CC2, D+/D, SHIELD (per KiCad `USB_C_Receptacle`) |
## Passives & discretes
| Part | Role | URL | Local |
|---|---|---|---|
| **ABM8 12 MHz** crystal | RP2350 clock | https://abracon.com/Resonators/ABM8.pdf | ⛔ |
| **Würth 7447789004** / EPCOS B82462-G4472 (4.7µH) | switcher inductors | https://www.we-online.com/components/products/datasheet/7447789004.pdf | ⛔ |
| **Abracon AOTA-B201610S3R3** (3.3µH) | RP2350 core SMPS inductor | https://abracon.com/Magnetics/AOTA.pdf | ⛔ |
| **MBRM120** | Schottky rectifier (switcher) | https://www.onsemi.com/pdf/datasheet/mbrm120-d.pdf | ⛔ |
| **BAT54** | Schottky (RTC diode-OR, peak-detect) | https://www.onsemi.com/pdf/datasheet/bat54lt1-d.pdf | ⛔ |
| **1N4148WS** | fast diode (input clamps, MIDI) | https://www.onsemi.com/pdf/datasheet/1n4148ws-d.pdf | ⛔ |
| **Bourns 3296W** | output level-cal trimmer | https://www.bourns.com/docs/Product-Datasheets/3296.pdf | ⛔ |
| **Keystone 1066** | CR2032 holder | https://www.keyelco.com/userAssets/file/M65p23.pdf | ⛔ |
| Murata BLM18 / Würth | ferrite bead (USB VBUS) | https://www.murata.com/products/productdetail?partno=BLM18KG121SN1 | ⛔ |
---
## Tools used (open-source)
- **KiCad 9** — schematic/PCB + symbol libs (the RP2350A, USB-C, header symbols came from here).
https://www.kicad.org/ · docs: https://docs.kicad.org/
- **SKiDL** — code-defined schematics (our `hardware/eda/circuits/*.py`). https://devbisme.github.io/skidl/
- **ngspice** — analog simulation (`hardware/eda/sim/*.cir`). https://ngspice.sourceforge.io/
- Pinned in `hardware/eda/Containerfile` (reproducible).
## Standards / app-notes referenced
- **MIDI electrical spec** (incl. 3.3 V TRS Type-A): https://midi.org/specs
- USB-C / USB 2.0 device (CC pulldowns, D± 90 Ω diff pair): https://www.usb.org/documents
- Balanced-audio interfacing (CMRR, build-out, ground-lift): THAT Corp app notes
https://thatcorp.com/Audio_Design_Resources.php
*Verification policy: every IC pinout above was confirmed against its primary datasheet at
capture time (see commit history / per-block files), except where marked "standard" /
"confirm" — those use the universal pinout for the part and should get a final datasheet
cross-check at layout.*

3
hardware/datasheets/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Datasheets are copyrighted -- kept locally, never committed/pushed.
*
!.gitignore

View file

@ -0,0 +1,45 @@
# Reproducible EDA toolchain for the PM_K-1 core board.
#
# Why this exists: the system KiCad is 7.0 (no CLI ERC). This pins a known,
# rebuildable environment so the design can be checked/simulated identically
# years from now — fitting for a device meant to outlive its tools.
#
# KiCad 9 -> schematic capture, CLI ERC/DRC, netlist/PDF/Gerber export
# ngspice -> SPICE simulation of the analog audio circuits
# python3 -> scripting / BOM / skidl-style helpers
#
# Build/run via ../eda/run.sh (or: podman build -t pmk1-eda:9.0 .)
FROM docker.io/library/ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN apt-get update && apt-get install -y --no-install-recommends \
software-properties-common ca-certificates gnupg && \
add-apt-repository -y ppa:kicad/kicad-9.0-releases && \
apt-get update && apt-get install -y --no-install-recommends \
kicad \
kicad-symbols \
kicad-footprints \
ngspice \
python3 python3-pip python3-venv \
git make && \
apt-get clean && rm -rf /var/lib/apt/lists/* && \
pip3 install --no-cache-dir --break-system-packages skidl
# netlistsvg: render the SKiDL netlist as a (browsable) schematic SVG
RUN apt-get update && apt-get install -y --no-install-recommends nodejs npm && \
npm install -g netlistsvg && \
npm cache clean --force && apt-get clean && rm -rf /var/lib/apt/lists/*
# gnuplot: render ngspice simulation results as PNG plots
RUN apt-get update && apt-get install -y --no-install-recommends gnuplot-nox && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Point SKiDL / KiCad CLI at the installed libraries (reproducible, not ad-hoc).
ENV KICAD9_SYMBOL_DIR=/usr/share/kicad/symbols \
KICAD9_FOOTPRINT_DIR=/usr/share/kicad/footprints \
KICAD_SYMBOL_DIR=/usr/share/kicad/symbols
WORKDIR /work
CMD ["bash"]

36
hardware/eda/README.md Normal file
View file

@ -0,0 +1,36 @@
# PM_K-1 EDA environment
A reproducible container with the tools to design, check, and simulate the core board —
so the work doesn't depend on whatever happens to be installed on a given machine, now or
in 50 years.
## What's inside
- **KiCad 9** — schematic capture + PCB layout, and a CLI (`kicad-cli`) that can run
**ERC** (Electrical Rules Check) and DRC, and export netlists/PDF/Gerbers.
- **ngspice** — SPICE simulator for validating the analog audio circuits before we commit
copper (op-amp stages, filters, input loading, etc.).
- **python3** — scripting, BOM munging, optional code-defined-schematic helpers.
## Why a container?
The system KiCad here is 7.0, whose CLI can't run ERC (that arrived in KiCad 8). Rather than
fight the host, we pin a known toolchain. Anyone — including future-you — rebuilds the exact
environment with one command.
## Use it
```bash
cd hardware/eda
./run.sh # interactive shell, lands in hardware/kicad/
./run.sh kicad-cli version # confirm KiCad 9
./run.sh kicad-cli sch erc pm_k1_core.kicad_sch # run ERC on the schematic
./run.sh ngspice -b ../eda/sim/input_loading.cir # run a simulation (cwd is kicad/)
```
`run.sh` builds the image on first use, then mounts the whole repo at `/work` (so KiCad sees
`hardware/`). Use `RUNTIME=docker ./run.sh …` to use Docker instead of Podman.
## Layout
```
eda/
Containerfile # the pinned toolchain (KiCad 9 + ngspice + python)
run.sh # build-if-needed + run with the repo mounted
sim/ # ngspice decks (SPICE simulations of the analog circuits)
```

View file

@ -0,0 +1,195 @@
#!/usr/bin/env python3
"""PM_K-1 audio chain - INTEGRATED board netlist (SKiDL).
Wires stages 1, 1b, 2, 3, 4 into one netlist with shared nets and DEDUPLICATED parts:
* the Stage-2 reconstruction filter and the Stage-3 summer share ONE OPA1612 dual
(U4: section A = filter, section B = summer) -- no parked half.
* the three relays (K1 select, K2 mute, K3 ground-lift) share ONE ULN2003 (U6).
This is the source of truth for the integrated audio netlist; the per-stage circuits/
files remain as the documented, individually-simulated building blocks.
Run INSIDE the EDA container:
cd hardware/eda && ./run.sh python3 ../eda/circuits/audio_chain.py
Outputs ERC + hardware/kicad/audio_chain.net.
All IC pinouts datasheet-verified (see the per-stage files for the citations):
THAT1240 1=Ref 2=In- 3=In+ 4=Vee 5=Sns 6=Vout 7=Vcc 8=NC
OPA1641 1=NC 2=-IN 3=+IN 4=V- 5=NC 6=OUT 7=V+ 8=NC
OPA1612 1=OUTA 2=-INA 3=+INA 4=V- 5=+INB 6=-INB 7=OUTB 8=V+
PCM5102A (TSSOP-20) per TI SLAS859C
THAT1646 1=Out- 2=Sns- 3=Gnd 4=In 5=Vee 6=Vcc 7=Sns+ 8=Out+
TQ2SA coil 1/10; pole1 COM3/NC4/NO2; pole2 COM8/NC7/NO9
ULN2003A in 1B-7B=1..7, out 1C-7C=16..10, GND=8, COM=9
"""
import os
from skidl import *
set_default_tool(KICAD9)
R = Part("Device", "R", dest=TEMPLATE, footprint="Resistor_SMD:R_0805_2012Metric")
C = Part("Device", "C", dest=TEMPLATE, footprint="Capacitor_SMD:C_0805_2012Metric")
D = Part("Device", "D", dest=TEMPLATE, footprint="Diode_SMD:D_SOD-323")
# ---------------- nets ----------------
p15, n15, p3v3, p5, gnd = Net("+15V"), Net("-15V"), Net("+3V3"), Net("+5V"), Net("GND")
for n in (p15, n15, p3v3, p5, gnd):
n.drive = POWER
# audio signal flow
ain_hot, ain_cold = Net("AIN_HOT"), Net("AIN_COLD") # input jack tip/ring
rx_hot_in = Net("RX_HOT_IN") # relay-routed hot -> receiver
rx_out, di_in, di_out = Net("RX_OUT"), Net("DI_IN"), Net("DI_OUT")
stage1_out, click_out, mix_out = Net("STAGE1_OUT"), Net("CLICK_OUT"), Net("MIX_OUT")
aout_hot, aout_cold, chassis = Net("AOUT_HOT"), Net("AOUT_COLD"), Net("CHASSIS")
# DAC clocks/data + control
mclk, i2s_bck, i2s_din, i2s_lrck = Net("MCLK"), Net("I2S_BCK"), Net("I2S_DIN"), Net("I2S_LRCK")
dac_xsmt = Net("DAC_XSMT")
sel_linst, mute_en, gndlift_en = Net("SEL_LINST"), Net("MUTE_EN"), Net("GNDLIFT_EN")
k1_drv, k2_drv, k3_drv = Net("K1_DRV"), Net("K2_DRV"), Net("K3_DRV")
# ---------------- part definitions ----------------
def mk(name, pins, ref_prefix="U", fp="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm"):
return Part(name=name, tool=SKIDL, dest=TEMPLATE, ref_prefix=ref_prefix, footprint=fp, pins=pins)
P = Pin.types
THAT1240 = mk("THAT1240", [Pin(num=1,name="REF",func=P.INPUT),Pin(num=2,name="IN-",func=P.INPUT),
Pin(num=3,name="IN+",func=P.INPUT),Pin(num=4,name="V-",func=P.PWRIN),Pin(num=5,name="SENSE",func=P.PASSIVE),
Pin(num=6,name="OUT",func=P.OUTPUT),Pin(num=7,name="V+",func=P.PWRIN),Pin(num=8,name="NC",func=P.NOCONNECT)])
OPA1641 = mk("OPA1641", [Pin(num=1,name="NC1",func=P.NOCONNECT),Pin(num=2,name="-IN",func=P.INPUT),
Pin(num=3,name="+IN",func=P.INPUT),Pin(num=4,name="V-",func=P.PWRIN),Pin(num=5,name="NC5",func=P.NOCONNECT),
Pin(num=6,name="OUT",func=P.OUTPUT),Pin(num=7,name="V+",func=P.PWRIN),Pin(num=8,name="NC8",func=P.NOCONNECT)])
OPA1612 = mk("OPA1612", [Pin(num=1,name="OUTA",func=P.OUTPUT),Pin(num=2,name="-INA",func=P.INPUT),
Pin(num=3,name="+INA",func=P.INPUT),Pin(num=4,name="V-",func=P.PWRIN),Pin(num=5,name="+INB",func=P.INPUT),
Pin(num=6,name="-INB",func=P.INPUT),Pin(num=7,name="OUTB",func=P.OUTPUT),Pin(num=8,name="V+",func=P.PWRIN)])
THAT1646 = mk("THAT1646", [Pin(num=1,name="OUT-",func=P.OUTPUT),Pin(num=2,name="SNS-",func=P.INPUT),
Pin(num=3,name="GND",func=P.PWRIN),Pin(num=4,name="IN",func=P.INPUT),Pin(num=5,name="V-",func=P.PWRIN),
Pin(num=6,name="V+",func=P.PWRIN),Pin(num=7,name="SNS+",func=P.INPUT),Pin(num=8,name="OUT+",func=P.OUTPUT)])
PCM5102A = mk("PCM5102A", [Pin(num=1,name="CPVDD",func=P.PWRIN),Pin(num=2,name="CAPP",func=P.PASSIVE),
Pin(num=3,name="CPGND",func=P.PWRIN),Pin(num=4,name="CAPM",func=P.PASSIVE),Pin(num=5,name="VNEG",func=P.PASSIVE),
Pin(num=6,name="OUTL",func=P.OUTPUT),Pin(num=7,name="OUTR",func=P.OUTPUT),Pin(num=8,name="AVDD",func=P.PWRIN),
Pin(num=9,name="AGND",func=P.PWRIN),Pin(num=10,name="DEMP",func=P.INPUT),Pin(num=11,name="FLT",func=P.INPUT),
Pin(num=12,name="SCK",func=P.INPUT),Pin(num=13,name="BCK",func=P.INPUT),Pin(num=14,name="DIN",func=P.INPUT),
Pin(num=15,name="LRCK",func=P.INPUT),Pin(num=16,name="FMT",func=P.INPUT),Pin(num=17,name="XSMT",func=P.INPUT),
Pin(num=18,name="LDOO",func=P.PWROUT),Pin(num=19,name="DGND",func=P.PWRIN),Pin(num=20,name="DVDD",func=P.PWRIN)],
fp="Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm")
TQ2SA = mk("TQ2SA-5V", [Pin(num=1,name="COIL_A",func=P.PASSIVE),Pin(num=10,name="COIL_B",func=P.PASSIVE),
Pin(num=3,name="P1_COM",func=P.PASSIVE),Pin(num=4,name="P1_NC",func=P.PASSIVE),Pin(num=2,name="P1_NO",func=P.PASSIVE),
Pin(num=8,name="P2_COM",func=P.PASSIVE),Pin(num=7,name="P2_NC",func=P.PASSIVE),Pin(num=9,name="P2_NO",func=P.PASSIVE),
Pin(num=5,name="NC5",func=P.NOCONNECT),Pin(num=6,name="NC6",func=P.NOCONNECT)],
ref_prefix="K", fp="Relay_SMD:Relay_DPDT_Panasonic_TQ2-SA")
ULN2003 = mk("ULN2003A", [Pin(num=1,name="1B",func=P.INPUT),Pin(num=2,name="2B",func=P.INPUT),
Pin(num=3,name="3B",func=P.INPUT),Pin(num=4,name="4B",func=P.INPUT),Pin(num=5,name="5B",func=P.INPUT),
Pin(num=6,name="6B",func=P.INPUT),Pin(num=7,name="7B",func=P.INPUT),Pin(num=8,name="GND",func=P.PWRIN),
Pin(num=9,name="COM",func=P.PWRIN),Pin(num=10,name="7C",func=P.OPENCOLL),Pin(num=11,name="6C",func=P.OPENCOLL),
Pin(num=12,name="5C",func=P.OPENCOLL),Pin(num=13,name="4C",func=P.OPENCOLL),Pin(num=14,name="3C",func=P.OPENCOLL),
Pin(num=15,name="2C",func=P.OPENCOLL),Pin(num=16,name="1C",func=P.OPENCOLL)],
fp="Package_SO:SOIC-16_3.9x9.9mm_P1.27mm")
# instances (deduplicated)
u1 = THAT1240(ref="U1") # line receiver
u2 = OPA1641(ref="U2") # Hi-Z DI buffer
u3 = PCM5102A(ref="U3") # DAC
u4 = OPA1612(ref="U4") # A = recon filter, B = summer (shared dual)
u5 = THAT1646(ref="U5") # balanced driver
u6 = ULN2003(ref="U6") # shared relay driver
k1, k2, k3 = TQ2SA(ref="K1"), TQ2SA(ref="K2"), TQ2SA(ref="K3")
def dcp(rail): # 100nF decoupling helper
c = C(value="100nF"); rail += c[1]; c[2] += gnd
# ---------------- Stage 1: balanced line receiver + protection ----------------
def protect_bal(src, tag):
cblk = C(value="2.2uF", footprint="Capacitor_SMD:C_1206_3216Metric")
rs, rb = R(value="1k"), R(value="1Meg")
dp, dn = D(value="1N4148WS"), D(value="1N4148WS")
node = Net(tag)
src += cblk[1]; cblk[2] += rs[1]; rs[2] += node
rb[1] += node; rb[2] += gnd
dp[1] += p15; dp[2] += node # Device:D pin1=K,pin2=A : high clamp
dn[1] += node; dn[2] += n15 # low clamp
return node
u1["IN+"] += protect_bal(rx_hot_in, "RXIN_P") # hot comes from the select relay (NC)
u1["IN-"] += protect_bal(ain_cold, "RXIN_N") # cold is the ring (direct)
u1["REF"] += gnd; u1["SENSE"] += rx_out; u1["OUT"] += rx_out
u1["V+"] += p15; u1["V-"] += n15; dcp(p15); dcp(n15)
# ---------------- Stage 1b: Hi-Z DI buffer (OPA1641) ----------------
cblk = C(value="100nF", footprint="Capacitor_SMD:C_1206_3216Metric")
rbias = R(value="1Meg"); dp, dn = D(value="1N4148WS"), D(value="1N4148WS")
di_node = Net("DI_NODE")
di_in += cblk[1]; cblk[2] += di_node
rbias[1] += di_node; rbias[2] += gnd
dp[1] += p15; dp[2] += di_node
dn[1] += di_node; dn[2] += n15
u2["+IN"] += di_node
rf2, rg2 = R(value="3k"), R(value="1k") # +12 dB
u2["OUT"] += di_out
rf2[1] += di_out; rf2[2] += u2["-IN"]
rg2[1] += u2["-IN"]; rg2[2] += gnd
u2["V+"] += p15; u2["V-"] += n15; dcp(p15); dcp(n15)
# ---------------- Stage 1b: select relay K1 ----------------
k1["P1_COM"] += ain_hot; k1["P1_NC"] += rx_hot_in; k1["P1_NO"] += di_in # route tip
k1["P2_COM"] += stage1_out; k1["P2_NC"] += rx_out; k1["P2_NO"] += di_out # select output
k1["COIL_A"] += p5; k1["COIL_B"] += k1_drv
# ---------------- Stage 2: PCM5102A DAC ----------------
for pin in ("AVDD","CPVDD","DVDD"): u3[pin] += p3v3
for pin in ("AGND","DGND","CPGND"): u3[pin] += gnd
for pin in ("AVDD","CPVDD","DVDD"):
c = C(value="100nF"); u3[pin] += c[1]; c[2] += gnd
cb = C(value="10uF", footprint="Capacitor_SMD:C_1206_3216Metric"); u3["AVDD"] += cb[1]; cb[2] += gnd
cfly = C(value="2.2uF"); u3["CAPP"] += cfly[1]; u3["CAPM"] += cfly[2]
cvneg = C(value="2.2uF"); u3["VNEG"] += cvneg[1]; cvneg[2] += gnd
cldoo = C(value="1uF"); u3["LDOO"] += cldoo[1]; cldoo[2] += gnd
u3["DEMP"] += gnd; u3["FLT"] += gnd; u3["FMT"] += gnd
u3["XSMT"] += dac_xsmt; rpu = R(value="10k"); dac_xsmt += rpu[1]; rpu[2] += p3v3
u3["SCK"] += mclk; u3["BCK"] += i2s_bck; u3["DIN"] += i2s_din; u3["LRCK"] += i2s_lrck
rload = R(value="2.2k"); u3["OUTR"] += rload[1]; rload[2] += gnd
# ---------------- Stage 2: reconstruction filter = OPA1612 section A ----------------
r1, r2 = R(value="1.5k"), R(value="1.5k"); ca = C(value="2.2nF"); cbq = C(value="1nF")
rcA = Net("RC_A")
u3["OUTL"] += r1[1]; r1[2] += rcA
r2[1] += rcA; r2[2] += u4["+INA"]
ca[1] += rcA; ca[2] += u4["OUTA"]
cbq[1] += u4["+INA"]; cbq[2] += gnd
u4["-INA"] += u4["OUTA"]; u4["OUTA"] += click_out
u4["V+"] += p15; u4["V-"] += n15; dcp(p15); dcp(n15)
# ---------------- Stage 3: summer = OPA1612 section B (shared chip) ----------------
ri_in, ri_clk, rf3 = R(value="10k"), R(value="10k"), R(value="10k")
stage1_out += ri_in[1]; ri_in[2] += u4["-INB"]
click_out += ri_clk[1]; ri_clk[2] += u4["-INB"]
rf3[1] += u4["-INB"]; rf3[2] += u4["OUTB"]
u4["+INB"] += gnd; u4["OUTB"] += mix_out
# ---------------- Stage 4: level trim + THAT1646 + build-out ----------------
RV = Part("Device","R_Potentiometer", dest=TEMPLATE,
footprint="Potentiometer_THT:Potentiometer_Bourns_3296W_Vertical")
rv1 = RV(value="10k", ref="RV1")
mix_out += rv1[1]; rv1[3] += gnd; rv1[2] += u5["IN"]
u5["SNS-"] += u5["OUT-"]; u5["SNS+"] += u5["OUT+"]; u5["GND"] += gnd
u5["V+"] += p15; u5["V-"] += n15; dcp(p15); dcp(n15)
rbo_h, rbo_c = R(value="47"), R(value="47")
u5["OUT-"] += rbo_h[1]; rbo_h[2] += aout_hot # phase-corrected: HOT<-OUT-
u5["OUT+"] += rbo_c[1]; rbo_c[2] += aout_cold # COLD<-OUT+
# ---------------- Stage 4: mute relay K2 + ground-lift relay K3 ----------------
k2["P1_COM"] += aout_hot; k2["P1_NC"] += gnd # de-energized = muted (short to gnd)
k2["P2_COM"] += aout_cold; k2["P2_NC"] += gnd
k2["COIL_A"] += p5; k2["COIL_B"] += k2_drv
k3["P1_COM"] += gnd; k3["P1_NC"] += chassis # de-energized = bonded
rlift = R(value="100"); clift = C(value="10nF")
rlift[1] += gnd; rlift[2] += chassis; clift[1] += gnd; clift[2] += chassis
k3["COIL_A"] += p5; k3["COIL_B"] += k3_drv
# ---------------- shared relay driver U6 (ULN2003): 3 channels ----------------
u6["1B"] += sel_linst; u6["1C"] += k1_drv
u6["2B"] += mute_en; u6["2C"] += k2_drv
u6["3B"] += gndlift_en; u6["3C"] += k3_drv
u6["GND"] += gnd; u6["COM"] += p5 # flyback common to coil supply
ERC()
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "audio_chain.net"))
generate_netlist(file_=out)
print("Integrated audio netlist ->", out)

View file

@ -0,0 +1,319 @@
#!/usr/bin/env python3
"""PM_K-1 FULL BOARD (SKiDL): the whole core board as one netlist for PCB layout.
Run INSIDE the EDA container:
cd hardware/eda && ./run.sh python3 ../eda/circuits/board.py
Outputs ERC + hardware/kicad/board.net (the master netlist to import into Pcbnew).
This re-implements every block (power, MCU, audio chain, RTC, MIDI-DNP, interconnects,
SIG/CLIP-DNP, speaker-DNP) with SHARED net objects and NO forced reference designators --
SKiDL auto-numbers them, so refs are unique board-wide (the per-block files have colliding
refs by design; this file is the single integrated source of truth, like audio_chain.py is
for the audio stages). All IC pinouts datasheet-verified in the per-block files.
MCLK-LESS: the PCM5102A SCK is tied to GND (internal-PLL operation) -- no audio oscillator.
"""
import os
from skidl import *
set_default_tool(KICAD9)
P = Pin.types
def mk(name, pins, ref="U", fp="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm"):
return Part(name=name, tool=SKIDL, dest=TEMPLATE, ref_prefix=ref, footprint=fp, pins=pins)
R = Part("Device","R", dest=TEMPLATE, footprint="Resistor_SMD:R_0402_1005Metric")
def C(v, fp="Capacitor_SMD:C_0402_1005Metric"): return Part("Device","C", value=v, footprint=fp)
D = Part("Device","D", dest=TEMPLATE, footprint="Diode_SMD:D_SOD-323")
DS = Part("Device","D_Schottky", dest=TEMPLATE, footprint="Diode_SMD:D_SOD-323")
L = Part("Device","L", dest=TEMPLATE, footprint="Inductor_SMD:L_1210_3225Metric") # placeholder; set per actual inductor (Wuerth/EPCOS/Abracon) at layout
def grp(n): g = Group(n); g.__enter__(); return g # tag a block as a "sheet" -> KiCad select-by-block
def endgrp(g): g.__exit__(None, None, None)
# ============================ shared nets ============================
vbus,p5,p18,n18,p15,n15,p3v3,dvdd,gnd = (Net("VBUS_5V"),Net("+5V"),Net("+18V"),Net("-18V"),
Net("+15V"),Net("-15V"),Net("+3V3"),Net("DVDD"),Net("GND"))
for nn in (vbus,p5,p18,n18,p15,n15,p3v3,dvdd): nn.drive = POWER
gnd.drive = POWER
vref = Net("VREF")
# audio
ain_hot,ain_cold,rx_hot_in,rx_out,di_in,di_out = (Net("AIN_HOT"),Net("AIN_COLD"),Net("RX_HOT_IN"),
Net("RX_OUT"),Net("DI_IN"),Net("DI_OUT"))
stage1_out,click_out,mix_out,aout_hot,aout_cold,chassis = (Net("STAGE1_OUT"),Net("CLICK_OUT"),
Net("MIX_OUT"),Net("AOUT_HOT"),Net("AOUT_COLD"),Net("CHASSIS"))
# DAC clocks/control (MCLK-less: no MCLK net)
i2s_bck,i2s_lrck,i2s_din,dac_xsmt = Net("I2S_BCK"),Net("I2S_LRCK"),Net("I2S_DIN"),Net("DAC_XSMT")
sel_linst,mute_en,gndlift_en = Net("SEL_LINST"),Net("MUTE_EN"),Net("GNDLIFT_EN")
sig_led,clip_led,gndlift_sw,lineinst_sw = Net("SIG_LED"),Net("CLIP_LED"),Net("GNDLIFT_SW"),Net("LINEINST_SW")
k1d,k2d,k3d = Net("K1_DRV"),Net("K2_DRV"),Net("K3_DRV")
spk_p,spk_n,spk_sd = Net("SPK_P"),Net("SPK_N"),Net("SPK_SD")
# digital ribbon
spi_sck,spi_mosi,lcd_cs,lcd_dc,lcd_rst = Net("SPI_SCK"),Net("SPI_MOSI"),Net("LCD_CS"),Net("LCD_DC"),Net("LCD_RST")
i2c_sda,i2c_scl,ws2812,btn_a,btn_b,joy_x,joy_y = (Net("I2C_SDA"),Net("I2C_SCL"),Net("WS2812"),
Net("BTN_A"),Net("BTN_B"),Net("JOY_X"),Net("JOY_Y"))
# QSPI / clock / USB / MIDI / RTC
qsck,qd0,qd1,qd2,qd3,qcs = (Net("QSPI_SCLK"),Net("QSPI_SD0"),Net("QSPI_SD1"),Net("QSPI_SD2"),Net("QSPI_SD3"),Net("QSPI_SS"))
xin,xout = Net("XIN"),Net("XOUT")
usb_dp_c,usb_dm_c = Net("USB_DP_CONN"),Net("USB_DM_CONN")
midi_tx,midi_rx = Net("MIDI_TX"),Net("MIDI_RX")
mo_a,mo_b,mi_a,mi_b,mt_a,mt_b = (Net("MIDI_OUT_A"),Net("MIDI_OUT_B"),Net("MIDI_IN_A"),Net("MIDI_IN_B"),
Net("MIDI_THRU_A"),Net("MIDI_THRU_B"))
vdd_rtc,rtc_int = Net("VDD_RTC"),Net("RTC_INT")
def dcp(rail, val="100nF"):
c = C(val); rail += c[1]; c[2] += gnd
# ============================ POWER TREE ============================
g = grp("Power")
TPS65131 = mk("TPS65131",[Pin(num=1,name="INP",func=P.PASSIVE),Pin(num=24,name="INP2",func=P.PASSIVE),
Pin(num=2,name="PGND",func=P.PWRIN),Pin(num=3,name="PGND2",func=P.PWRIN),Pin(num=4,name="VIN",func=P.PWRIN),
Pin(num=5,name="INN",func=P.PASSIVE),Pin(num=6,name="INN2",func=P.PASSIVE),Pin(num=7,name="BSW",func=P.OUTPUT),
Pin(num=8,name="ENP",func=P.INPUT),Pin(num=9,name="PSP",func=P.INPUT),Pin(num=10,name="ENN",func=P.INPUT),
Pin(num=11,name="PSN",func=P.INPUT),Pin(num=12,name="NC12",func=P.NOCONNECT),Pin(num=20,name="NC20",func=P.NOCONNECT),
Pin(num=13,name="OUTN",func=P.PASSIVE),Pin(num=14,name="OUTN2",func=P.PASSIVE),Pin(num=15,name="VNEG",func=P.INPUT),
Pin(num=16,name="FBN",func=P.INPUT),Pin(num=17,name="VREF",func=P.PWROUT),Pin(num=18,name="CN",func=P.PASSIVE),
Pin(num=19,name="AGND",func=P.PWRIN),Pin(num=21,name="CP",func=P.PASSIVE),Pin(num=22,name="FBP",func=P.INPUT),
Pin(num=23,name="VPOS",func=P.INPUT),Pin(num=25,name="EP",func=P.PWRIN)],fp="Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.65x2.65mm")
def ldo(name): return mk(name,[Pin(num=1,name="OUT",func=P.PWROUT),Pin(num=2,name="FB",func=P.INPUT),
Pin(num=3,name="NC",func=P.NOCONNECT),Pin(num=4,name="GND",func=P.PWRIN),Pin(num=5,name="EN",func=P.INPUT),
Pin(num=6,name="NR",func=P.PASSIVE),Pin(num=7,name="DNC",func=P.NOCONNECT),Pin(num=8,name="IN",func=P.PWRIN),
Pin(num=9,name="EP",func=P.PWRIN)],fp="Package_SO:HVSSOP-8-1EP_3x3mm_P0.65mm_EP1.57x1.89mm")
AP2112 = mk("AP2112K-3.3",[Pin(num=1,name="VIN",func=P.PWRIN),Pin(num=2,name="GND",func=P.PWRIN),
Pin(num=3,name="EN",func=P.INPUT),Pin(num=4,name="NC",func=P.NOCONNECT),Pin(num=5,name="VOUT",func=P.PWROUT)],
fp="Package_TO_SOT_SMD:SOT-23-5")
sw = TPS65131(); pos = ldo("TPS7A4901")(); neg = ldo("TPS7A3001")(); reg3 = AP2112()
fb1 = L(value="600R"); vbus += fb1[1]; fb1[2] += p5
cbk = C("10uF","Capacitor_SMD:C_1206_3216Metric"); p5 += cbk[1]; cbk[2] += gnd
sw["VIN"]+=p5; sw["PGND"]+=gnd; sw["PGND2"]+=gnd; sw["AGND"]+=gnd; sw["EP"]+=gnd
sw["ENP"]+=p5; sw["ENN"]+=p5; sw["PSP"]+=gnd; sw["PSN"]+=gnd
c1=C("4.7uF"); p5+=c1[1]; c1[2]+=gnd; c2=C("4.7uF"); p5+=c2[1]; c2[2]+=gnd
l1=L(value="4.7uH"); p5+=l1[1]; l1[2]+=Net("SW_BOOST"); swb=l1[2]; sw["INP"]+=swb; sw["INP2"]+=swb
d1=DS(value="MBRM120"); d1[2]+=swb; d1[1]+=p18
c4=C("22uF","Capacitor_SMD:C_1206_3216Metric"); p18+=c4[1]; c4[2]+=gnd; sw["VPOS"]+=p18
r1=R(value="1.4M"); r2=R(value="100k"); c9=C("6.8pF"); p18+=r1[1]; r1[2]+=Net("FBP"); fbp=r1[2]
c9[1]+=p18; c9[2]+=fbp; r2[1]+=fbp; r2[2]+=gnd; sw["FBP"]+=fbp
r7=R(value="100"); c3=C("100nF"); p5+=r7[1]; r7[2]+=Net("INN_F"); innf=r7[2]; c3[1]+=innf; c3[2]+=gnd
sw["INN"]+=innf; sw["INN2"]+=innf
l2=L(value="4.7uH"); swi=Net("SW_INV"); sw["OUTN"]+=swi; sw["OUTN2"]+=swi
l2[1]+=swi; l2[2]+=gnd; d2=DS(value="MBRM120"); d2[1]+=swi; d2[2]+=n18
c5=C("22uF","Capacitor_SMD:C_1206_3216Metric"); n18+=c5[1]; c5[2]+=gnd; sw["VNEG"]+=n18
r3=R(value="1.5M"); r4=R(value="100k"); c10=C("7.5pF"); n18+=r3[1]; r3[2]+=Net("FBN"); fbn=r3[2]
c10[1]+=n18; c10[2]+=fbn; r4[1]+=vref; r4[2]+=fbn; sw["FBN"]+=fbn
sw["VREF"]+=vref; c8=C("220nF"); vref+=c8[1]; c8[2]+=gnd
c7=C("4.7nF"); sw["CP"]+=c7[1]; c7[2]+=gnd; c6=C("10nF"); sw["CN"]+=c6[1]; c6[2]+=gnd
pos["IN"]+=p18; ci=C("1uF"); p18+=ci[1]; ci[2]+=gnd; pos["OUT"]+=p15; co=C("2.2uF"); p15+=co[1]; co[2]+=gnd
pos["GND"]+=gnd; pos["EP"]+=gnd; pos["EN"]+=p18
rtp=R(value="116k"); rbp=R(value="10k"); p15+=rtp[1]; rtp[2]+=pos["FB"]; rbp[1]+=pos["FB"]; rbp[2]+=gnd
cnp=C("10nF"); pos["NR"]+=cnp[1]; cnp[2]+=gnd
neg["IN"]+=n18; cin=C("1uF"); n18+=cin[1]; cin[2]+=gnd; neg["OUT"]+=n15; con=C("2.2uF"); n15+=con[1]; con[2]+=gnd
neg["GND"]+=gnd; neg["EP"]+=gnd; neg["EN"]+=n18
rtn=R(value="117k"); rbn=R(value="10k"); n15+=rtn[1]; rtn[2]+=neg["FB"]; rbn[1]+=neg["FB"]; rbn[2]+=gnd
cnn=C("10nF"); neg["NR"]+=cnn[1]; cnn[2]+=gnd
reg3["VIN"]+=p5; reg3["EN"]+=p5; reg3["GND"]+=gnd; reg3["VOUT"]+=p3v3
ci3=C("1uF"); p5+=ci3[1]; ci3[2]+=gnd; co3=C("1uF"); p3v3+=co3[1]; co3[2]+=gnd
endgrp(g)
# ============================ RP2350 CORE ============================
g = grp("MCU")
rp = Part("MCU_RaspberryPi","RP2350A", footprint="Package_DFN_QFN:QFN-60-1EP_7x7mm_P0.4mm_EP3.4x3.4mm")
for n in (1,11,20,30,38,45): rp[n]+=p3v3
for n in (6,23,39): rp[n]+=dvdd
rp[53]+=p3v3; rp[54]+=p3v3; rp[49]+=p3v3; rp[47]+=gnd; rp[61]+=gnd
lc=L(value="3.3uH"); rp[48]+=lc[1]; lc[2]+=dvdd; rp[50]+=dvdd
cc=C("1uF"); dvdd+=cc[1]; cc[2]+=gnd; cvin=C("4.7uF"); rp[49]+=cvin[1]; cvin[2]+=gnd
rav=R(value="33"); cav=C("4.7uF"); p3v3+=rav[1]; rav[2]+=rp[46]; rp[46]+=cav[1]; cav[2]+=gnd
rad=R(value="0"); cad=C("100nF"); p3v3+=rad[1]; rad[2]+=rp[44]; rp[44]+=cad[1]; cad[2]+=gnd
for _ in range(6): dcp(p3v3)
for _ in range(2): dcp(dvdd)
rp[21]+=xin; rp[22]+=xout
xt=Part("Device","Crystal",value="12MHz",footprint="Crystal:Crystal_SMD_2012-2Pin_2.0x1.2mm") # 2-pin matches the 2-pin symbol (was a 4-pad fp -> Y1 pads 3/4 had no symbol pin). Set to your chosen crystal's footprint; if 4-pad, ground the case pads.
xin+=xt[1]; xout+=xt[2]; cx1=C("15pF"); cx2=C("15pF"); xin+=cx1[1]; cx1[2]+=gnd; xout+=cx2[1]; cx2[2]+=gnd
FL=mk("W25Q128JVS",[Pin(num=1,name="CS",func=P.INPUT),Pin(num=2,name="IO1",func=P.BIDIR),Pin(num=3,name="IO2",func=P.BIDIR),
Pin(num=4,name="GND",func=P.PWRIN),Pin(num=5,name="IO0",func=P.BIDIR),Pin(num=6,name="CLK",func=P.INPUT),
Pin(num=7,name="IO3",func=P.BIDIR),Pin(num=8,name="VCC",func=P.PWRIN)],fp="Package_SO:SOIC-8_5.3x5.3mm_P1.27mm")
fl=FL(); fl["CLK"]+=qsck; fl["IO0"]+=qd0; fl["IO1"]+=qd1; fl["IO2"]+=qd2; fl["IO3"]+=qd3; fl["CS"]+=qcs
fl["VCC"]+=p3v3; fl["GND"]+=gnd; dcp(p3v3)
rp[56]+=qsck; rp[57]+=qd0; rp[59]+=qd1; rp[58]+=qd2; rp[55]+=qd3; rp[60]+=qcs
rbt=R(value="1k"); swb2=Part("Switch","SW_Push",footprint="Button_Switch_SMD:SW_SPST_SKQG_WithStem")
qcs+=rbt[1]; rbt[2]+=swb2[1]; swb2[2]+=gnd
rru=R(value="10k"); swr=Part("Switch","SW_Push",footprint="Button_Switch_SMD:SW_SPST_SKQG_WithStem")
p3v3+=rru[1]; rru[2]+=rp[26]; rp[26]+=swr[1]; swr[2]+=gnd; cru=C("100nF"); rp[26]+=cru[1]; cru[2]+=gnd
swd=Part("Connector_Generic","Conn_01x04",footprint="Connector_PinHeader_1.27mm:PinHeader_1x04_P1.27mm_Vertical")
swd[1]+=p3v3; swd[2]+=rp[25]; swd[3]+=rp[24]; swd[4]+=gnd
rdp=R(value="27"); rdm=R(value="27"); rp[52]+=rdp[1]; rdp[2]+=usb_dp_c; rp[51]+=rdm[1]; rdm[2]+=usb_dm_c
rp["GPIO2"]+=spi_sck; rp["GPIO3"]+=spi_mosi; rp["GPIO5"]+=lcd_cs; rp["GPIO6"]+=lcd_dc; rp["GPIO7"]+=lcd_rst
rp["GPIO8"]+=i2c_sda; rp["GPIO9"]+=i2c_scl; rp["GPIO10"]+=i2s_bck; rp["GPIO11"]+=i2s_lrck; rp["GPIO13"]+=i2s_din
rp["GPIO12"]+=ws2812; rp["GPIO14"]+=btn_b; rp["GPIO15"]+=btn_a
rp["GPIO16"]+=sel_linst; rp["GPIO17"]+=gndlift_en; rp["GPIO18"]+=mute_en
rp["GPIO19"]+=sig_led; rp["GPIO20"]+=clip_led; rp["GPIO21"]+=gndlift_sw; rp["GPIO22"]+=lineinst_sw
rp["GPIO26/ADC0"]+=joy_x; rp["GPIO27/ADC1"]+=joy_y; rp["GPIO0"]+=dac_xsmt
rp["GPIO4"]+=midi_tx; rp["GPIO1"]+=midi_rx
endgrp(g)
# ============================ AUDIO CHAIN ============================
g = grp("Audio")
THAT1240=mk("THAT1240",[Pin(num=1,name="REF",func=P.INPUT),Pin(num=2,name="IN-",func=P.INPUT),Pin(num=3,name="IN+",func=P.INPUT),
Pin(num=4,name="V-",func=P.PWRIN),Pin(num=5,name="SENSE",func=P.PASSIVE),Pin(num=6,name="OUT",func=P.OUTPUT),
Pin(num=7,name="V+",func=P.PWRIN),Pin(num=8,name="NC",func=P.NOCONNECT)])
OPA1641=mk("OPA1641",[Pin(num=1,name="NC1",func=P.NOCONNECT),Pin(num=2,name="-IN",func=P.INPUT),Pin(num=3,name="+IN",func=P.INPUT),
Pin(num=4,name="V-",func=P.PWRIN),Pin(num=5,name="NC5",func=P.NOCONNECT),Pin(num=6,name="OUT",func=P.OUTPUT),
Pin(num=7,name="V+",func=P.PWRIN),Pin(num=8,name="NC8",func=P.NOCONNECT)])
OPA1612=mk("OPA1612",[Pin(num=1,name="OUTA",func=P.OUTPUT),Pin(num=2,name="-INA",func=P.INPUT),Pin(num=3,name="+INA",func=P.INPUT),
Pin(num=4,name="V-",func=P.PWRIN),Pin(num=5,name="+INB",func=P.INPUT),Pin(num=6,name="-INB",func=P.INPUT),
Pin(num=7,name="OUTB",func=P.OUTPUT),Pin(num=8,name="V+",func=P.PWRIN)])
THAT1646=mk("THAT1646",[Pin(num=1,name="OUT-",func=P.OUTPUT),Pin(num=2,name="SNS-",func=P.INPUT),Pin(num=3,name="GND",func=P.PWRIN),
Pin(num=4,name="IN",func=P.INPUT),Pin(num=5,name="V-",func=P.PWRIN),Pin(num=6,name="V+",func=P.PWRIN),
Pin(num=7,name="SNS+",func=P.INPUT),Pin(num=8,name="OUT+",func=P.OUTPUT)])
PCM5102A=mk("PCM5102A",[Pin(num=1,name="CPVDD",func=P.PWRIN),Pin(num=2,name="CAPP",func=P.PASSIVE),Pin(num=3,name="CPGND",func=P.PWRIN),
Pin(num=4,name="CAPM",func=P.PASSIVE),Pin(num=5,name="VNEG",func=P.PASSIVE),Pin(num=6,name="OUTL",func=P.OUTPUT),
Pin(num=7,name="OUTR",func=P.OUTPUT),Pin(num=8,name="AVDD",func=P.PWRIN),Pin(num=9,name="AGND",func=P.PWRIN),
Pin(num=10,name="DEMP",func=P.INPUT),Pin(num=11,name="FLT",func=P.INPUT),Pin(num=12,name="SCK",func=P.INPUT),
Pin(num=13,name="BCK",func=P.INPUT),Pin(num=14,name="DIN",func=P.INPUT),Pin(num=15,name="LRCK",func=P.INPUT),
Pin(num=16,name="FMT",func=P.INPUT),Pin(num=17,name="XSMT",func=P.INPUT),Pin(num=18,name="LDOO",func=P.PWROUT),
Pin(num=19,name="DGND",func=P.PWRIN),Pin(num=20,name="DVDD",func=P.PWRIN)],fp="Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm")
TQ2=mk("TQ2SA-5V",[Pin(num=1,name="COIL_A",func=P.PASSIVE),Pin(num=10,name="COIL_B",func=P.PASSIVE),
Pin(num=3,name="P1_COM",func=P.PASSIVE),Pin(num=4,name="P1_NC",func=P.PASSIVE),Pin(num=2,name="P1_NO",func=P.PASSIVE),
Pin(num=8,name="P2_COM",func=P.PASSIVE),Pin(num=7,name="P2_NC",func=P.PASSIVE),Pin(num=9,name="P2_NO",func=P.PASSIVE),
Pin(num=5,name="NC5",func=P.NOCONNECT),Pin(num=6,name="NC6",func=P.NOCONNECT)],ref="K",fp="pm_k1:Relay_DPDT_Panasonic_TQ2-SA")
ULN=mk("ULN2003A",[Pin(num=1,name="1B",func=P.INPUT),Pin(num=2,name="2B",func=P.INPUT),Pin(num=3,name="3B",func=P.INPUT),
Pin(num=4,name="4B",func=P.INPUT),Pin(num=5,name="5B",func=P.INPUT),Pin(num=6,name="6B",func=P.INPUT),Pin(num=7,name="7B",func=P.INPUT),
Pin(num=8,name="GND",func=P.PWRIN),Pin(num=9,name="COM",func=P.PWRIN),Pin(num=10,name="7C",func=P.OPENCOLL),
Pin(num=11,name="6C",func=P.OPENCOLL),Pin(num=12,name="5C",func=P.OPENCOLL),Pin(num=13,name="4C",func=P.OPENCOLL),
Pin(num=14,name="3C",func=P.OPENCOLL),Pin(num=15,name="2C",func=P.OPENCOLL),Pin(num=16,name="1C",func=P.OPENCOLL)],
fp="Package_SO:SOIC-16_3.9x9.9mm_P1.27mm")
rx=THAT1240(); dib=OPA1641(); dac=PCM5102A(); oa=OPA1612(); drv=THAT1646()
k1=TQ2(); k2=TQ2(); k3=TQ2(); uln=ULN()
def protect_bal(src):
cb=C("2.2uF","Capacitor_SMD:C_1206_3216Metric"); rs=R(value="1k"); rb=R(value="1Meg")
dp=D(value="1N4148WS"); dn=D(value="1N4148WS"); node=Net()
src+=cb[1]; cb[2]+=rs[1]; rs[2]+=node; rb[1]+=node; rb[2]+=gnd
dp[1]+=p15; dp[2]+=node; dn[1]+=node; dn[2]+=n15; return node
rx["IN+"]+=protect_bal(rx_hot_in); rx["IN-"]+=protect_bal(ain_cold)
rx["REF"]+=gnd; rx["SENSE"]+=rx_out; rx["OUT"]+=rx_out; rx["V+"]+=p15; rx["V-"]+=n15; dcp(p15); dcp(n15)
# DI buffer
cbk2=C("100nF","Capacitor_SMD:C_1206_3216Metric"); rbias=R(value="1Meg"); dpi=D(value="1N4148WS"); dni=D(value="1N4148WS"); dn_=Net()
di_in+=cbk2[1]; cbk2[2]+=dn_; rbias[1]+=dn_; rbias[2]+=gnd; dpi[1]+=p15; dpi[2]+=dn_; dni[1]+=dn_; dni[2]+=n15
dib["+IN"]+=dn_; rf2=R(value="3k"); rg2=R(value="1k"); dib["OUT"]+=di_out; rf2[1]+=di_out; rf2[2]+=dib["-IN"]
rg2[1]+=dib["-IN"]; rg2[2]+=gnd; dib["V+"]+=p15; dib["V-"]+=n15; dcp(p15); dcp(n15)
# select relay K1
k1["P1_COM"]+=ain_hot; k1["P1_NC"]+=rx_hot_in; k1["P1_NO"]+=di_in
k1["P2_COM"]+=stage1_out; k1["P2_NC"]+=rx_out; k1["P2_NO"]+=di_out; k1["COIL_A"]+=p5; k1["COIL_B"]+=k1d
# DAC
for pin in ("AVDD","CPVDD","DVDD"): dac[pin]+=p3v3
for pin in ("AGND","DGND","CPGND"): dac[pin]+=gnd
for pin in ("AVDD","CPVDD","DVDD"): dcp(p3v3)
cbd=C("10uF","Capacitor_SMD:C_1206_3216Metric"); dac["AVDD"]+=cbd[1]; cbd[2]+=gnd
cfly=C("2.2uF"); dac["CAPP"]+=cfly[1]; dac["CAPM"]+=cfly[2]; cvn=C("2.2uF"); dac["VNEG"]+=cvn[1]; cvn[2]+=gnd
cld=C("1uF"); dac["LDOO"]+=cld[1]; cld[2]+=gnd
dac["DEMP"]+=gnd; dac["FLT"]+=gnd; dac["FMT"]+=gnd; dac["SCK"]+=gnd # MCLK-less: SCK to GND (internal PLL)
dac["XSMT"]+=dac_xsmt; rpu=R(value="10k"); dac_xsmt+=rpu[1]; rpu[2]+=p3v3
dac["BCK"]+=i2s_bck; dac["DIN"]+=i2s_din; dac["LRCK"]+=i2s_lrck
rld=R(value="2.2k"); dac["OUTR"]+=rld[1]; rld[2]+=gnd
# recon filter = OPA1612 A
rfa=R(value="1.5k"); rfb=R(value="1.5k"); ca=C("2.2nF"); cbq=C("1nF"); rca=Net()
dac["OUTL"]+=rfa[1]; rfa[2]+=rca; rfb[1]+=rca; rfb[2]+=oa["+INA"]; ca[1]+=rca; ca[2]+=oa["OUTA"]
cbq[1]+=oa["+INA"]; cbq[2]+=gnd; oa["-INA"]+=oa["OUTA"]; oa["OUTA"]+=click_out
oa["V+"]+=p15; oa["V-"]+=n15; dcp(p15); dcp(n15)
# summer = OPA1612 B
ris=R(value="10k"); ric=R(value="10k"); rfs=R(value="10k")
stage1_out+=ris[1]; ris[2]+=oa["-INB"]; click_out+=ric[1]; ric[2]+=oa["-INB"]
rfs[1]+=oa["-INB"]; rfs[2]+=oa["OUTB"]; oa["+INB"]+=gnd; oa["OUTB"]+=mix_out
# level trim + THAT1646 + build-out
RV=Part("Device","R_Potentiometer",dest=TEMPLATE,footprint="Potentiometer_THT:Potentiometer_Bourns_3296W_Vertical")
rv1=RV(value="10k"); mix_out+=rv1[1]; rv1[3]+=gnd; rv1[2]+=drv["IN"]
drv["SNS-"]+=drv["OUT-"]; drv["SNS+"]+=drv["OUT+"]; drv["GND"]+=gnd; drv["V+"]+=p15; drv["V-"]+=n15; dcp(p15); dcp(n15)
rbo=R(value="47"); rbc=R(value="47"); drv["OUT-"]+=rbo[1]; rbo[2]+=aout_hot; drv["OUT+"]+=rbc[1]; rbc[2]+=aout_cold
# mute K2 + ground-lift K3
k2["P1_COM"]+=aout_hot; k2["P1_NC"]+=gnd; k2["P2_COM"]+=aout_cold; k2["P2_NC"]+=gnd; k2["COIL_A"]+=p5; k2["COIL_B"]+=k2d
k3["P1_COM"]+=gnd; k3["P1_NC"]+=chassis; rl=R(value="100"); cl=C("10nF")
rl[1]+=gnd; rl[2]+=chassis; cl[1]+=gnd; cl[2]+=chassis; k3["COIL_A"]+=p5; k3["COIL_B"]+=k3d
# shared relay driver
uln["1B"]+=sel_linst; uln["1C"]+=k1d; uln["2B"]+=mute_en; uln["2C"]+=k2d; uln["3B"]+=gndlift_en; uln["3C"]+=k3d
uln["GND"]+=gnd; uln["COM"]+=p5
endgrp(g)
# ============================ RTC ============================
g = grp("RTC")
RV8803=mk("RV-8803-C7",[Pin(num=1,name="SDA",func=P.BIDIR),Pin(num=2,name="CLKOUT",func=P.OUTPUT),Pin(num=3,name="VDD",func=P.PWRIN),
Pin(num=4,name="CLKOE",func=P.INPUT),Pin(num=5,name="VSS",func=P.PWRIN),Pin(num=6,name="INT",func=P.OPENCOLL),
Pin(num=7,name="EVI",func=P.INPUT),Pin(num=8,name="SCL",func=P.INPUT)],fp="pm_k1:RV-8803-C7")
rtc=RV8803(); rtc["VDD"]+=vdd_rtc; rtc["VSS"]+=gnd; rtc["SDA"]+=i2c_sda; rtc["SCL"]+=i2c_scl
rtc["CLKOE"]+=gnd; rtc["EVI"]+=gnd; rtc["INT"]+=rtc_int
db1=DS(value="BAT54"); db2=DS(value="BAT54"); p3v3+=db1[2]; db1[1]+=vdd_rtc
bt=Part("Device","Battery_Cell",value="CR2032",footprint="Battery:BatteryHolder_Keystone_1060_1x2032")
bt["+"]+=db2[2]; db2[1]+=vdd_rtc; bt["-"]+=gnd; crt=C("100nF"); vdd_rtc+=crt[1]; crt[2]+=gnd
for net in (i2c_sda,i2c_scl):
rpu2=R(value="4.7k"); net+=rpu2[1]; rpu2[2]+=p3v3
rint=R(value="10k"); rtc_int+=rint[1]; rint[2]+=p3v3
endgrp(g)
# ============================ MIDI (DNP) ============================
g = grp("MIDI")
H11L1=mk("H11L1",[Pin(num=1,name="A",func=P.PASSIVE),Pin(num=2,name="C",func=P.PASSIVE),Pin(num=3,name="NC",func=P.NOCONNECT),
Pin(num=4,name="GND",func=P.PWRIN),Pin(num=5,name="VO",func=P.OPENCOLL),Pin(num=6,name="VCC",func=P.PWRIN)],fp="Package_DIP:DIP-6_W7.62mm")
LVC14=mk("74LVC14",[Pin(num=1,name="1A",func=P.INPUT),Pin(num=2,name="1Y",func=P.OUTPUT),Pin(num=3,name="2A",func=P.INPUT),
Pin(num=4,name="2Y",func=P.OUTPUT),Pin(num=5,name="3A",func=P.INPUT),Pin(num=6,name="3Y",func=P.OUTPUT),Pin(num=7,name="GND",func=P.PWRIN),
Pin(num=8,name="4Y",func=P.OUTPUT),Pin(num=9,name="4A",func=P.INPUT),Pin(num=10,name="5Y",func=P.OUTPUT),Pin(num=11,name="5A",func=P.INPUT),
Pin(num=12,name="6Y",func=P.OUTPUT),Pin(num=13,name="6A",func=P.INPUT),Pin(num=14,name="VCC",func=P.PWRIN)],fp="Package_SO:TSSOP-14_4.4x5mm_P0.65mm")
opto=H11L1(); buf=LVC14()
rin=R(value="220"); dpr=D(value="1N4148WS"); mi_a+=rin[1]; rin[2]+=opto["A"]; opto["C"]+=mi_b
dpr[2]+=opto["C"]; dpr[1]+=opto["A"]; opto["VCC"]+=p3v3; opto["GND"]+=gnd
rvo=R(value="10k"); opto["VO"]+=midi_rx; midi_rx+=rvo[1]; rvo[2]+=p3v3; dcp(p3v3)
buf["1A"]+=midi_tx; buf["1Y"]+=Net(); buf["2A"]+=buf["1Y"]; rob=R(value="33"); buf["2Y"]+=rob[1]; rob[2]+=mo_b
roa=R(value="33"); p3v3+=roa[1]; roa[2]+=mo_a
buf["3A"]+=midi_rx; buf["3Y"]+=Net(); buf["4A"]+=buf["3Y"]; rtb=R(value="33"); buf["4Y"]+=rtb[1]; rtb[2]+=mt_b
rta=R(value="33"); p3v3+=rta[1]; rta[2]+=mt_a; buf["5A"]+=gnd; buf["6A"]+=gnd; buf["VCC"]+=p3v3; buf["GND"]+=gnd; dcp(p3v3)
endgrp(g)
# ============================ INDICATOR (DNP) ============================
g = grp("Indicator")
LM393=mk("LM393",[Pin(num=1,name="OUT1",func=P.OPENCOLL),Pin(num=2,name="-IN1",func=P.INPUT),Pin(num=3,name="+IN1",func=P.INPUT),
Pin(num=4,name="V-",func=P.PWRIN),Pin(num=5,name="+IN2",func=P.INPUT),Pin(num=6,name="-IN2",func=P.INPUT),
Pin(num=7,name="OUT2",func=P.OPENCOLL),Pin(num=8,name="V+",func=P.PWRIN)])
cmp=LM393(); cmp["V+"]+=p15; cmp["V-"]+=gnd; dcp(p15)
def peak(src):
d=DS(value="BAT54"); ch=C("100nF"); rb=R(value="100k"); node=Net()
src+=d[2]; d[1]+=node; ch[1]+=node; ch[2]+=gnd; rb[1]+=node; rb[2]+=gnd; return node
def thr(t,b):
n=Net(); rt=R(value=t); rb=R(value=b); rt[1]+=p15; rt[2]+=n; rb[1]+=n; rb[2]+=gnd; return n
cmp["+IN1"]+=peak(stage1_out); cmp["-IN1"]+=thr("1Meg","10k")
cmp["+IN2"]+=peak(mix_out); cmp["-IN2"]+=thr("100k","68k")
rsl=R(value="10k"); cmp["OUT1"]+=sig_led; sig_led+=rsl[1]; rsl[2]+=p3v3
rcl=R(value="10k"); cmp["OUT2"]+=clip_led; clip_led+=rcl[1]; rcl[2]+=p3v3
endgrp(g)
# ============================ SPEAKER (DNP) ============================
g = grp("Speaker")
PAM=mk("PAM8302A",[Pin(num=1,name="SD",func=P.INPUT),Pin(num=2,name="NC",func=P.NOCONNECT),Pin(num=3,name="IN+",func=P.INPUT),
Pin(num=4,name="IN-",func=P.INPUT),Pin(num=5,name="VO+",func=P.OUTPUT),Pin(num=6,name="VDD",func=P.PWRIN),
Pin(num=7,name="GND",func=P.PWRIN),Pin(num=8,name="VO-",func=P.OUTPUT)])
amp=PAM(); amp["VDD"]+=p5; amp["GND"]+=gnd; cba=C("1uF"); p5+=cba[1]; cba[2]+=gnd; dcp(p5)
rsd=R(value="100k"); spk_sd+=rsd[1]; rsd[2]+=p5; amp["SD"]+=spk_sd
cia=C("1uF"); ria=R(value="68k"); mix_out+=cia[1]; cia[2]+=ria[1]; ria[2]+=amp["IN+"]
cim=C("1uF"); amp["IN-"]+=cim[1]; cim[2]+=gnd; amp["VO+"]+=spk_p; amp["VO-"]+=spk_n
endgrp(g)
# ============================ INTERCONNECTS ============================
g = grp("Interconnect")
def hdr(name,fp): return Part("Connector_Generic",name,footprint=fp)
j3=hdr("Conn_02x13_Odd_Even","Connector_PinHeader_2.54mm:PinHeader_2x13_P2.54mm_Vertical")
for pin,net in {1:p5,2:gnd,3:p3v3,4:gnd,5:spi_sck,6:gnd,7:spi_mosi,8:lcd_cs,9:lcd_dc,10:lcd_rst,11:gnd,12:i2c_sda,
13:i2c_scl,14:gnd,15:joy_x,16:joy_y,17:btn_a,18:btn_b,19:ws2812,20:gnd,21:gndlift_sw,22:lineinst_sw,
23:sig_led,24:clip_led,25:gnd,26:gnd}.items(): j3[pin]+=net
j4=hdr("Conn_02x05_Odd_Even","Connector_PinHeader_2.54mm:PinHeader_2x05_P2.54mm_Vertical")
for pin,net in {1:aout_hot,2:gnd,3:aout_cold,4:chassis,5:ain_hot,6:gnd,7:ain_cold,8:spk_p,9:gnd,10:spk_n}.items(): j4[pin]+=net
j5=hdr("Conn_01x08","Connector_PinHeader_2.54mm:PinHeader_1x08_P2.54mm_Vertical")
for pin,net in {1:mo_a,2:mo_b,3:mi_a,4:mi_b,5:mt_a,6:mt_b,7:p5,8:gnd}.items(): j5[pin]+=net
j1=Part("Connector","USB_C_Receptacle",footprint="Connector_USB:USB_C_Receptacle_HRO_TYPE-C-31-M-12")
j1["VBUS"]+=vbus; j1["GND"]+=gnd; j1["D+"]+=usb_dp_c; j1["D-"]+=usb_dm_c
# USB shell/shield -> chassis is tied at LAYOUT (the symbol's shield pin "S1" mismatches older
# KiCad footprints that name the shell pads "SH" -> would error on import). Ground the shell to
# CHASSIS in Pcbnew directly, or via the usual 1Mohm || small-cap.
rc1=R(value="5.1k"); rc2=R(value="5.1k"); j1["CC1"]+=rc1[1]; rc1[2]+=gnd; j1["CC2"]+=rc2[1]; rc2[2]+=gnd
ESD=mk("USBLC6-2SC6",[Pin(num=1,name="IO1",func=P.PASSIVE),Pin(num=2,name="GND",func=P.PWRIN),Pin(num=3,name="IO2",func=P.PASSIVE),
Pin(num=4,name="IO2b",func=P.PASSIVE),Pin(num=5,name="VBUS",func=P.PASSIVE),Pin(num=6,name="IO1b",func=P.PASSIVE)],fp="Package_TO_SOT_SMD:SOT-23-6")
esd=ESD(); esd[1]+=usb_dp_c; esd[6]+=usb_dp_c; esd[3]+=usb_dm_c; esd[4]+=usb_dm_c; esd[2]+=gnd; esd[5]+=vbus
endgrp(g)
ERC()
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "board.net"))
generate_netlist(file_=out)
print("FULL BOARD netlist ->", out)

View file

@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""PM_K-1 SIG/CLIP indicator (SKiDL): peak-detect + LM393 -> SIG_LED / CLIP_LED.
Run INSIDE the EDA container:
cd hardware/eda && ./run.sh python3 ../eda/circuits/indicator.py
Outputs ERC + hardware/kicad/indicator.net.
Signal-present detect taps STAGE1_OUT; clip detect taps MIX_OUT (pre-driver). Each is
peak-rectified (Schottky + hold cap + bleed R) into an LM393 comparator vs a threshold
divider. Outputs are open-collector, pulled to +3V3, so they drive the face LED line AND
read cleanly on an RP2350 GPIO (SIG_LED=GPIO19, CLIP_LED=GPIO20 in the core).
LM393 powered from +15V (handles the audio common-mode range); open-collector pulled to
+3V3 keeps the logic level RP2350-safe. Threshold-divider values are TUNABLE (set the
signal-present and clip points at bring-up). DNP/optional -- a face fits the LEDs.
LM393 pinout standard: 1=OUT1 2=-IN1 3=+IN1 4=V- 5=+IN2 6=-IN2 7=OUT2 8=V+.
"""
import os
from skidl import *
set_default_tool(KICAD9)
P = Pin.types
R = Part("Device","R", dest=TEMPLATE, footprint="Resistor_SMD:R_0402_1005Metric")
def C(v): return Part("Device","C", value=v, footprint="Capacitor_SMD:C_0402_1005Metric")
DS = Part("Device","D_Schottky", dest=TEMPLATE, footprint="Diode_SMD:D_SOD-323")
p15, p3v3, gnd = Net("+15V"), Net("+3V3"), Net("GND")
for n in (p15, p3v3): n.drive = POWER
gnd.drive = POWER
stage1_out, mix_out = Net("STAGE1_OUT"), Net("MIX_OUT") # taps (shared by name)
sig_led, clip_led = Net("SIG_LED"), Net("CLIP_LED") # open-collector -> GPIO + face LED
LM393 = Part(name="LM393", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
footprint="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
pins=[Pin(num=1,name="OUT1",func=P.OPENCOLL),Pin(num=2,name="-IN1",func=P.INPUT),Pin(num=3,name="+IN1",func=P.INPUT),
Pin(num=4,name="V-",func=P.PWRIN),Pin(num=5,name="+IN2",func=P.INPUT),Pin(num=6,name="-IN2",func=P.INPUT),
Pin(num=7,name="OUT2",func=P.OPENCOLL),Pin(num=8,name="V+",func=P.PWRIN)])
u = LM393(ref="U11")
u["V+"] += p15; u["V-"] += gnd
cdec = C("100nF"); p15 += cdec[1]; cdec[2] += gnd
def peak_detect(src):
d = DS(value="BAT54"); ch = C("100nF"); rb = R(value="100k")
node = Net()
src += d[2]; d[1] += node # Schottky pin1=K,2=A: anode at src, cathode->node (rectify +peaks)
ch[1] += node; ch[2] += gnd; rb[1] += node; rb[2] += gnd
return node
def threshold(div_top, div_bot):
n = Net(); rt = R(value=div_top); rb = R(value=div_bot)
rt[1] += p15; rt[2] += n; rb[1] += n; rb[2] += gnd # rt[1]+=p15 (not p15+=...) to avoid local rebind
return n
# signal-present: low threshold ; clip: high threshold (TUNABLE)
u["+IN1"] += peak_detect(stage1_out); u["-IN1"] += threshold("1Meg","10k") # ~0.15V trip
u["+IN2"] += peak_detect(mix_out); u["-IN2"] += threshold("100k","68k") # higher (clip)
rs = R(value="10k"); u["OUT1"] += sig_led; sig_led += rs[1]; rs[2] += p3v3 # open-coll pull-ups to 3V3
rc = R(value="10k"); u["OUT2"] += clip_led; clip_led += rc[1]; rc[2] += p3v3
ERC()
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "indicator.net"))
generate_netlist(file_=out)
print("Indicator netlist ->", out)

View file

@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""PM_K-1 interconnects (SKiDL): digital ribbon + analog header + MIDI header + USB-C sub-block.
Run INSIDE the EDA container:
cd hardware/eda && ./run.sh python3 ../eda/circuits/interconnect.py
Outputs ERC + hardware/kicad/interconnect.net.
Maps the named board nets onto the physical connectors (pinouts per DESIGN.md s7):
J3 digital ribbon (2x13, Pico-pinout) -- display SPI, touch I2C, joystick ADC, buttons,
LED, panel-switch inputs, SIG/CLIP LED lines, power.
J4 analog header (2x5) -- balanced out hot/cold, shield, balanced in hot/cold, speaker.
J5 MIDI header (1x8) -- OUT/IN/THRU loops + 5V + GND (used only if DNP MIDI fitted).
J1 USB-C receptacle + USBLC6-2SC6 ESD + CC pulldowns. D+/D- come from the core's 27R
series (USB_DP_CONN/USB_DM_CONN); VBUS feeds the power tree.
USB_C_Receptacle = authoritative KiCad symbol (24-pin; SS pins unused for USB2.0). If a
16-pin USB2.0 connector (GCT USB4085, in BOM) is used, swap symbol+footprint at layout.
USBLC6-2SC6 hand-defined to the standard ST SOT-23-6 pinout (I/O1=1,6 / GND=2 / I/O2=3,4 /
VBUS=5) -- confirm at layout.
"""
import os
from skidl import *
set_default_tool(KICAD9)
P = Pin.types
R = Part("Device","R", dest=TEMPLATE, footprint="Resistor_SMD:R_0402_1005Metric")
# ---- rails + every signal that crosses to a face ----
p5, p3v3, gnd = Net("+5V"), Net("+3V3"), Net("GND")
for n in (p5, p3v3): n.drive = POWER
gnd.drive = POWER
N = lambda s: Net(s)
spi_sck,spi_mosi,lcd_cs,lcd_dc,lcd_rst = N("SPI_SCK"),N("SPI_MOSI"),N("LCD_CS"),N("LCD_DC"),N("LCD_RST")
i2c_sda,i2c_scl,joy_x,joy_y,btn_a,btn_b,ws2812 = N("I2C_SDA"),N("I2C_SCL"),N("JOY_X"),N("JOY_Y"),N("BTN_A"),N("BTN_B"),N("WS2812")
gndlift_sw,lineinst_sw,sig_led,clip_led = N("GNDLIFT_SW"),N("LINEINST_SW"),N("SIG_LED"),N("CLIP_LED")
aout_hot,aout_cold,chassis,ain_hot,ain_cold,spkp,spkn = (N("AOUT_HOT"),N("AOUT_COLD"),N("CHASSIS"),
N("AIN_HOT"),N("AIN_COLD"),N("SPK_P"),N("SPK_N"))
mo_a,mo_b,mi_a,mi_b,mt_a,mt_b = N("MIDI_OUT_A"),N("MIDI_OUT_B"),N("MIDI_IN_A"),N("MIDI_IN_B"),N("MIDI_THRU_A"),N("MIDI_THRU_B")
vbus,usb_dp_c,usb_dm_c = N("VBUS_5V"),N("USB_DP_CONN"),N("USB_DM_CONN")
vbus.drive = POWER
def hdr(name, fp, ref): return Part("Connector_Generic", name, footprint=fp, ref=ref)
# ---- J3 digital ribbon (2x13, Pico-pinout) -- DESIGN.md s7.1 ----
j3 = hdr("Conn_02x13_Odd_Even","Connector_PinHeader_2.54mm:PinHeader_2x13_P2.54mm_Vertical","J3")
dmap = {1:p5,2:gnd,3:p3v3,4:gnd,5:spi_sck,6:gnd,7:spi_mosi,8:lcd_cs,9:lcd_dc,10:lcd_rst,
11:gnd,12:i2c_sda,13:i2c_scl,14:gnd,15:joy_x,16:joy_y,17:btn_a,18:btn_b,19:ws2812,20:gnd,
21:gndlift_sw,22:lineinst_sw,23:sig_led,24:clip_led,25:gnd,26:gnd}
for pin,net in dmap.items(): j3[pin] += net
# ---- J4 analog header (2x5) -- DESIGN.md s7.2 (kept away from the digital ribbon) ----
j4 = hdr("Conn_02x05_Odd_Even","Connector_PinHeader_2.54mm:PinHeader_2x05_P2.54mm_Vertical","J4")
amap = {1:aout_hot,2:gnd,3:aout_cold,4:chassis,5:ain_hot,6:gnd,7:ain_cold,8:spkp,9:gnd,10:spkn}
for pin,net in amap.items(): j4[pin] += net
# ---- J5 MIDI header (1x8) -- OUT/IN/THRU + power (DNP MIDI only) ----
j5 = hdr("Conn_01x08","Connector_PinHeader_2.54mm:PinHeader_1x08_P2.54mm_Vertical","J5")
mmap = {1:mo_a,2:mo_b,3:mi_a,4:mi_b,5:mt_a,6:mt_b,7:p5,8:gnd}
for pin,net in mmap.items(): j5[pin] += net
# ---- J1 USB-C receptacle (USB2.0 subset of the 24-pin symbol) ----
j1 = Part("Connector","USB_C_Receptacle",
footprint="Connector_USB:USB_C_Receptacle_HRO_TYPE-C-31-M-12", ref="J1")
j1["VBUS"] += vbus; j1["GND"] += gnd; j1["SHIELD"] += chassis
j1["D+"] += usb_dp_c; j1["D-"] += usb_dm_c # name returns both A/B-side pins
rcc1 = R(value="5.1k"); rcc2 = R(value="5.1k") # UFP/device CC pulldowns
j1["CC1"] += rcc1[1]; rcc1[2] += gnd
j1["CC2"] += rcc2[1]; rcc2[2] += gnd
# ---- USBLC6-2SC6 ESD (standard ST SOT-23-6 pinout) ----
ESD = Part(name="USBLC6-2SC6", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
footprint="Package_TO_SOT_SMD:SOT-23-6",
pins=[Pin(num=1,name="IO1",func=P.PASSIVE),Pin(num=2,name="GND",func=P.PWRIN),Pin(num=3,name="IO2",func=P.PASSIVE),
Pin(num=4,name="IO2b",func=P.PASSIVE),Pin(num=5,name="VBUS",func=P.PASSIVE),Pin(num=6,name="IO1b",func=P.PASSIVE)])
esd = ESD(ref="U10")
esd[1] += usb_dp_c; esd[6] += usb_dp_c # I/O1 (D+) clamp
esd[3] += usb_dm_c; esd[4] += usb_dm_c # I/O2 (D-) clamp
esd[2] += gnd; esd[5] += vbus
ERC()
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "interconnect.net"))
generate_netlist(file_=out)
print("Interconnect netlist ->", out)

View file

@ -0,0 +1,122 @@
#!/usr/bin/env python3
"""PM_K-1 RP2350 core (SKiDL): MCU + QSPI flash + 12MHz crystal (MCLK-less) + USB + boot/reset + SWD.
Run INSIDE the EDA container:
cd hardware/eda && ./run.sh python3 ../eda/circuits/mcu_core.py
Outputs ERC + hardware/kicad/mcu_core.net.
VERIFIED
* RP2350A pinout: the authoritative KiCad MCU_RaspberryPi:RP2350A library symbol
(datasheet-derived). Power/clock/QSPI/USB/debug pins referenced by NUMBER, GPIO by name.
* Minimal design per RP "Hardware design with RP2350" (RP-008280): core SMPS
VREG_LX -> 3.3uH -> DVDD, VREG_FB sense to DVDD; VREG_AVDD via 33ohm+4.7uF RC filter;
100nF per power pin; W25Q128JVS QSPI flash (Fig 8 pinout); BOOTSEL = QSPI_SS via 1k +
button; RUN 10k pull-up + reset button; USB D+/D- via 27ohm series.
* CLOCK = 12MHz crystal, MCLK-LESS: RP2350 generates I2S BCK/LRCK/DIN; the PCM5102A
derives its own clock (internal PLL), so no separate audio oscillator and no MCLK net.
CONFIRM AT LAYOUT (flagged, not guessed): crystal load-cap value (~15pF, per chosen
crystal) + possible XOUT series R; QFN-60 footprint variant; and the USB-C connector +
USBLC6-2 ESD + CC resistors (a small USB sub-block; parts in BOM) -- their pinouts to be
verified then. Here USB exits via 27ohm series to USB_DP_CONN/USB_DM_CONN.
GPIO MAP (DESIGN.md s7.1 + audio control): see assignments at the bottom.
"""
import os
from skidl import *
set_default_tool(KICAD9)
P = Pin.types
R = Part("Device","R", dest=TEMPLATE, footprint="Resistor_SMD:R_0402_1005Metric")
def C(v, fp="Capacitor_SMD:C_0402_1005Metric"): return Part("Device","C", value=v, footprint=fp)
# ---- nets ----
p3v3, gnd, dvdd, vbus = Net("+3V3"), Net("GND"), Net("DVDD"), Net("VBUS_5V")
for n in (p3v3, vbus): n.drive = POWER
gnd.drive = POWER; dvdd.drive = POWER
xin, xout = Net("XIN"), Net("XOUT")
qsck,qd0,qd1,qd2,qd3,qcs = (Net("QSPI_SCLK"),Net("QSPI_SD0"),Net("QSPI_SD1"),Net("QSPI_SD2"),Net("QSPI_SD3"),Net("QSPI_SS"))
usb_dp_c, usb_dm_c = Net("USB_DP_CONN"), Net("USB_DM_CONN")
# audio + control (shared with audio chain / interconnect by name)
i2s_bck,i2s_lrck,i2s_din = Net("I2S_BCK"),Net("I2S_LRCK"),Net("I2S_DIN")
sel_linst,mute_en,gndlift_en,dac_xsmt = Net("SEL_LINST"),Net("MUTE_EN"),Net("GNDLIFT_EN"),Net("DAC_XSMT")
sig_led,clip_led,gndlift_sw,lineinst_sw = Net("SIG_LED"),Net("CLIP_LED"),Net("GNDLIFT_SW"),Net("LINEINST_SW")
spi_sck,spi_mosi,lcd_cs,lcd_dc,lcd_rst = Net("SPI_SCK"),Net("SPI_MOSI"),Net("LCD_CS"),Net("LCD_DC"),Net("LCD_RST")
i2c_sda,i2c_scl,ws2812,btn_a,btn_b,joy_x,joy_y = (Net("I2C_SDA"),Net("I2C_SCL"),Net("WS2812"),
Net("BTN_A"),Net("BTN_B"),Net("JOY_X"),Net("JOY_Y"))
# ---- RP2350A (authoritative KiCad symbol) ----
rp = Part("MCU_RaspberryPi","RP2350A", footprint="Package_DFN_QFN:QFN-60-1EP_7x7mm_P0.4mm_EP3.6x3.6mm", ref="U1")
# power: IOVDD x6, DVDD x3, plus the special supplies (by pin number)
for n in (1,11,20,30,38,45): rp[n] += p3v3 # IOVDD
for n in (6,23,39): rp[n] += dvdd # DVDD (1.1V core out)
rp[53] += p3v3; rp[54] += p3v3; rp[49] += p3v3 # USB_OTP_VDD, QSPI_IOVDD, VREG_VIN
rp[47] += gnd; rp[61] += gnd # VREG_PGND, GND/EP
# core SMPS: VREG_LX(48) -> 3.3uH -> DVDD ; VREG_FB(50) senses DVDD
lcore = Part("Device","L", value="3.3uH", footprint="Inductor_SMD:L_0806_2016Metric", ref="L1")
rp[48] += lcore[1]; lcore[2] += dvdd; rp[50] += dvdd
ccore = C("1uF","Capacitor_SMD:C_0402_1005Metric"); dvdd += ccore[1]; ccore[2] += gnd
cvin = C("4.7uF","Capacitor_SMD:C_0805_2012Metric"); rp[49] += cvin[1]; cvin[2] += gnd
# VREG_AVDD(46): 33ohm + 4.7uF RC filter from 3V3
rav = R(value="33"); cav = C("4.7uF","Capacitor_SMD:C_0805_2012Metric")
p3v3 += rav[1]; rav[2] += rp[46]; rp[46] += cav[1]; cav[2] += gnd
# ADC_AVDD(44): ferrite/0R + 100nF
rad = R(value="0"); cad = C("100nF"); p3v3 += rad[1]; rad[2] += rp[44]; rp[44] += cad[1]; cad[2] += gnd
# bulk decoupling (per-power-pin 100nF; placement is a layout concern)
for _ in range(6):
c = C("100nF"); p3v3 += c[1]; c[2] += gnd
for _ in range(2):
c = C("100nF"); dvdd += c[1]; c[2] += gnd
# ---- 12MHz crystal (MCLK-less) ----
rp[21] += xin; rp[22] += xout
xtal = Part("Device","Crystal", value="12MHz", footprint="Crystal:Crystal_SMD_3225-4Pin_3.2x2.5mm", ref="Y1")
xin += xtal[1]; xout += xtal[2]
cx1 = C("15pF"); cx2 = C("15pF"); xin += cx1[1]; cx1[2] += gnd; xout += cx2[1]; cx2[2] += gnd
# ---- QSPI flash W25Q128JVS (pinout per RP2350 hw-design Fig 8) ----
FL = Part(name="W25Q128JVS", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
footprint="Package_SO:SOIC-8_5.23x5.23mm_P1.27mm",
pins=[Pin(num=1,name="CS",func=P.INPUT),Pin(num=2,name="IO1",func=P.BIDIR),Pin(num=3,name="IO2",func=P.BIDIR),
Pin(num=4,name="GND",func=P.PWRIN),Pin(num=5,name="IO0",func=P.BIDIR),Pin(num=6,name="CLK",func=P.INPUT),
Pin(num=7,name="IO3",func=P.BIDIR),Pin(num=8,name="VCC",func=P.PWRIN)])
fl = FL(ref="U2")
fl["CLK"]+=qsck; fl["IO0"]+=qd0; fl["IO1"]+=qd1; fl["IO2"]+=qd2; fl["IO3"]+=qd3; fl["CS"]+=qcs
fl["VCC"]+=p3v3; fl["GND"]+=gnd
cfl = C("100nF"); p3v3 += cfl[1]; cfl[2] += gnd
rp[56]+=qsck; rp[57]+=qd0; rp[59]+=qd1; rp[58]+=qd2; rp[55]+=qd3; rp[60]+=qcs # QSPI_SCLK/SD0/SD1/SD2/SD3/SS
# ---- BOOTSEL: QSPI_SS -> 1k -> button -> GND ----
rboot = R(value="1k"); swboot = Part("Switch","SW_Push", footprint="Button_Switch_SMD:SW_SPST_SKQG_WithStem", ref="SW1")
qcs += rboot[1]; rboot[2] += swboot[1]; swboot[2] += gnd
# ---- RUN: 10k pull-up + reset button + 100nF ----
rrun = R(value="10k"); swrun = Part("Switch","SW_Push", footprint="Button_Switch_SMD:SW_SPST_SKQG_WithStem", ref="SW2")
p3v3 += rrun[1]; rrun[2] += rp[26]; rp[26] += swrun[1]; swrun[2] += gnd
crun = C("100nF"); rp[26] += crun[1]; crun[2] += gnd
# ---- SWD debug header (SWDIO/SWCLK/GND/3V3) ----
swd = Part("Connector_Generic","Conn_01x04", footprint="Connector_PinHeader_1.27mm:PinHeader_1x04_P1.27mm_Vertical", ref="J2")
swd[1]+=p3v3; swd[2]+=rp[25]; swd[3]+=rp[24]; swd[4]+=gnd # SWDIO=25, SWCLK=24
# ---- USB D+/D- via 27ohm series (USB-C connector + ESD = separate sub-block) ----
rdp = R(value="27"); rdm = R(value="27")
rp[52]+=rdp[1]; rdp[2]+=usb_dp_c # USB_DP
rp[51]+=rdm[1]; rdm[2]+=usb_dm_c # USB_DM
# ---- GPIO assignments (DESIGN.md s7.1 + audio control) ----
rp["GPIO2"]+=spi_sck; rp["GPIO3"]+=spi_mosi; rp["GPIO5"]+=lcd_cs; rp["GPIO6"]+=lcd_dc; rp["GPIO7"]+=lcd_rst
rp["GPIO8"]+=i2c_sda; rp["GPIO9"]+=i2c_scl
rp["GPIO10"]+=i2s_bck; rp["GPIO11"]+=i2s_lrck; rp["GPIO13"]+=i2s_din
rp["GPIO12"]+=ws2812
rp["GPIO14"]+=btn_b; rp["GPIO15"]+=btn_a
rp["GPIO16"]+=sel_linst; rp["GPIO17"]+=gndlift_en; rp["GPIO18"]+=mute_en
rp["GPIO19"]+=sig_led; rp["GPIO20"]+=clip_led
rp["GPIO21"]+=gndlift_sw; rp["GPIO22"]+=lineinst_sw
rp["GPIO26/ADC0"]+=joy_x; rp["GPIO27/ADC1"]+=joy_y
rp["GPIO0"]+=dac_xsmt
ERC()
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "mcu_core.net"))
generate_netlist(file_=out)
print("MCU core netlist ->", out)

View file

@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""PM_K-1 MIDI block (SKiDL) -- DNP populate-option: opto-isolated IN + buffered OUT + THRU.
Run INSIDE the EDA container:
cd hardware/eda && ./run.sh python3 ../eda/circuits/midi.py
Outputs ERC + hardware/kicad/midi.net.
USB-MIDI is the DEFAULT (firmware). This hardware DIN/TRS MIDI is a DO-NOT-POPULATE
option for laptop-free sync to standalone gear (a "stage" face fits the connector + parts).
IN : opto-isolated (H11L1) -- breaks the ground loop the MIDI spec requires.
OUT : RP2350 UART TX -> 74LVC14 buffer (2 inverters = non-inverting) -> series R.
THRU: a buffered copy of the received IN signal (re-transmit), same buffer chip.
PINOUTS
* 74LVC14 hex Schmitt inverter: standard 14-pin (1A/1Y..6A/6Y, GND=7, VCC=14).
* H11L1 6-pin Schmitt opto: standard 1=Anode 2=Cathode 3=NC 4=GND 5=VO 6=VCC
(universal across makers of this part; datasheet fetch timed out -- reconfirm at layout).
CONFIRM: the series-resistor values for 3.3V MIDI per the MIDI Association spec (placeholders
below). Connector (TRS-MIDI Type-A vs DIN-5) is a FACE choice; the core exposes the loop nets.
"""
import os
from skidl import *
set_default_tool(KICAD9)
P = Pin.types
R = Part("Device","R", dest=TEMPLATE, footprint="Resistor_SMD:R_0402_1005Metric")
def C(v): return Part("Device","C", value=v, footprint="Capacitor_SMD:C_0402_1005Metric")
D = Part("Device","D", dest=TEMPLATE, footprint="Diode_SMD:D_SOD-323")
p3v3, gnd = Net("+3V3"), Net("GND")
p3v3.drive = POWER; gnd.drive = POWER
midi_tx, midi_rx = Net("MIDI_TX"), Net("MIDI_RX") # to/from RP2350 UART (assign free GPIO at integ.)
in_a, in_b = Net("MIDI_IN_A"), Net("MIDI_IN_B") # isolated current loop -> face connector
out_a, out_b = Net("MIDI_OUT_A"), Net("MIDI_OUT_B")
thru_a, thru_b = Net("MIDI_THRU_A"), Net("MIDI_THRU_B")
OPTO = Part(name="H11L1", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
footprint="Package_DIP:DIP-6_W7.62mm",
pins=[Pin(num=1,name="A",func=P.PASSIVE),Pin(num=2,name="C",func=P.PASSIVE),Pin(num=3,name="NC",func=P.NOCONNECT),
Pin(num=4,name="GND",func=P.PWRIN),Pin(num=5,name="VO",func=P.OPENCOLL),Pin(num=6,name="VCC",func=P.PWRIN)])
BUF = Part(name="74LVC14", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
footprint="Package_SO:TSSOP-14_4.4x5mm_P0.65mm",
pins=[Pin(num=1,name="1A",func=P.INPUT),Pin(num=2,name="1Y",func=P.OUTPUT),Pin(num=3,name="2A",func=P.INPUT),
Pin(num=4,name="2Y",func=P.OUTPUT),Pin(num=5,name="3A",func=P.INPUT),Pin(num=6,name="3Y",func=P.OUTPUT),
Pin(num=7,name="GND",func=P.PWRIN),Pin(num=8,name="4Y",func=P.OUTPUT),Pin(num=9,name="4A",func=P.INPUT),
Pin(num=10,name="5Y",func=P.OUTPUT),Pin(num=11,name="5A",func=P.INPUT),Pin(num=12,name="6Y",func=P.OUTPUT),
Pin(num=13,name="6A",func=P.INPUT),Pin(num=14,name="VCC",func=P.PWRIN)])
opto = OPTO(ref="U8"); buf = BUF(ref="U9")
# ---- MIDI IN (opto-isolated) ----
rin = R(value="220"); dprot = D(value="1N4148WS")
in_a += rin[1]; rin[2] += opto["A"] # current-limit into LED
opto["C"] += in_b # LED return (isolated side)
dprot[2] += opto["C"]; dprot[1] += opto["A"] # reverse-protection across the LED (pin1=K,2=A)
opto["VCC"] += p3v3; opto["GND"] += gnd
rvo = R(value="10k"); opto["VO"] += midi_rx; midi_rx += rvo[1]; rvo[2] += p3v3 # pull-up; received data
copt = C("100nF"); p3v3 += copt[1]; copt[2] += gnd
# ---- MIDI OUT: TX -> two inverters -> series R ----
buf["1A"] += midi_tx; buf["1Y"] += Net("MIDI_OUT_N") # first invert
buf["2A"] += buf["1Y"] # second invert -> non-inverted
ro_b = R(value="33"); buf["2Y"] += ro_b[1]; ro_b[2] += out_b
ro_a = R(value="33"); p3v3 += ro_a[1]; ro_a[2] += out_a # current-source leg
# ---- MIDI THRU: re-buffer the received signal ----
buf["3A"] += midi_rx; buf["3Y"] += Net("MIDI_THRU_N")
buf["4A"] += buf["3Y"]
rt_b = R(value="33"); buf["4Y"] += rt_b[1]; rt_b[2] += thru_b
rt_a = R(value="33"); p3v3 += rt_a[1]; rt_a[2] += thru_a
# unused inverters parked, supply
buf["5A"] += gnd; buf["6A"] += gnd
buf["VCC"] += p3v3; buf["GND"] += gnd
cbuf = C("100nF"); p3v3 += cbuf[1]; cbuf[2] += gnd
ERC()
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "midi.net"))
generate_netlist(file_=out)
print("MIDI netlist ->", out)

View file

@ -0,0 +1,141 @@
#!/usr/bin/env python3
"""PM_K-1 power tree (SKiDL): USB 5V -> +/-18V switcher -> ultra-low-noise +/-15V LDOs,
plus the digital 3V3 rail.
Run INSIDE the EDA container:
cd hardware/eda && ./run.sh python3 ../eda/circuits/power_tree.py
Outputs ERC + hardware/kicad/power_tree.net.
WHY THIS SHAPE
A switching converter is noisy; audio op-amps need quiet rails. So the TPS65131 makes
RAW +/-18V (efficiently, from USB 5V), then TPS7A4901/TPS7A3001 ultra-low-noise LDOs
drop that to CLEAN +/-15V for the analog section. 3V3 (digital) comes from a simple LDO.
VERIFIED FROM DATASHEETS (topology + values, not guessed)
* TPS65131 (TI SLVS493E): pinout p.3; Typical Application Fig 8-1 (p.11) gives the
L/D/C topology; Table 8-2 (p.12) the BOM; FB equations p.13 (Vref=1.213V):
Vpos=Vref(1+R1/R2); Vneg=-Vref*R3/R4. Inductors 4.7uH; D1/D2=MBRM120 Schottky.
We target ~+/-18V (LDO headroom): R1=1.4M/R2=100k -> +18.2V ; R3=1.5M/R4=100k -> -18.2V.
No Q1 battery switch (USB powered) -> BSW left open. PSP/PSN tied LOW = forced PWM
(constant-frequency, cleaner in the audio band) -- a deliberate change from the
battery-oriented example.
* TPS7A4901 / TPS7A3001 (8-pin, identical pinout 1=OUT 2=FB 3=NC 4=GND 5=EN 6=NR/SS
7=DNC 8=IN + PowerPAD): Vout=Vfb(1+Rt/Rb). Vfb approx +1.194V / -1.18V -- CONFIRM the
exact Vfb in each elec-char table before finalizing the divider.
* AP2112K-3.3 SOT-23-5 (1=VIN 2=GND 3=EN 4=NC 5=VOUT) -- standard pinout, confirm.
No SPICE here: a switcher is validated against TI's reference design + bench measurement,
not a behavioral op-amp model. Layout (switch nodes, ground return) is critical -- follow
the TI EVM/layout guidance.
"""
import os
from skidl import *
set_default_tool(KICAD9)
P = Pin.types
R = Part("Device","R", dest=TEMPLATE, footprint="Resistor_SMD:R_0805_2012Metric")
def Cp(v, fp="Capacitor_SMD:C_0805_2012Metric"):
return Part("Device","C", value=v, footprint=fp)
L = Part("Device","L", dest=TEMPLATE, footprint="Inductor_SMD:L_Wuerth_WE-PD_Typ4_M")
DS = Part("Device","D_Schottky", dest=TEMPLATE, footprint="Diode_SMD:D_SMA")
FB = Part("Device","L", dest=TEMPLATE, footprint="Inductor_SMD:L_0805_2012Metric") # ferrite bead
def mk(name, pins, fp, ref="U"):
return Part(name=name, tool=SKIDL, dest=TEMPLATE, ref_prefix=ref, footprint=fp, pins=pins)
# ---- nets ----
vbus, p5, p18, n18, p15, n15, p3v3, gnd, vref = (Net("VBUS_5V"), Net("+5V"), Net("+18V"),
Net("-18V"), Net("+15V"), Net("-15V"), Net("+3V3"), Net("GND"), Net("VREF"))
for n in (vbus, p5, p18, n18, p15, n15, p3v3): n.drive = POWER
gnd.drive = POWER
sw_boost, sw_inv, fbp, fbn = Net("SW_BOOST"), Net("SW_INV"), Net("FBP"), Net("FBN")
innf = Net("INN_F") # RC-filtered +5V to the inverter input
# ---- parts ----
TPS65131 = mk("TPS65131",
[Pin(num=1,name="INP",func=P.PASSIVE),Pin(num=24,name="INP2",func=P.PASSIVE),
Pin(num=2,name="PGND",func=P.PWRIN),Pin(num=3,name="PGND2",func=P.PWRIN),
Pin(num=4,name="VIN",func=P.PWRIN),Pin(num=5,name="INN",func=P.PASSIVE),Pin(num=6,name="INN2",func=P.PASSIVE),
Pin(num=7,name="BSW",func=P.OUTPUT),Pin(num=8,name="ENP",func=P.INPUT),Pin(num=9,name="PSP",func=P.INPUT),
Pin(num=10,name="ENN",func=P.INPUT),Pin(num=11,name="PSN",func=P.INPUT),
Pin(num=12,name="NC12",func=P.NOCONNECT),Pin(num=20,name="NC20",func=P.NOCONNECT),
Pin(num=13,name="OUTN",func=P.PASSIVE),Pin(num=14,name="OUTN2",func=P.PASSIVE),
Pin(num=15,name="VNEG",func=P.INPUT),Pin(num=16,name="FBN",func=P.INPUT),Pin(num=17,name="VREF",func=P.PWROUT),
Pin(num=18,name="CN",func=P.PASSIVE),Pin(num=19,name="AGND",func=P.PWRIN),Pin(num=21,name="CP",func=P.PASSIVE),
Pin(num=22,name="FBP",func=P.INPUT),Pin(num=23,name="VPOS",func=P.INPUT),
Pin(num=25,name="EP",func=P.PWRIN)],
"Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm")
# LDOs share one pinout (HVSSOP-8 PowerPAD)
def ldo(name):
return mk(name, [Pin(num=1,name="OUT",func=P.PWROUT),Pin(num=2,name="FB",func=P.INPUT),
Pin(num=3,name="NC",func=P.NOCONNECT),Pin(num=4,name="GND",func=P.PWRIN),Pin(num=5,name="EN",func=P.INPUT),
Pin(num=6,name="NR",func=P.PASSIVE),Pin(num=7,name="DNC",func=P.NOCONNECT),Pin(num=8,name="IN",func=P.PWRIN),
Pin(num=9,name="EP",func=P.PWRIN)], "Package_SO:HVSSOP-8-1EP_3x3mm_P0.65mm")
AP2112 = mk("AP2112K-3.3", [Pin(num=1,name="VIN",func=P.PWRIN),Pin(num=2,name="GND",func=P.PWRIN),
Pin(num=3,name="EN",func=P.INPUT),Pin(num=4,name="NC",func=P.NOCONNECT),Pin(num=5,name="VOUT",func=P.PWROUT)],
"Package_TO_SOT_SMD:SOT-23-5")
u7 = TPS65131(ref="U7"); u8 = ldo("TPS7A4901")(ref="U8"); u9 = ldo("TPS7A3001")(ref="U9"); u10 = AP2112(ref="U10")
l1, l2 = L(value="4.7uH"), L(value="4.7uH")
d1, d2 = DS(value="MBRM120"), DS(value="MBRM120")
# ---- USB input: ferrite + bulk ----
fb1 = FB(value="600R@100MHz"); vbus += fb1[1]; fb1[2] += p5
cbulk = Cp("10uF","Capacitor_SMD:C_1206_3216Metric"); p5 += cbulk[1]; cbulk[2] += gnd
# ---- TPS65131: control supply + grounds + enables ----
u7["VIN"] += p5; c2 = Cp("4.7uF"); p5 += c2[1]; c2[2] += gnd
u7["PGND"] += gnd; u7["PGND2"] += gnd; u7["AGND"] += gnd; u7["EP"] += gnd
u7["ENP"] += p5; u7["ENN"] += p5 # enabled
u7["PSP"] += gnd; u7["PSN"] += gnd # forced PWM (low audio-band noise)
# BSW, NC left unconnected (no battery switch)
# boost: +5V -> L1 -> INP(switch); C1 at inductor input; D1 INP->+18V; C4 on +18V
c1 = Cp("4.7uF"); p5 += c1[1]; c1[2] += gnd
p5 += l1[1]; l1[2] += sw_boost; u7["INP"] += sw_boost; u7["INP2"] += sw_boost
d1[2] += sw_boost; d1[1] += p18 # Device D_Schottky: pin1=K, pin2=A -> anode at SW, cathode at +18V
c4 = Cp("22uF","Capacitor_SMD:C_1206_3216Metric"); p18 += c4[1]; c4[2] += gnd
u7["VPOS"] += p18
# positive feedback divider (+18.2V): R1 +18->FBP (C9 feed-forward), R2 FBP->gnd
r1 = R(value="1.4M"); r2 = R(value="100k"); c9 = Cp("6.8pF","Capacitor_SMD:C_0603_1608Metric")
p18 += r1[1]; r1[2] += fbp; c9[1] += p18; c9[2] += fbp
r2[1] += fbp; r2[2] += gnd; u7["FBP"] += fbp
# inverter input: +5V -> R7 -> INN ; C3 filter
r7 = R(value="100"); c3 = Cp("100nF"); p5 += r7[1]; r7[2] += innf; c3[1] += innf; c3[2] += gnd
u7["INN"] += innf; u7["INN2"] += innf
# inverter: OUTN -> L2 -> gnd ; D2 -18V->OUTN ; C5 on -18V
u7["OUTN"] += sw_inv; u7["OUTN2"] += sw_inv; l2[1] += sw_inv; l2[2] += gnd
d2[1] += sw_inv; d2[2] += n18 # anode at -18V, cathode at SW_INV
c5 = Cp("22uF","Capacitor_SMD:C_1206_3216Metric"); n18 += c5[1]; c5[2] += gnd
u7["VNEG"] += n18
# negative feedback: R3 -18->FBN (C10 ff), R4 VREF->FBN
r3 = R(value="1.5M"); r4 = R(value="100k"); c10 = Cp("7.5pF","Capacitor_SMD:C_0603_1608Metric")
n18 += r3[1]; r3[2] += fbn; c10[1] += n18; c10[2] += fbn
r4[1] += vref; r4[2] += fbn; u7["FBN"] += fbn
# VREF bypass + compensation
u7["VREF"] += vref; c8 = Cp("220nF"); vref += c8[1]; c8[2] += gnd
c7 = Cp("4.7nF","Capacitor_SMD:C_0603_1608Metric"); u7["CP"] += c7[1]; c7[2] += gnd # boost comp
c6 = Cp("10nF"); u7["CN"] += c6[1]; c6[2] += gnd # inverter comp
# ---- +15V LDO (TPS7A4901): IN<-+18, OUT->+15, divider, NR cap ----
u8["IN"] += p18; ci8 = Cp("1uF"); p18 += ci8[1]; ci8[2] += gnd
u8["OUT"] += p15; co8 = Cp("2.2uF"); p15 += co8[1]; co8[2] += gnd
u8["GND"] += gnd; u8["EP"] += gnd; u8["EN"] += p18
rt8 = R(value="116k"); rb8 = R(value="10k"); p15 += rt8[1]; rt8[2] += u8["FB"]; rb8[1] += u8["FB"]; rb8[2] += gnd
cnr8 = Cp("10nF"); u8["NR"] += cnr8[1]; cnr8[2] += gnd
# ---- -15V LDO (TPS7A3001): IN<--18, OUT->-15, divider, NR cap ----
u9["IN"] += n18; ci9 = Cp("1uF"); n18 += ci9[1]; ci9[2] += gnd
u9["OUT"] += n15; co9 = Cp("2.2uF"); n15 += co9[1]; co9[2] += gnd
u9["GND"] += gnd; u9["EP"] += gnd; u9["EN"] += n18
rt9 = R(value="117k"); rb9 = R(value="10k"); n15 += rt9[1]; rt9[2] += u9["FB"]; rb9[1] += u9["FB"]; rb9[2] += gnd
cnr9 = Cp("10nF"); u9["NR"] += cnr9[1]; cnr9[2] += gnd
# ---- 3V3 LDO (AP2112K): +5V -> +3V3 ----
u10["VIN"] += p5; u10["EN"] += p5; u10["GND"] += gnd; u10["VOUT"] += p3v3
ci10 = Cp("1uF"); p5 += ci10[1]; ci10[2] += gnd
co10 = Cp("1uF"); p3v3 += co10[1]; co10[2] += gnd
ERC()
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "power_tree.net"))
generate_netlist(file_=out)
print("Power tree netlist ->", out)

View file

@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""PM_K-1 RTC (SKiDL): RV-8803-C7 I2C real-time clock for the practice-log timestamps.
Run INSIDE the EDA container:
cd hardware/eda && ./run.sh python3 ../eda/circuits/rtc.py
Outputs ERC + hardware/kicad/rtc.net.
VERIFIED pinout (Micro Crystal RV-8803-C7, 8-WCDFN 3.2x1.5mm):
1=SDA 2=CLKOUT 3=VDD 4=CLKOE 5=VSS 6=/INT 7=EVI 8=SCL. Single VDD (no separate VBACKUP
pin); ~240nA typ. Shares the touch I2C bus (SDA=GPIO8, SCL=GPIO9).
BACKUP: diode-OR -- system +3V3 OR the CR2032 feed VDD_RTC through Schottkys, so the
board runs the RTC off 3V3 when on, the coin cell only when off, and (importantly for an
heirloom) the cell can be replaced WITHOUT the RTC losing time. Schottkys block charging
the (non-rechargeable) cell.
Unused pins: CLKOE->GND (CLKOUT disabled), CLKOUT->NC, EVI->GND (no event input),
/INT pulled up + routed to RTC_INT (optional alarm IRQ to a GPIO).
CONFIRM at layout: the RV-8803-C7 footprint, and cross-check the App Manual's recommended
backup circuit.
"""
import os
from skidl import *
set_default_tool(KICAD9)
P = Pin.types
R = Part("Device","R", dest=TEMPLATE, footprint="Resistor_SMD:R_0402_1005Metric")
def C(v): return Part("Device","C", value=v, footprint="Capacitor_SMD:C_0402_1005Metric")
DS = Part("Device","D_Schottky", dest=TEMPLATE, footprint="Diode_SMD:D_SOD-323")
p3v3, gnd = Net("+3V3"), Net("GND")
p3v3.drive = POWER; gnd.drive = POWER
i2c_sda, i2c_scl = Net("I2C_SDA"), Net("I2C_SCL") # shared bus (RP2350 GPIO8/9 + touch)
vdd_rtc, rtc_int = Net("VDD_RTC"), Net("RTC_INT")
RV8803 = Part(name="RV-8803-C7", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
footprint="RTC_MicroCrystal:RV-8803-C7", # footprint: confirm at layout
pins=[Pin(num=1,name="SDA",func=P.BIDIR),Pin(num=2,name="CLKOUT",func=P.OUTPUT),
Pin(num=3,name="VDD",func=P.PWRIN),Pin(num=4,name="CLKOE",func=P.INPUT),
Pin(num=5,name="VSS",func=P.PWRIN),Pin(num=6,name="INT",func=P.OPENCOLL),
Pin(num=7,name="EVI",func=P.INPUT),Pin(num=8,name="SCL",func=P.INPUT)])
u = RV8803(ref="U7")
u["VDD"] += vdd_rtc; u["VSS"] += gnd
u["SDA"] += i2c_sda; u["SCL"] += i2c_scl
u["CLKOE"] += gnd # disable CLKOUT
u["EVI"] += gnd # unused event input
u["INT"] += rtc_int # open-drain alarm (optional)
# CLKOUT left unconnected
# diode-OR backup: 3V3 -> D1 -> VDD_RTC ; CR2032 -> D2 -> VDD_RTC
d1, d2 = DS(value="BAT54"), DS(value="BAT54")
p3v3 += d1[2]; d1[1] += vdd_rtc # D_Schottky pin1=K, pin2=A : anode at 3V3, cathode at VDD_RTC
bt = Part("Device","Battery_Cell", value="CR2032",
footprint="Battery:BatteryHolder_Keystone_1066_1x2032", ref="BT1")
bt["+"] += d2[2]; d2[1] += vdd_rtc; bt["-"] += gnd
crtc = C("100nF"); vdd_rtc += crtc[1]; crtc[2] += gnd
# I2C pull-ups (bus shared with touch) + /INT pull-up, to 3V3
for net in (i2c_sda, i2c_scl):
r = R(value="4.7k"); net += r[1]; r[2] += p3v3
rint = R(value="10k"); rtc_int += rint[1]; rint[2] += p3v3
ERC()
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "rtc.net"))
generate_netlist(file_=out)
print("RTC netlist ->", out)

View file

@ -0,0 +1,51 @@
#!/usr/bin/env python3
"""PM_K-1 monitor speaker amp (SKiDL): PAM8302A class-D -> SPK_P/SPK_N. DNP/optional.
Run INSIDE the EDA container:
cd hardware/eda && ./run.sh python3 ../eda/circuits/speaker.py
Outputs ERC + hardware/kicad/speaker.net.
Filterless 2.5W mono class-D for a built-in monitor (populated only on form factors that
want one). Fed from MIX_OUT (the mixed click+input, single-ended into IN+; IN- AC-coupled
to GND). Gain = 20*log(150k/(10k+RIN)); RIN=68k -> ~+5.7dB. SD pulled high = enabled
(route SPK_SD to a GPIO if software shutdown is wanted). Output is BTL (filterless) to the
speaker via the analog interconnect.
PAM8302A SO-8 pinout (Diodes, verified): 1=SD 2=NC 3=IN+ 4=IN- 5=VO+ 6=VDD 7=GND 8=VO-.
"""
import os
from skidl import *
set_default_tool(KICAD9)
P = Pin.types
R = Part("Device","R", dest=TEMPLATE, footprint="Resistor_SMD:R_0402_1005Metric")
def C(v, fp="Capacitor_SMD:C_0402_1005Metric"): return Part("Device","C", value=v, footprint=fp)
p5, p3v3, gnd = Net("+5V"), Net("+3V3"), Net("GND")
for n in (p5, p3v3): n.drive = POWER
gnd.drive = POWER
mix_out, spk_p, spk_n, spk_sd = Net("MIX_OUT"), Net("SPK_P"), Net("SPK_N"), Net("SPK_SD")
PAM = Part(name="PAM8302A", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
footprint="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
pins=[Pin(num=1,name="SD",func=P.INPUT),Pin(num=2,name="NC",func=P.NOCONNECT),Pin(num=3,name="IN+",func=P.INPUT),
Pin(num=4,name="IN-",func=P.INPUT),Pin(num=5,name="VO+",func=P.OUTPUT),Pin(num=6,name="VDD",func=P.PWRIN),
Pin(num=7,name="GND",func=P.PWRIN),Pin(num=8,name="VO-",func=P.OUTPUT)])
u = PAM(ref="U12")
# supply
u["VDD"] += p5; u["GND"] += gnd
cb = C("1uF","Capacitor_SMD:C_0805_2012Metric"); p5 += cb[1]; cb[2] += gnd
cd = C("100nF"); p5 += cd[1]; cd[2] += gnd
# enable (SD high); route SPK_SD to a GPIO if desired
rsd = R(value="100k"); spk_sd += rsd[1]; rsd[2] += p5; u["SD"] += spk_sd
# single-ended input: MIX_OUT -> coupling cap + RIN -> IN+ ; IN- coupled to gnd (matched)
cin = C("1uF","Capacitor_SMD:C_0805_2012Metric"); rin = R(value="68k") # ~+5.7dB
mix_out += cin[1]; cin[2] += rin[1]; rin[2] += u["IN+"]
cinm = C("1uF","Capacitor_SMD:C_0805_2012Metric"); u["IN-"] += cinm[1]; cinm[2] += gnd
# filterless BTL output to speaker (via interconnect); add EMI ferrite/cap at layout if needed
u["VO+"] += spk_p; u["VO-"] += spk_n
ERC()
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "speaker.net"))
generate_netlist(file_=out)
print("Speaker netlist ->", out)

View file

@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""PM_K-1 audio chain - Stage 1: balanced LINE input receiver + protection (SKiDL).
Code-defined schematic. Run INSIDE the EDA container:
cd hardware/eda && ./run.sh python3 ../eda/circuits/stage1_input.py
Outputs ERC results + a KiCad netlist at hardware/kicad/stage1_input.net,
which imports into Pcbnew for layout.
VERIFIED against datasheets:
* THAT1240 = 0 dB (unity-gain) line receiver; SO-8 pinout per THAT doc 600035
rev 05: 1=Ref 2=In- 3=In+ 4=Vee 5=Sense 6=Vout 7=Vcc 8=NC. Supply 6-36V
(our +/-15V is in range). Pin-compatible 2nd sources: INA134 / SSM2141.
* clamp-diode orientation per KiCad Device:D (pin1=K cathode, pin2=A anode).
Open at BOM time only: exact clamp-diode part (fast low-leakage small-signal;
1N4148WS is a placeholder).
"""
import os
from skidl import *
set_default_tool(KICAD9)
# ---------- passive templates ----------
R = Part("Device", "R", dest=TEMPLATE, footprint="Resistor_SMD:R_0805_2012Metric")
C = Part("Device", "C", dest=TEMPLATE, footprint="Capacitor_SMD:C_0805_2012Metric")
D = Part("Device", "D", dest=TEMPLATE, footprint="Diode_SMD:D_SOD-323")
# ---------- power rails (global nets; marked POWER-driven for ERC) ----------
# No power-flag *symbols* -- they carry no footprint and SKiDL errors on that.
# The rails arrive from the power block; here we just declare + drive them.
p15, n15, gnd = Net("+15V"), Net("-15V"), Net("GND")
for n in (p15, n15, gnd):
n.drive = POWER
# ---------- THAT1240 balanced line receiver (0 dB) : pinout VERIFIED ----------
RX = Part(name="THAT1240", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
footprint="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
pins=[
Pin(num=1, name="REF", func=Pin.types.INPUT),
Pin(num=2, name="IN-", func=Pin.types.INPUT),
Pin(num=3, name="IN+", func=Pin.types.INPUT),
Pin(num=4, name="V-", func=Pin.types.PWRIN),
Pin(num=5, name="SENSE", func=Pin.types.PASSIVE),
Pin(num=6, name="OUT", func=Pin.types.OUTPUT),
Pin(num=7, name="V+", func=Pin.types.PWRIN),
Pin(num=8, name="NC", func=Pin.types.NOCONNECT),
])
rx = RX(ref="U1")
# ---------- nets ----------
ain_hot, ain_cold, rxout = Net("AIN_HOT"), Net("AIN_COLD"), Net("RX_OUT")
def protected_input(src, node_name):
"""series DC-block cap (blocks +48V phantom) -> series R -> node;
bias R to gnd; clamp diodes to the +/-15V rails (orientation TODO-VERIFY)."""
cblk = C(value="2.2uF", footprint="Capacitor_SMD:C_1206_3216Metric") # film
rs, rb = R(value="1k"), R(value="1Meg")
dp, dn = D(value="1N4148WS"), D(value="1N4148WS")
node = Net(node_name)
src += cblk[1]; cblk[2] += rs[1]; rs[2] += node
rb[1] += node; rb[2] += gnd
# clamp diodes: KiCad Device:D is pin1=K (cathode), pin2=A (anode).
dp[1] += p15; dp[2] += node # high clamp: conducts when node > +15
dn[1] += node; dn[2] += n15 # low clamp: conducts when node < -15
return node
rx["IN+"] += protected_input(ain_hot, "RXIN_P")
rx["IN-"] += protected_input(ain_cold, "RXIN_N")
rx["REF"] += gnd
rx["SENSE"] += rxout # sense tied to output (local feedback)
rx["OUT"] += rxout
rx["V+"] += p15
rx["V-"] += n15
# supply decoupling at the receiver
for rail in (p15, n15):
c = C(value="100nF")
rail += c[1]; c[2] += gnd
ERC()
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "stage1_input.net"))
generate_netlist(file_=out)
print("Stage 1 netlist ->", out)

View file

@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""PM_K-1 audio chain - Stage 1b: Hi-Z instrument DI buffer + line/instrument select (SKiDL).
Run INSIDE the EDA container:
cd hardware/eda && ./run.sh python3 ../eda/circuits/stage1b_select.py
Outputs ERC + a KiCad netlist at hardware/kicad/stage1b_select.net.
WHAT THIS STAGE DOES
The same input jack feeds either the balanced LINE receiver (Stage 1) or a Hi-Z
INSTRUMENT buffer (here). One DPDT relay (K1) does two jobs at once:
pole 1 routes the jack TIP to the line receiver (default) OR the DI buffer
pole 2 selects which OUTPUT (RX_OUT or DI_OUT) feeds the summing stage
De-energized (relay OFF) = LINE = the common case (saves coil power; fail to line).
The DI buffer = OPA1641 non-inverting, gain +12 dB (1+Rf/Rg), 1 Mohm input.
PINOUTS VERIFIED FROM DATASHEETS
* OPA1641 (TI SBOS484D): 1=NC 2=-IN 3=+IN 4=V- 5=NC 6=OUT 7=V+ 8=NC. Supply +/-18V max.
* TQ2SA (Panasonic TQ-SMD): 2 Form C. pole1 COM=3 (throws 2,4); pole2 COM=8 (throws 7,9)
-- confirmed from the contact-resistance terminal pairs (2-3,3-4,7-8,8-9).
RESOLVED from the Panasonic TQ-SMD connection diagram (single-side-stable, top view)
+ the contact-resistance terminal pairs (2-3,3-4,7-8,8-9):
* coil = pins 1 & 10 (polarity irrelevant -- non-latching single coil, no diode).
* pole1 COM=3, NC=4, NO=2 ; pole2 COM=8, NC=7, NO=9 ; pins 5,6 unused.
NC/NO sense is also firmware-correctable: the relay is GPIO-driven, so if a physical
unit reads opposite, invert SEL_LINST in firmware (no board change).
Relay coil is driven by the SHARED ULN2003 (U14) -- represented here as net K1_DRV
(sinks the coil) + SEL_LINST (the RP2350 GPIO). The ULN2003 is instantiated once in
the power/control block, not per-relay.
"""
import os
from skidl import *
set_default_tool(KICAD9)
R = Part("Device", "R", dest=TEMPLATE, footprint="Resistor_SMD:R_0805_2012Metric")
C = Part("Device", "C", dest=TEMPLATE, footprint="Capacitor_SMD:C_0805_2012Metric")
D = Part("Device", "D", dest=TEMPLATE, footprint="Diode_SMD:D_SOD-323")
# ---- rails ----
p15, n15, gnd, p5 = Net("+15V"), Net("-15V"), Net("GND"), Net("+5V")
for n in (p15, n15, gnd, p5):
n.drive = POWER
# ---- OPA1641 JFET Hi-Z buffer (pinout verified) ----
OPA = Part(name="OPA1641", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
footprint="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
pins=[
Pin(num=1, name="NC1", func=Pin.types.NOCONNECT),
Pin(num=2, name="-IN", func=Pin.types.INPUT),
Pin(num=3, name="+IN", func=Pin.types.INPUT),
Pin(num=4, name="V-", func=Pin.types.PWRIN),
Pin(num=5, name="NC5", func=Pin.types.NOCONNECT),
Pin(num=6, name="OUT", func=Pin.types.OUTPUT),
Pin(num=7, name="V+", func=Pin.types.PWRIN),
Pin(num=8, name="NC8", func=Pin.types.NOCONNECT),
])
u = OPA(ref="U2")
# ---- TQ2SA DPDT select relay (contacts verified; NC/NO + coil pins flagged) ----
RLY = Part(name="TQ2SA-5V", tool=SKIDL, dest=TEMPLATE, ref_prefix="K",
footprint="Relay_SMD:Relay_DPDT_Panasonic_TQ2-SA", # footprint: confirm at layout
pins=[
Pin(num=1, name="COIL_A", func=Pin.types.PASSIVE),
Pin(num=10, name="COIL_B", func=Pin.types.PASSIVE),
Pin(num=3, name="P1_COM", func=Pin.types.PASSIVE),
Pin(num=4, name="P1_NC", func=Pin.types.PASSIVE), # de-energized = LINE
Pin(num=2, name="P1_NO", func=Pin.types.PASSIVE), # energized = INSTRUMENT
Pin(num=8, name="P2_COM", func=Pin.types.PASSIVE),
Pin(num=7, name="P2_NC", func=Pin.types.PASSIVE),
Pin(num=9, name="P2_NO", func=Pin.types.PASSIVE),
Pin(num=5, name="NC5", func=Pin.types.NOCONNECT),
Pin(num=6, name="NC6", func=Pin.types.NOCONNECT),
])
k1 = RLY(ref="K1")
# ---- nets (shared with Stage 1 / Stage 3 by name) ----
ain_hot = Net("AIN_HOT") # jack TIP
rx_hot_in = Net("RX_HOT_IN") # -> Stage 1 line-receiver hot protection (was AIN_HOT there)
di_in = Net("DI_IN") # -> this DI buffer input
rx_out = Net("RX_OUT") # <- Stage 1 line receiver output
di_out = Net("DI_OUT") # this DI buffer output
stage1_out = Net("STAGE1_OUT") # selected -> Stage 3 summing
sel = Net("SEL_LINST") # RP2350 GPIO: low=LINE(default), high=INSTRUMENT
k1_drv = Net("K1_DRV") # shared ULN2003 output sinks the coil
# pole 1: route jack tip
k1["P1_COM"] += ain_hot
k1["P1_NC"] += rx_hot_in # default -> line receiver
k1["P1_NO"] += di_in # energized -> DI buffer
# pole 2: select output
k1["P2_COM"] += stage1_out
k1["P2_NC"] += rx_out # default -> line receiver output
k1["P2_NO"] += di_out # energized -> DI buffer output
# coil: +5V -- coil -- K1_DRV (ULN2003 sinks to gnd when SEL_LINST high)
k1["COIL_A"] += p5
k1["COIL_B"] += k1_drv
# ---- DI buffer input: DC-block, 1M bias, rail clamps ----
cblk = C(value="100nF", footprint="Capacitor_SMD:C_1206_3216Metric") # film
rbias = R(value="1Meg")
dp, dn = D(value="1N4148WS"), D(value="1N4148WS")
node = Net("DI_NODE")
di_in += cblk[1]; cblk[2] += node
rbias[1] += node; rbias[2] += gnd
dp[1] += p15; dp[2] += node # Device:D pin1=K, pin2=A -> high clamp (>+15)
dn[1] += node; dn[2] += n15 # low clamp (< -15)
u["+IN"] += node
# ---- non-inverting gain: Av = 1 + Rf/Rg = 1 + 3k/1k = 4 (+12 dB) ----
rf, rg = R(value="3k"), R(value="1k")
u["OUT"] += di_out
rf[1] += di_out; rf[2] += u["-IN"]
rg[1] += u["-IN"]; rg[2] += gnd
# ---- supplies + decoupling ----
u["V+"] += p15; u["V-"] += n15
for rail in (p15, n15):
c = C(value="100nF"); rail += c[1]; c[2] += gnd
ERC()
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "stage1b_select.net"))
generate_netlist(file_=out)
print("Stage 1b netlist ->", out)

View file

@ -0,0 +1,137 @@
#!/usr/bin/env python3
"""PM_K-1 audio chain - Stage 2: PCM5102A DAC + reconstruction filter (SKiDL).
Run INSIDE the EDA container:
cd hardware/eda && ./run.sh python3 ../eda/circuits/stage2_dac.py
Outputs ERC + a KiCad netlist at hardware/kicad/stage2_dac.net.
WHAT THIS STAGE DOES
The RP2350 streams I2S audio (the click, and later sampled sounds) to a PCM5102A
DAC clocked by the dedicated low-jitter oscillator (SCK=MCLK). The PCM5102A puts
out 2.1 Vrms GROUND-CENTERED analog (no DC-block cap needed). A 2nd-order
Sallen-Key low-pass (~75 kHz, one OPA1612 section) removes the delta-sigma HF
residue before the click reaches the summing/output stages -> CLICK_OUT.
PINOUTS VERIFIED FROM DATASHEETS
* PCM5102A (TI SLAS859C, PW/TSSOP-20): pin map below matches the datasheet table.
Output 2.1Vrms GND-centered; 3.3V supplies; charge pump (CAPP/CAPM flying cap +
VNEG) makes the negative rail for the ground-centered swing.
* OPA1612 (dual): standard JEDEC dual-opamp SOIC-8 pinout
(1=OUTA 2=-INA 3=+INA 4=V- 5=+INB 6=-INB 7=OUTB 8=V+), confirmed against the
OPA164x dual on its datasheet (identical layout). Confirm OPA1612 sheet at layout.
"""
import os
from skidl import *
set_default_tool(KICAD9)
R = Part("Device", "R", dest=TEMPLATE, footprint="Resistor_SMD:R_0805_2012Metric")
C = Part("Device", "C", dest=TEMPLATE, footprint="Capacitor_SMD:C_0805_2012Metric")
# ---- rails / signals ----
p3v3, gnd, p15, n15 = Net("+3V3"), Net("GND"), Net("+15V"), Net("-15V")
for n in (p3v3, gnd, p15, n15):
n.drive = POWER
mclk = Net("MCLK") # low-jitter audio clock -> SCK
i2s_bck, i2s_din, i2s_lrck = Net("I2S_BCK"), Net("I2S_DIN"), Net("I2S_LRCK") # from RP2350
dac_xsmt = Net("DAC_XSMT") # GPIO soft-mute (high=unmute), pulled up
click_out = Net("CLICK_OUT")# filtered click -> Stage 3 summing
# ---- PCM5102A (pinout verified, TI SLAS859C) ----
DAC = Part(name="PCM5102A", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
footprint="Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm",
pins=[
Pin(num=1, name="CPVDD", func=Pin.types.PWRIN),
Pin(num=2, name="CAPP", func=Pin.types.PASSIVE),
Pin(num=3, name="CPGND", func=Pin.types.PWRIN),
Pin(num=4, name="CAPM", func=Pin.types.PASSIVE),
Pin(num=5, name="VNEG", func=Pin.types.PASSIVE),
Pin(num=6, name="OUTL", func=Pin.types.OUTPUT),
Pin(num=7, name="OUTR", func=Pin.types.OUTPUT),
Pin(num=8, name="AVDD", func=Pin.types.PWRIN),
Pin(num=9, name="AGND", func=Pin.types.PWRIN),
Pin(num=10, name="DEMP", func=Pin.types.INPUT),
Pin(num=11, name="FLT", func=Pin.types.INPUT),
Pin(num=12, name="SCK", func=Pin.types.INPUT),
Pin(num=13, name="BCK", func=Pin.types.INPUT),
Pin(num=14, name="DIN", func=Pin.types.INPUT),
Pin(num=15, name="LRCK", func=Pin.types.INPUT),
Pin(num=16, name="FMT", func=Pin.types.INPUT),
Pin(num=17, name="XSMT", func=Pin.types.INPUT),
Pin(num=18, name="LDOO", func=Pin.types.PWROUT),
Pin(num=19, name="DGND", func=Pin.types.PWRIN),
Pin(num=20, name="DVDD", func=Pin.types.PWRIN),
])
dac = DAC(ref="U3")
# ---- OPA1612 dual op-amp (standard dual pinout) ----
OPA2 = Part(name="OPA1612", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
footprint="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
pins=[
Pin(num=1, name="OUTA", func=Pin.types.OUTPUT),
Pin(num=2, name="-INA", func=Pin.types.INPUT),
Pin(num=3, name="+INA", func=Pin.types.INPUT),
Pin(num=4, name="V-", func=Pin.types.PWRIN),
Pin(num=5, name="+INB", func=Pin.types.INPUT),
Pin(num=6, name="-INB", func=Pin.types.INPUT),
Pin(num=7, name="OUTB", func=Pin.types.OUTPUT),
Pin(num=8, name="V+", func=Pin.types.PWRIN),
])
flt = OPA2(ref="U4")
# ---- PCM5102A supplies + grounds (all grounds tied; <0.2V per datasheet) ----
dac["AVDD"] += p3v3
dac["CPVDD"] += p3v3
dac["DVDD"] += p3v3
dac["AGND"] += gnd
dac["DGND"] += gnd
dac["CPGND"] += gnd
for pin in ("AVDD", "CPVDD", "DVDD"):
c = C(value="100nF"); dac[pin] += c[1]; c[2] += gnd
cbulk = C(value="10uF", footprint="Capacitor_SMD:C_1206_3216Metric")
dac["AVDD"] += cbulk[1]; cbulk[2] += gnd
# charge pump: flying cap across CAPP/CAPM, VNEG + LDOO decoupling
cfly = C(value="2.2uF", footprint="Capacitor_SMD:C_0805_2012Metric")
dac["CAPP"] += cfly[1]; dac["CAPM"] += cfly[2]
cvneg = C(value="2.2uF"); dac["VNEG"] += cvneg[1]; cvneg[2] += gnd
cldoo = C(value="1uF"); dac["LDOO"] += cldoo[1]; cldoo[2] += gnd
# control-pin tie-offs: no de-emphasis, normal latency, I2S format
dac["DEMP"] += gnd
dac["FLT"] += gnd
dac["FMT"] += gnd
# soft-mute: GPIO with 10k pull-up to keep un-muted by default
dac["XSMT"] += dac_xsmt
rpull = R(value="10k"); dac_xsmt += rpull[1]; rpull[2] += p3v3
# clocks + data
dac["SCK"] += mclk
dac["BCK"] += i2s_bck
dac["DIN"] += i2s_din
dac["LRCK"] += i2s_lrck
# OUTR unused (mono click on L): give it the datasheet-recommended load
rload = R(value="2.2k"); dac["OUTR"] += rload[1]; rload[2] += gnd
# ---- Sallen-Key reconstruction LPF on OUTL (R=1.5k, Ca=2.2n, Cb=1n ~75kHz) ----
r1, r2 = R(value="1.5k"), R(value="1.5k")
ca = C(value="2.2nF"); cb = C(value="1nF")
nodeA = Net("RC_A")
dac["OUTL"] += r1[1]; r1[2] += nodeA
r2[1] += nodeA; r2[2] += flt["+INA"]
ca[1] += nodeA; ca[2] += flt["OUTA"] # feedback cap
cb[1] += flt["+INA"]; cb[2] += gnd
flt["-INA"] += flt["OUTA"] # unity-gain follower
flt["OUTA"] += click_out
# ---- OPA1612 supplies + decoupling; park unused section B ----
flt["V+"] += p15; flt["V-"] += n15
for rail in (p15, n15):
c = C(value="100nF"); rail += c[1]; c[2] += gnd
flt["+INB"] += gnd; flt["OUTB"] += flt["-INB"] # B = grounded follower (parked)
ERC()
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "stage2_dac.net"))
generate_netlist(file_=out)
print("Stage 2 netlist ->", out)

View file

@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""PM_K-1 audio chain - Stage 3: summing node (selected input + click) (SKiDL).
Run INSIDE the EDA container:
cd hardware/eda && ./run.sh python3 ../eda/circuits/stage3_sum.py
Outputs ERC + a KiCad netlist at hardware/kicad/stage3_sum.net.
WHAT THIS STAGE DOES
Inverting summing amp (OPA1612 section) mixes STAGE1_OUT (the line/instrument input,
unity) and CLICK_OUT (the filtered DAC click). Each source enters through its own
10k resistor into the op-amp's virtual-ground node, so the two never interact. The
"digital mix" lives upstream: click level is set by the DAC; the input passes at
unity. Output MIX_OUT -> Stage 4 balanced driver.
Vout = -(STAGE1_OUT + CLICK_OUT) (Rf = Ri = 10k)
POLARITY: an inverting summer flips phase. That is corrected for free at the Stage 4
balanced driver by assigning hot/cold accordingly (absolute polarity preserved).
OPA1612 dual: standard JEDEC pinout (1=OUTA 2=-INA 3=+INA 4=V- 5=+INB 6=-INB 7=OUTB
8=V+). At INTEGRATION this summer can use the PARKED 2nd half of the Stage 2 filter's
OPA1612 (U4) instead of a separate package -- noted for the merge step.
"""
import os
from skidl import *
set_default_tool(KICAD9)
R = Part("Device", "R", dest=TEMPLATE, footprint="Resistor_SMD:R_0805_2012Metric")
C = Part("Device", "C", dest=TEMPLATE, footprint="Capacitor_SMD:C_0805_2012Metric")
p15, n15, gnd = Net("+15V"), Net("-15V"), Net("GND")
for n in (p15, n15, gnd):
n.drive = POWER
stage1_out = Net("STAGE1_OUT") # from Stage 1b relay (selected input)
click_out = Net("CLICK_OUT") # from Stage 2 reconstruction filter
mix_out = Net("MIX_OUT") # -> Stage 4 balanced driver
OPA2 = Part(name="OPA1612", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
footprint="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
pins=[
Pin(num=1, name="OUTA", func=Pin.types.OUTPUT),
Pin(num=2, name="-INA", func=Pin.types.INPUT),
Pin(num=3, name="+INA", func=Pin.types.INPUT),
Pin(num=4, name="V-", func=Pin.types.PWRIN),
Pin(num=5, name="+INB", func=Pin.types.INPUT),
Pin(num=6, name="-INB", func=Pin.types.INPUT),
Pin(num=7, name="OUTB", func=Pin.types.OUTPUT),
Pin(num=8, name="V+", func=Pin.types.PWRIN),
])
u = OPA2(ref="U5")
# inverting summer: each source -> 10k -> virtual-ground (-INA); Rf 10k; +INA -> gnd
ri_in, ri_clk, rf = R(value="10k"), R(value="10k"), R(value="10k")
stage1_out += ri_in[1]; ri_in[2] += u["-INA"]
click_out += ri_clk[1]; ri_clk[2] += u["-INA"]
rf[1] += u["-INA"]; rf[2] += u["OUTA"]
u["+INA"] += gnd
u["OUTA"] += mix_out
# supplies + decoupling; park unused section B
u["V+"] += p15; u["V-"] += n15
for rail in (p15, n15):
c = C(value="100nF"); rail += c[1]; c[2] += gnd
u["+INB"] += gnd; u["OUTB"] += u["-INB"] # parked follower
ERC()
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "stage3_sum.net"))
generate_netlist(file_=out)
print("Stage 3 netlist ->", out)

View file

@ -0,0 +1,113 @@
#!/usr/bin/env python3
"""PM_K-1 audio chain - Stage 4: balanced output driver (THAT1646) + mute + ground-lift.
Run INSIDE the EDA container:
cd hardware/eda && ./run.sh python3 ../eda/circuits/stage4_driver.py
Outputs ERC + a KiCad netlist at hardware/kicad/stage4_driver.net. This closes the
audio chain: MIX_OUT -> level trim -> THAT1646 -> build-out -> mute relay -> balanced
output on the analog interconnect; plus the ground-lift relay.
PINOUT VERIFIED (THAT doc 600078 rev 07, SO-8):
THAT1646: 1=Out- 2=Sns- 3=Gnd 4=In 5=Vee(V-) 6=Vcc(V+) 7=Sns+ 8=Out+.
Fixed +6 dB differential gain; sense pins tie to the output pins (local sense) with
the 47 ohm build-out OUTSIDE the loop for cable stability. Supply 4-18V (we use +/-15).
TQ2SA relay pinout per stage1b (coil 1/10; pole1 COM=3 NC=4 NO=2; pole2 COM=8 NC=7 NO=9).
KEY CHOICES
* Level cal: the THAT1646 gain is FIXED (+6 dB), so the calibration trim is a 25-turn
pot ahead of it (DAC full-scale -> +4 dBu, accounting for summer + the +6 dB).
* Phase: Stage 3's summer inverted the signal; corrected here by taking AOUT_HOT from
Out- and AOUT_COLD from Out+ (absolute polarity preserved).
* Mute (K2): fail-safe -- de-energized shorts both legs to GND (after the 47 ohm
build-out, so the driver is current-limited). Energized = un-muted. Driven by the
hardware rail-supervisor via K2_DRV (in the power block); MCU can also assert.
* Ground-lift (K3): de-energized = bonded (GND<->CHASSIS); energize to LIFT. Soft-lift
100 ohm || 10 nF across the contact keeps an RF/safety path. A face panel switch sits
in series downstream (CHASSIS pin on the interconnect) -> both-must-close to bond.
"""
import os
from skidl import *
set_default_tool(KICAD9)
R = Part("Device", "R", dest=TEMPLATE, footprint="Resistor_SMD:R_0805_2012Metric")
C = Part("Device", "C", dest=TEMPLATE, footprint="Capacitor_SMD:C_0805_2012Metric")
p15, n15, gnd, p5 = Net("+15V"), Net("-15V"), Net("GND"), Net("+5V")
for n in (p15, n15, gnd, p5):
n.drive = POWER
mix_out = Net("MIX_OUT") # from Stage 3 summer (inverted)
aout_hot, aout_cold = Net("AOUT_HOT"), Net("AOUT_COLD") # -> analog interconnect
chassis = Net("CHASSIS") # shield -> interconnect (face panel switch + chassis)
k2_drv, k3_drv = Net("K2_DRV"), Net("K3_DRV") # relay coil drives
# ---- THAT1646 balanced line driver (pinout verified) ----
DRV = Part(name="THAT1646", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
footprint="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
pins=[
Pin(num=1, name="OUT-", func=Pin.types.OUTPUT),
Pin(num=2, name="SNS-", func=Pin.types.INPUT),
Pin(num=3, name="GND", func=Pin.types.PWRIN),
Pin(num=4, name="IN", func=Pin.types.INPUT),
Pin(num=5, name="V-", func=Pin.types.PWRIN),
Pin(num=6, name="V+", func=Pin.types.PWRIN),
Pin(num=7, name="SNS+", func=Pin.types.INPUT),
Pin(num=8, name="OUT+", func=Pin.types.OUTPUT),
])
drv = DRV(ref="U6")
# ---- TQ2SA relay (verified pinout, reused def) ----
def tq2sa(ref):
p = Part(name="TQ2SA-5V", tool=SKIDL, dest=TEMPLATE, ref_prefix="K",
footprint="Relay_SMD:Relay_DPDT_Panasonic_TQ2-SA",
pins=[
Pin(num=1, name="COIL_A", func=Pin.types.PASSIVE),
Pin(num=10, name="COIL_B", func=Pin.types.PASSIVE),
Pin(num=3, name="P1_COM", func=Pin.types.PASSIVE),
Pin(num=4, name="P1_NC", func=Pin.types.PASSIVE),
Pin(num=2, name="P1_NO", func=Pin.types.PASSIVE),
Pin(num=8, name="P2_COM", func=Pin.types.PASSIVE),
Pin(num=7, name="P2_NC", func=Pin.types.PASSIVE),
Pin(num=9, name="P2_NO", func=Pin.types.PASSIVE),
Pin(num=5, name="NC5", func=Pin.types.NOCONNECT),
Pin(num=6, name="NC6", func=Pin.types.NOCONNECT),
])
return p(ref=ref)
k2 = tq2sa("K2") # mute
k3 = tq2sa("K3") # ground-lift
# ---- level-cal trim (25-turn pot): MIX_OUT (top) / GND (bottom) / wiper -> THAT1646 IN ----
RV = Part("Device", "R_Potentiometer", dest=TEMPLATE,
footprint="Potentiometer_THT:Potentiometer_Bourns_3296W_Vertical")
rv1 = RV(value="10k", ref="RV1")
mix_out += rv1[1]; rv1[3] += gnd; rv1[2] += drv["IN"]
# ---- THAT1646: sense ties (local) + supplies ----
drv["SNS-"] += drv["OUT-"]
drv["SNS+"] += drv["OUT+"]
drv["GND"] += gnd
drv["V+"] += p15; drv["V-"] += n15
for rail in (p15, n15):
c = C(value="100nF"); rail += c[1]; c[2] += gnd
# ---- 47 ohm build-out (phase-corrected: HOT<-OUT-, COLD<-OUT+) ----
rbo_h, rbo_c = R(value="47"), R(value="47")
drv["OUT-"] += rbo_h[1]; rbo_h[2] += aout_hot
drv["OUT+"] += rbo_c[1]; rbo_c[2] += aout_cold
# ---- mute relay K2: de-energized shorts both legs to GND (fail-safe) ----
k2["P1_COM"] += aout_hot; k2["P1_NC"] += gnd # de-energized: hot -> GND (muted)
k2["P2_COM"] += aout_cold; k2["P2_NC"] += gnd # de-energized: cold -> GND
k2["COIL_A"] += p5; k2["COIL_B"] += k2_drv # energize (rails OK) = un-mute
# ---- ground-lift K3: de-energized bonds GND<->CHASSIS; energize to lift ----
k3["P1_COM"] += gnd; k3["P1_NC"] += chassis # de-energized: bonded
rlift = R(value="100"); clift = C(value="10nF")
gnd += rlift[1]; rlift[2] += chassis # soft-lift: 100 ohm || 10 nF
clift[1] += gnd; clift[2] += chassis
k3["COIL_A"] += p5; k3["COIL_B"] += k3_drv
ERC()
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "stage4_driver.net"))
generate_netlist(file_=out)
print("Stage 4 netlist ->", out)

76
hardware/eda/gen_bom.py Normal file
View file

@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""Generate a consolidated BOM from the integrated board.net.
Run INSIDE the EDA container:
cd hardware/eda && ./run.sh python3 ../eda/gen_bom.py
Reads hardware/kicad/board.net, groups like parts, attaches MPNs, writes
hardware/BOM_board.csv (authoritative part list keyed to the board.net references).
"""
import re, csv, os, collections
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
NET = os.path.join(ROOT, "hardware/kicad/board.net")
OUT = os.path.join(ROOT, "hardware/BOM_board.csv")
# value -> (manufacturer, MPN, note)
MPN = {
"RP2350A": ("Raspberry Pi", "RP2350A", "MCU (QFN-60); via KiCad lib symbol"),
"W25Q128JVS": ("Winbond", "W25Q128JVSIQ", "16MB QSPI flash"),
"PCM5102A": ("TI", "PCM5102APWR", "I2S DAC; SCK->GND (MCLK-less)"),
"THAT1240": ("THAT Corp", "THAT1240S08-U", "0dB balanced line receiver; 2nd-src INA134/SSM2141"),
"THAT1646": ("THAT Corp", "THAT1646S08-U", "balanced line driver, +6dB; 2nd-src DRV134/SSM2142"),
"OPA1641": ("TI", "OPA1641AID", "JFET Hi-Z DI buffer"),
"OPA1612": ("TI", "OPA1612AIDR", "dual: recon filter + summer"),
"TPS65131": ("TI", "TPS65131RGER", "dual boost/inverter -> +/-18V"),
"TPS7A4901": ("TI", "TPS7A4901DGNR", "+15V ultra-low-noise LDO; confirm Vfb"),
"TPS7A3001": ("TI", "TPS7A3001DGNR", "-15V ultra-low-noise LDO; confirm Vfb"),
"AP2112K-3.3": ("Diodes", "AP2112K-3.3TRG1", "3V3 LDO; confirm SOT-23-5 pinout"),
"ULN2003A": ("TI", "ULN2003ADR", "shared relay driver (3 of 7 ch used)"),
"RV-8803-C7": ("Micro Crystal", "RV-8803-C7", "I2C RTC; confirm footprint"),
"LM393": ("TI", "LM393DR", "DNP - SIG/CLIP comparator"),
"H11L1": ("Vishay", "H11L1M", "DNP - MIDI opto IN; confirm pinout"),
"74LVC14": ("Nexperia", "74LVC14APW", "DNP - MIDI OUT/THRU buffer"),
"PAM8302A": ("Diodes", "PAM8302AASCR", "DNP - monitor speaker amp"),
"USBLC6-2SC6": ("STMicro", "USBLC6-2SC6", "USB ESD"),
"TQ2SA-5V": ("Panasonic", "TQ2SA-5V", "DPDT signal relay, gold; K1 select/K2 mute/K3 gnd-lift"),
"12MHz": ("Abracon", "ABM8-272-12.000MHZ-T3", "RP2350 crystal; confirm load caps"),
"USB_C_Receptacle": ("GCT", "USB4085-GF-A", "USB-C; 24-pin sym vs 16-pin part - resolve at layout"),
"CR2032": ("Keystone", "1066", "coin-cell holder (RTC backup)"),
"MBRM120": ("onsemi", "MBRM120ET3G", "Schottky rectifier (switcher)"),
"BAT54": ("onsemi", "BAT54", "Schottky (RTC diode-OR / peak-detect)"),
"1N4148WS": ("onsemi", "1N4148WS", "fast diode (input clamps / MIDI protect)"),
"4.7uH": ("Wurth/EPCOS", "7447789004 / B82462-G4472", "switcher inductor"),
"3.3uH": ("Abracon", "AOTA-B201610S3R3-101-T", "RP2350 core SMPS inductor"),
"600R": ("Murata", "BLM18KG..", "ferrite bead (USB VBUS input)"),
}
comps = re.findall(r'\(comp\s*\(ref "([^"]+)"\)\s*\(value "([^"]+)"\)(.*?)\(libsource', open(NET).read(), re.S)
def fp(blk):
m = re.search(r'\(footprint "([^"]+)"', blk); return m.group(1) if m else ""
groups = collections.OrderedDict()
for ref, val, blk in comps:
key = (val, fp(blk))
groups.setdefault(key, []).append(ref)
def sortkey(r):
m = re.match(r'([A-Za-z]+)(\d+)', r); return (m.group(1), int(m.group(2))) if m else (r, 0)
rows = []
for (val, foot), refs in groups.items():
refs = sorted(refs, key=sortkey)
man, mpn, note = MPN.get(val, ("", "", ""))
if "Potentiometer" in foot: # the level-cal trimmer (value "10k" but a pot)
man, mpn, note = ("Bourns", "3296W-1-103LF", "output level cal trim (25-turn)")
# heuristic DNP flag
dnp = any(s in note for s in ("DNP",))
rows.append([len(refs), " ".join(refs), val, foot, man, mpn, ("DNP" if dnp else ""), note])
rows.sort(key=lambda r: (r[6] == "DNP", -r[0], r[2])) # populated first, then by qty
with open(OUT, "w", newline="") as f:
w = csv.writer(f)
w.writerow(["Qty", "Refs", "Value", "Footprint", "Manufacturer", "MPN", "Populate", "Notes"])
w.writerows(rows)
print("wrote", OUT)
print("line items:", len(rows), " total placements:", sum(r[0] for r in rows))

14
hardware/eda/make_svg.py Normal file
View file

@ -0,0 +1,14 @@
#!/usr/bin/env python3
"""Render one SKiDL block as a schematic SVG (via netlistsvg).
Usage (inside the container): python3 make_svg.py <block.py> <out_basename>
Runs the block (so its circuit is built, with __file__ set via runpy), then calls
SKiDL generate_svg() on the resulting default circuit.
"""
import sys, runpy, os
from skidl import generate_svg
block, out = sys.argv[1], sys.argv[2]
runpy.run_path(block, run_name="__main__") # builds default_circuit (also runs its ERC/netlist)
generate_svg(file_=out) # netlistsvg -> <out>.svg
print("SVG ->", out + ".svg")

24
hardware/eda/run.sh Executable file
View file

@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Build (first time) and run the PM_K-1 EDA container with the repo mounted.
#
# ./run.sh # interactive shell in hardware/kicad/
# ./run.sh kicad-cli sch erc pm_k1_core.kicad_sch
# ./run.sh ngspice -b sim/some.cir
#
# Override the runtime with RUNTIME=docker ./run.sh ...
set -euo pipefail
IMG="pmk1-eda:9.0"
EDA_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # hardware/eda
REPO_DIR="$(cd "$EDA_DIR/../.." && pwd)" # repo root
RUNTIME="${RUNTIME:-podman}"
if ! "$RUNTIME" image inspect "$IMG" >/dev/null 2>&1; then
echo ">> building $IMG (first run, a few minutes)…" >&2
"$RUNTIME" build -t "$IMG" "$EDA_DIR"
fi
# Mount the whole repo so KiCad/ngspice see hardware/ ; land in hardware/kicad.
flags=(--rm -v "$REPO_DIR":/work:Z -w /work/hardware/kicad)
[[ -t 0 && $# -eq 0 ]] && flags+=(-it)
exec "$RUNTIME" run "${flags[@]}" "$IMG" "${@:-bash}"

2
hardware/eda/schematics/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.json
*_skin.svg

View file

@ -0,0 +1,34 @@
# Schematic views (auto-generated)
Per-block schematic images of the PM_K-1 core board, rendered from the SKiDL circuits with
**netlistsvg**. Open any `.svg` in a web browser.
**What these are:** functional, auto-routed schematics (boxes + wires) for *tracing
connections* — not hand-arranged, pretty EE drawings. Each file is one block, so it's
readable; the full 167-part board would be an unreadable hairball.
| File | Block |
|---|---|
| `stage1_input.svg` | balanced line receiver + protection |
| `stage1b_select.svg` | Hi-Z instrument DI buffer + select relay |
| `stage2_dac.svg` | PCM5102A DAC + reconstruction filter |
| `stage3_sum.svg` | summing node |
| `stage4_driver.svg` | balanced output driver + mute + ground-lift |
| `audio_chain.svg` | the five audio stages integrated (busier) |
| `power_tree.svg` | ±18 V switcher → ±15 V LDOs + 3V3 |
| `mcu_core.svg` | RP2350 + flash + crystal + USB + boot/SWD |
| `rtc.svg` | RV-8803 RTC + coin-cell backup |
| `midi.svg` | DNP opto IN + buffered OUT/THRU |
| `indicator.svg` | SIG/CLIP detector |
| `speaker.svg` | monitor speaker amp |
**`interconnect` is intentionally absent** — netlistsvg's layout engine errors on the
24-pin USB-C + many headers. Its connections are pure pin-to-net mapping, fully tabulated
in `hardware/DESIGN.md` §7 and `circuits/interconnect.py`.
**Regenerate** (inside the container): for each block,
`./run.sh python3 ../eda/make_svg.py circuits/<block>.py schematics/<block>` then
`netlistsvg schematics/<block>.json -o schematics/<block>.svg --skin schematics/<block>_skin.svg`.
For the authoritative, hand-written design intent see the `circuits/*.py` files and
`hardware/DESIGN.md` block diagrams; for the connection list see `hardware/kicad/board.net`.

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 357 KiB

View file

@ -0,0 +1,407 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:s="https://github.com/nturley/netlistsvg" width="733.5500000000001" height="472.572"><rect id="pm_k1-bg" x="0" y="0" width="100%" height="100%" fill="#ffffff"/>
<style>svg {
stroke: #000;
fill: none;
stroke-linejoin: round;
stroke-linecap: round;
}
text {
fill: #000;
stroke: none;
font-size: 10px;
font-weight: bold;
font-family: "Courier New", monospace;
}
.skidl_text {
fill: #999;
stroke: none;
font-weight: bold;
font-family: consolas, "Courier New", monospace;
}
.pin_num_text {
fill: #840000;
}
.pin_name_text {
fill: #008484;
}
.net_name_text {
font-style: italic;
fill: #840084;
}
.part_text {
fill: #840000;
}
.part_ref_text {
fill: #008484;
}
.part_name_text {
fill: #008484;
}
.pen_fill {
fill: #840000;
}
.background_fill {
fill: #FFFFC2
}
.nodelabel {
text-anchor: middle;
}
.inputPortLabel {
text-anchor: end;
}
.splitjoinBody {
fill: #000;
}
.symbol {
stroke-linejoin: round;
stroke-linecap: round;
stroke: #840000;
}
.detail {
stroke-linejoin: round;
stroke-linecap: round;
fill: #000;
}</style>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(338.672,327.42)" id="cell_C1">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C1 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C1 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C1 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C1 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C1</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(599.618,307.42)" id="cell_C2">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C2 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C2 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C2 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C2 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C2</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(510.60400000000004,317.42)" id="cell_C3">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C3 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C3 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C3 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C3 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C3</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="D_Schottky_1_" s:width="109.728" s:height="56.083" transform="translate(41.754,14.342)" id="cell_D1">
<s:alias val="D_Schottky_1_"/>
<polyline points="18.288,21.946 18.288,15.850 24.384,15.850 24.384,40.234 30.480,40.234 30.480,34.138" style="stroke-width:2.438" class="cell_D1 symbol none"/>
<polyline points="48.768,15.850 48.768,40.234 24.384,28.042 48.768,15.850" style="stroke-width:2.438" class="cell_D1 symbol none"/>
<polyline points="48.768,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D1 symbol none"/>
<polyline points="0.000,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D1 symbol none"/>
<text class="pin_num_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="end">1</text>
<text class="pin_name_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">K</text>
<g s:x="0.000" s:y="28.042" s:pid="1" s:position="left"/>
<polyline points="73.152,28.042 48.768,28.042" style="stroke-width:0.960" class="cell_D1 symbol none"/>
<text class="pin_num_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="start">2</text>
<text class="pin_name_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">A</text>
<g s:x="73.152" s:y="28.042" s:pid="2" s:position="right"/>
<text class="part_ref_text" x="36.576" y="3.658" transform="rotate(0 36.576 3.658)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">D1</text>
<text class="part_name_text" x="36.576" y="52.426" transform="rotate(0 36.576 52.426)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">BAT54</text>
</g>
<g s:type="D_Schottky_1_" s:width="109.728" s:height="56.083" transform="translate(447.44000000000005,219.99400000000003)" id="cell_D2">
<s:alias val="D_Schottky_1_"/>
<polyline points="18.288,21.946 18.288,15.850 24.384,15.850 24.384,40.234 30.480,40.234 30.480,34.138" style="stroke-width:2.438" class="cell_D2 symbol none"/>
<polyline points="48.768,15.850 48.768,40.234 24.384,28.042 48.768,15.850" style="stroke-width:2.438" class="cell_D2 symbol none"/>
<polyline points="48.768,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D2 symbol none"/>
<polyline points="0.000,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D2 symbol none"/>
<text class="pin_num_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="end">1</text>
<text class="pin_name_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">K</text>
<g s:x="0.000" s:y="28.042" s:pid="1" s:position="left"/>
<polyline points="73.152,28.042 48.768,28.042" style="stroke-width:0.960" class="cell_D2 symbol none"/>
<text class="pin_num_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="start">2</text>
<text class="pin_name_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">A</text>
<g s:x="73.152" s:y="28.042" s:pid="2" s:position="right"/>
<text class="part_ref_text" x="36.576" y="3.658" transform="rotate(0 36.576 3.658)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">D2</text>
<text class="part_name_text" x="36.576" y="52.426" transform="rotate(0 36.576 52.426)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">BAT54</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(22,91.768)" id="cell_R1">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R1 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R1 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R1 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R1</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100k</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(64.918,347.42)" id="cell_R2">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R2 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R2 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R2 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R2</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1Meg</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(12,357.42)" id="cell_R3">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R3 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R3 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R3 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R3</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">10k</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(427.68600000000004,317.42)" id="cell_R4">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R4 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R4 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R4 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R4</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100k</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(255.75400000000002,327.42)" id="cell_R5">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R5 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R5 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R5 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R5</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100k</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(202.836,337.42)" id="cell_R6">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R6 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R6 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R6 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R6</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">68k</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(688.6320000000001,367.42)" id="cell_R7">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R7 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R7 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R7 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R7</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">10k</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(149.918,357.42)" id="cell_R8">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R8 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R8 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R8 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R8</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">10k</text>
</g>
<g s:type="generic" s:width="30" s:height="40" transform="translate(111.672,164.42000000000002)" id="cell_U11">
<text x="15" y="-4" class="nodelabel cell_U11" s:attribute="ref">LM393_1_</text>
<rect width="30" height="120" x="0" y="0" s:generic="body" class="cell_U11"/>
<g transform="translate(0,10)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U11~2">
<text x="-3" y="-4" class="inputPortLabel cell_U11">2</text>
</g>
<g transform="translate(0,30)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U11~3">
<text x="-3" y="-4" class="inputPortLabel cell_U11">3</text>
</g>
<g transform="translate(0,50)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U11~4">
<text x="-3" y="-4" class="inputPortLabel cell_U11">4</text>
</g>
<g transform="translate(0,70)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U11~5">
<text x="-3" y="-4" class="inputPortLabel cell_U11">5</text>
</g>
<g transform="translate(0,90)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U11~6">
<text x="-3" y="-4" class="inputPortLabel cell_U11">6</text>
</g>
<g transform="translate(0,110)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U11~8">
<text x="-3" y="-4" class="inputPortLabel cell_U11">8</text>
</g>
<g transform="translate(30,10)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U11~1">
<text x="5" y="-4" class="cell_U11">1</text>
</g>
<g transform="translate(30,30)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U11~7">
<text x="5" y="-4" class="cell_U11">7</text>
</g>
</g>
<line x1="111.672" x2="74.672" y1="274.92" y2="274.92" class="net_10"/>
<line x1="74.672" x2="74.672" y1="274.92" y2="317.42" class="net_10"/>
<line x1="74.672" x2="358.17900000000003" y1="317.42" y2="317.42" class="net_10"/>
<circle cx="74.672" cy="317.42" r="2" style="fill:#000" class="net_10"/>
<line x1="358.17900000000003" x2="358.17900000000003" y1="317.42" y2="327.42" class="net_10"/>
<line x1="111.672" x2="74.672" y1="274.92" y2="274.92" class="net_10"/>
<line x1="74.672" x2="74.672" y1="274.92" y2="347.42" class="net_10"/>
<line x1="111.672" x2="74.672" y1="274.92" y2="274.92" class="net_10"/>
<line x1="74.672" x2="74.672" y1="274.92" y2="317.42" class="net_10"/>
<line x1="74.672" x2="265.50800000000004" y1="317.42" y2="317.42" class="net_10"/>
<circle cx="265.50800000000004" cy="317.42" r="2" style="fill:#000" class="net_10"/>
<line x1="265.50800000000004" x2="265.50800000000004" y1="317.42" y2="327.42" class="net_10"/>
<line x1="41.754" x2="31.753999999999998" y1="42.384" y2="42.384" class="net_8"/>
<line x1="31.753999999999998" x2="31.753999999999998" y1="42.384" y2="81.768" class="net_8"/>
<line x1="31.753999999999998" x2="619.125" y1="81.768" y2="81.768" class="net_8"/>
<circle cx="31.753999999999998" cy="81.768" r="2" style="fill:#000" class="net_8"/>
<line x1="619.125" x2="619.125" y1="81.768" y2="307.42" class="net_8"/>
<line x1="41.754" x2="31.753999999999998" y1="42.384" y2="42.384" class="net_8"/>
<line x1="31.753999999999998" x2="31.753999999999998" y1="42.384" y2="91.768" class="net_8"/>
<line x1="111.672" x2="94.672" y1="194.92000000000002" y2="194.92000000000002" class="net_8"/>
<line x1="94.672" x2="94.672" y1="194.92000000000002" y2="297.42" class="net_8"/>
<line x1="94.672" x2="619.125" y1="297.42" y2="297.42" class="net_8"/>
<circle cx="619.125" cy="297.42" r="2" style="fill:#000" class="net_8"/>
<circle cx="94.672" cy="194.92000000000002" r="2" style="fill:#000" class="net_8"/>
<line x1="619.125" x2="619.125" y1="297.42" y2="307.42" class="net_8"/>
<line x1="111.672" x2="94.672" y1="194.92000000000002" y2="194.92000000000002" class="net_8"/>
<line x1="94.672" x2="94.672" y1="194.92000000000002" y2="81.768" class="net_8"/>
<line x1="94.672" x2="31.753999999999998" y1="81.768" y2="81.768" class="net_8"/>
<circle cx="94.672" cy="81.768" r="2" style="fill:#000" class="net_8"/>
<circle cx="94.672" cy="194.92000000000002" r="2" style="fill:#000" class="net_8"/>
<line x1="31.753999999999998" x2="31.753999999999998" y1="81.768" y2="91.768" class="net_8"/>
<line x1="447.44000000000005" x2="437.44000000000005" y1="248.03600000000003" y2="248.036" class="net_2"/>
<line x1="437.44000000000005" x2="437.44000000000005" y1="248.036" y2="307.42" class="net_2"/>
<line x1="437.44000000000005" x2="530.111" y1="307.42" y2="307.42" class="net_2"/>
<line x1="530.111" x2="530.111" y1="307.42" y2="317.42" class="net_2"/>
<line x1="447.44000000000005" x2="437.44000000000005" y1="248.03600000000003" y2="248.036" class="net_2"/>
<line x1="437.44000000000005" x2="437.44000000000005" y1="248.036" y2="317.42" class="net_2"/>
<line x1="111.672" x2="84.672" y1="234.92000000000002" y2="234.92000000000002" class="net_2"/>
<line x1="84.672" x2="84.672" y1="234.92000000000002" y2="307.42" class="net_2"/>
<line x1="84.672" x2="530.111" y1="307.42" y2="307.42" class="net_2"/>
<line x1="530.111" x2="530.111" y1="307.42" y2="317.42" class="net_2"/>
<line x1="111.672" x2="84.672" y1="234.92000000000002" y2="234.92000000000002" class="net_2"/>
<line x1="84.672" x2="84.672" y1="234.92000000000002" y2="307.42" class="net_2"/>
<line x1="84.672" x2="437.44000000000005" y1="307.42" y2="307.42" class="net_2"/>
<circle cx="437.44000000000005" cy="307.42" r="2" style="fill:#000" class="net_2"/>
<line x1="437.44000000000005" x2="437.44000000000005" y1="307.42" y2="317.42" class="net_2"/>
<line x1="74.672" x2="74.672" y1="420.572" y2="430.572" class="net_6"/>
<line x1="74.672" x2="54.918" y1="430.572" y2="430.572" class="net_6"/>
<line x1="54.918" x2="54.918" y1="430.572" y2="347.42" class="net_6"/>
<line x1="54.918" x2="21.753999999999998" y1="347.42" y2="347.42" class="net_6"/>
<line x1="21.753999999999998" x2="21.753999999999998" y1="347.42" y2="174.92000000000002" class="net_6"/>
<circle cx="21.753999999999998" cy="347.42" r="2" style="fill:#000" class="net_6"/>
<line x1="21.753999999999998" x2="111.672" y1="174.92000000000002" y2="174.92000000000002" class="net_6"/>
<line x1="111.672" x2="21.753999999999998" y1="174.92000000000002" y2="174.92000000000002" class="net_6"/>
<line x1="21.753999999999998" x2="21.753999999999998" y1="174.92000000000002" y2="357.42" class="net_6"/>
<line x1="265.50800000000004" x2="265.50800000000004" y1="400.572" y2="420.572" class="net_1"/>
<line x1="265.50800000000004" x2="245.75400000000002" y1="420.572" y2="420.572" class="net_1"/>
<line x1="245.75400000000002" x2="245.75400000000002" y1="420.572" y2="327.42" class="net_1"/>
<line x1="245.75400000000002" x2="41.754" y1="327.42" y2="327.42" class="net_1"/>
<line x1="41.754" x2="41.754" y1="327.42" y2="254.92000000000002" class="net_1"/>
<line x1="41.754" x2="111.672" y1="254.92000000000002" y2="254.92000000000002" class="net_1"/>
<line x1="111.672" x2="41.754" y1="254.92000000000002" y2="254.92000000000002" class="net_1"/>
<line x1="41.754" x2="41.754" y1="254.92000000000002" y2="327.42" class="net_1"/>
<line x1="41.754" x2="212.59" y1="327.42" y2="327.42" class="net_1"/>
<circle cx="212.59" cy="327.42" r="2" style="fill:#000" class="net_1"/>
<line x1="212.59" x2="212.59" y1="327.42" y2="337.42" class="net_1"/>
<line x1="142.672" x2="169.672" y1="174.92000000000002" y2="174.92000000000002" class="net_7"/>
<line x1="169.672" x2="169.672" y1="174.92000000000002" y2="287.42" class="net_7"/>
<line x1="169.672" x2="698.3860000000001" y1="287.42" y2="287.42" class="net_7"/>
<line x1="698.3860000000001" x2="698.3860000000001" y1="287.42" y2="367.42" class="net_7"/>
<line x1="142.672" x2="159.672" y1="194.92000000000002" y2="194.92000000000002" class="net_5"/>
<line x1="159.672" x2="159.672" y1="194.92000000000002" y2="357.42" class="net_5"/>
<line x1="358.17900000000003" x2="358.17900000000003" y1="400.572" y2="430.572" class="net_3"/>
<line x1="358.17900000000003" x2="192.836" y1="430.572" y2="430.572" class="net_3"/>
<line x1="192.836" x2="192.836" y1="430.572" y2="337.42" class="net_3"/>
<line x1="192.836" x2="31.753999999999998" y1="337.42" y2="337.42" class="net_3"/>
<line x1="31.753999999999998" x2="31.753999999999998" y1="337.42" y2="214.92000000000002" class="net_3"/>
<circle cx="358.17900000000003" cy="430.572" r="2" style="fill:#000" class="net_3"/>
<circle cx="31.753999999999998" cy="214.92000000000002" r="2" style="fill:#000" class="net_3"/>
<line x1="31.753999999999998" x2="111.672" y1="214.92000000000002" y2="214.92000000000002" class="net_3"/>
<line x1="619.125" x2="619.125" y1="380.572" y2="430.572" class="net_3"/>
<line x1="619.125" x2="192.836" y1="430.572" y2="430.572" class="net_3"/>
<line x1="192.836" x2="192.836" y1="430.572" y2="337.42" class="net_3"/>
<line x1="192.836" x2="31.753999999999998" y1="337.42" y2="337.42" class="net_3"/>
<line x1="31.753999999999998" x2="31.753999999999998" y1="337.42" y2="214.92000000000002" class="net_3"/>
<circle cx="31.753999999999998" cy="214.92000000000002" r="2" style="fill:#000" class="net_3"/>
<line x1="31.753999999999998" x2="111.672" y1="214.92000000000002" y2="214.92000000000002" class="net_3"/>
<line x1="530.111" x2="530.111" y1="390.572" y2="440.572" class="net_3"/>
<line x1="530.111" x2="107.836" y1="440.572" y2="440.572" class="net_3"/>
<line x1="107.836" x2="107.836" y1="440.572" y2="337.42" class="net_3"/>
<line x1="107.836" x2="31.753999999999998" y1="337.42" y2="337.42" class="net_3"/>
<line x1="31.753999999999998" x2="31.753999999999998" y1="337.42" y2="214.92000000000002" class="net_3"/>
<circle cx="31.753999999999998" cy="214.92000000000002" r="2" style="fill:#000" class="net_3"/>
<line x1="31.753999999999998" x2="111.672" y1="214.92000000000002" y2="214.92000000000002" class="net_3"/>
<line x1="31.753999999999998" x2="31.753999999999998" y1="164.92000000000002" y2="214.92000000000002" class="net_3"/>
<circle cx="31.753999999999998" cy="214.92000000000002" r="2" style="fill:#000" class="net_3"/>
<line x1="31.753999999999998" x2="111.672" y1="214.92000000000002" y2="214.92000000000002" class="net_3"/>
<line x1="21.753999999999998" x2="21.753999999999998" y1="430.572" y2="440.572" class="net_3"/>
<line x1="21.753999999999998" x2="107.836" y1="440.572" y2="440.572" class="net_3"/>
<line x1="107.836" x2="107.836" y1="440.572" y2="337.42" class="net_3"/>
<line x1="107.836" x2="31.753999999999998" y1="337.42" y2="337.42" class="net_3"/>
<line x1="31.753999999999998" x2="31.753999999999998" y1="337.42" y2="214.92000000000002" class="net_3"/>
<circle cx="107.836" cy="337.42" r="2" style="fill:#000" class="net_3"/>
<circle cx="107.836" cy="440.572" r="2" style="fill:#000" class="net_3"/>
<circle cx="31.753999999999998" cy="214.92000000000002" r="2" style="fill:#000" class="net_3"/>
<line x1="31.753999999999998" x2="111.672" y1="214.92000000000002" y2="214.92000000000002" class="net_3"/>
<line x1="437.44000000000005" x2="437.44000000000005" y1="390.572" y2="430.572" class="net_3"/>
<line x1="437.44000000000005" x2="192.836" y1="430.572" y2="430.572" class="net_3"/>
<line x1="192.836" x2="192.836" y1="430.572" y2="337.42" class="net_3"/>
<line x1="192.836" x2="31.753999999999998" y1="337.42" y2="337.42" class="net_3"/>
<line x1="31.753999999999998" x2="31.753999999999998" y1="337.42" y2="214.92000000000002" class="net_3"/>
<circle cx="437.44000000000005" cy="430.572" r="2" style="fill:#000" class="net_3"/>
<circle cx="31.753999999999998" cy="214.92000000000002" r="2" style="fill:#000" class="net_3"/>
<line x1="31.753999999999998" x2="111.672" y1="214.92000000000002" y2="214.92000000000002" class="net_3"/>
<line x1="212.59" x2="212.59" y1="410.572" y2="430.572" class="net_3"/>
<line x1="212.59" x2="192.836" y1="430.572" y2="430.572" class="net_3"/>
<line x1="192.836" x2="192.836" y1="430.572" y2="337.42" class="net_3"/>
<line x1="192.836" x2="31.753999999999998" y1="337.42" y2="337.42" class="net_3"/>
<line x1="31.753999999999998" x2="31.753999999999998" y1="337.42" y2="214.92000000000002" class="net_3"/>
<circle cx="212.59" cy="430.572" r="2" style="fill:#000" class="net_3"/>
<circle cx="31.753999999999998" cy="214.92000000000002" r="2" style="fill:#000" class="net_3"/>
<line x1="31.753999999999998" x2="111.672" y1="214.92000000000002" y2="214.92000000000002" class="net_3"/>
<line x1="698.3860000000001" x2="698.3860000000001" y1="440.572" y2="450.572" class="net_11"/>
<line x1="698.3860000000001" x2="159.672" y1="450.572" y2="450.572" class="net_11"/>
<line x1="159.672" x2="159.672" y1="430.572" y2="450.572" class="net_11"/>
</svg>

After

Width:  |  Height:  |  Size: 36 KiB

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 173 KiB

View file

@ -0,0 +1,373 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:s="https://github.com/nturley/netlistsvg" width="591.6800000000001" height="516.456"><rect id="pm_k1-bg" x="0" y="0" width="100%" height="100%" fill="#ffffff"/>
<style>svg {
stroke: #000;
fill: none;
stroke-linejoin: round;
stroke-linecap: round;
}
text {
fill: #000;
stroke: none;
font-size: 10px;
font-weight: bold;
font-family: "Courier New", monospace;
}
.skidl_text {
fill: #999;
stroke: none;
font-weight: bold;
font-family: consolas, "Courier New", monospace;
}
.pin_num_text {
fill: #840000;
}
.pin_name_text {
fill: #008484;
}
.net_name_text {
font-style: italic;
fill: #840084;
}
.part_text {
fill: #840000;
}
.part_ref_text {
fill: #008484;
}
.part_name_text {
fill: #008484;
}
.pen_fill {
fill: #840000;
}
.background_fill {
fill: #FFFFC2
}
.nodelabel {
text-anchor: middle;
}
.inputPortLabel {
text-anchor: end;
}
.splitjoinBody {
fill: #000;
}
.symbol {
stroke-linejoin: round;
stroke-linecap: round;
stroke: #840000;
}
.detail {
stroke-linejoin: round;
stroke-linecap: round;
fill: #000;
}</style>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(130.768,105.152)" id="cell_C1">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C1 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C1 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C1 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C1 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C1</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(41.754,105.152)" id="cell_C2">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C2 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C2 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C2 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C2 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C2</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="D_1_" s:width="73.152" s:height="56.083" transform="translate(421.50800000000004,120.87800000000003)" id="cell_D1">
<s:alias val="D_1_"/>
<polyline points="24.384,15.850 24.384,40.234" style="stroke-width:2.438" class="cell_D1 symbol none"/>
<polyline points="48.768,15.850 48.768,40.234 24.384,28.042 48.768,15.850" style="stroke-width:2.438" class="cell_D1 symbol none"/>
<polyline points="48.768,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D1 symbol none"/>
<polyline points="0.000,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D1 symbol none"/>
<text class="pin_num_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="end">1</text>
<text class="pin_name_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">K</text>
<g s:x="0.000" s:y="28.042" s:pid="1" s:position="left"/>
<polyline points="73.152,28.042 48.768,28.042" style="stroke-width:0.960" class="cell_D1 symbol none"/>
<text class="pin_num_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="start">2</text>
<text class="pin_name_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">A</text>
<g s:x="73.152" s:y="28.042" s:pid="2" s:position="right"/>
<text class="part_ref_text" x="36.576" y="3.658" transform="rotate(0 36.576 3.658)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">D1</text>
<text class="part_name_text" x="36.576" y="52.426" transform="rotate(0 36.576 52.426)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1N4148WS</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(401.754,12)" id="cell_R1">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R1 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R1 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R1 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R1</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">220</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(187.836,421.304)" id="cell_R2">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R2 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R2 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R2 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R2</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">10k</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(288.836,421.304)" id="cell_R3">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R3 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R3 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R3 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R3</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">33</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(22,421.304)" id="cell_R4">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R4 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R4 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R4 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R4</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">33</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(371.754,421.304)" id="cell_R5">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R5 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R5 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R5 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R5</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">33</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(104.918,421.304)" id="cell_R6">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R6 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R6 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R6 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R6</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">33</text>
</g>
<g s:type="generic" s:width="30" s:height="40" transform="translate(531.6600000000001,137.80400000000006)" id="cell_U8">
<text x="15" y="-4" class="nodelabel cell_U8" s:attribute="ref">H11L1_1_</text>
<rect width="30" height="80" x="0" y="0" s:generic="body" class="cell_U8"/>
<g transform="translate(0,10)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U8~1">
<text x="-3" y="-4" class="inputPortLabel cell_U8">1</text>
</g>
<g transform="translate(0,30)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U8~2">
<text x="-3" y="-4" class="inputPortLabel cell_U8">2</text>
</g>
<g transform="translate(0,50)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U8~4">
<text x="-3" y="-4" class="inputPortLabel cell_U8">4</text>
</g>
<g transform="translate(0,70)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U8~6">
<text x="-3" y="-4" class="inputPortLabel cell_U8">6</text>
</g>
<g transform="translate(30,10)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U8~5">
<text x="5" y="-4" class="cell_U8">5</text>
</g>
</g>
<g s:type="generic" s:width="30" s:height="40" transform="translate(230.59,208.304)" id="cell_U9">
<text x="15" y="-4" class="nodelabel cell_U9" s:attribute="ref">74LVC14_1_</text>
<rect width="30" height="160" x="0" y="0" s:generic="body" class="cell_U9"/>
<g transform="translate(0,10)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U9~1">
<text x="-3" y="-4" class="inputPortLabel cell_U9">1</text>
</g>
<g transform="translate(0,30)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U9~3">
<text x="-3" y="-4" class="inputPortLabel cell_U9">3</text>
</g>
<g transform="translate(0,50)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U9~5">
<text x="-3" y="-4" class="inputPortLabel cell_U9">5</text>
</g>
<g transform="translate(0,70)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U9~7">
<text x="-3" y="-4" class="inputPortLabel cell_U9">7</text>
</g>
<g transform="translate(0,90)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U9~9">
<text x="-3" y="-4" class="inputPortLabel cell_U9">9</text>
</g>
<g transform="translate(0,110)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U9~11">
<text x="-3" y="-4" class="inputPortLabel cell_U9">11</text>
</g>
<g transform="translate(0,130)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U9~13">
<text x="-3" y="-4" class="inputPortLabel cell_U9">13</text>
</g>
<g transform="translate(0,150)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U9~14">
<text x="-3" y="-4" class="inputPortLabel cell_U9">14</text>
</g>
<g transform="translate(30,10)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U9~2">
<text x="5" y="-4" class="cell_U9">2</text>
</g>
<g transform="translate(30,30)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U9~4">
<text x="5" y="-4" class="cell_U9">4</text>
</g>
<g transform="translate(30,50)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U9~6">
<text x="5" y="-4" class="cell_U9">6</text>
</g>
<g transform="translate(30,70)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U9~8">
<text x="5" y="-4" class="cell_U9">8</text>
</g>
</g>
<line x1="197.59" x2="197.59" y1="494.45599999999996" y2="504.456" class="net_9"/>
<line x1="197.59" x2="414.672" y1="504.456" y2="504.456" class="net_9"/>
<line x1="414.672" x2="414.672" y1="504.456" y2="411.304" class="net_9"/>
<line x1="414.672" x2="401.50800000000004" y1="411.304" y2="411.304" class="net_9"/>
<line x1="401.50800000000004" x2="401.50800000000004" y1="411.304" y2="208.30400000000003" class="net_9"/>
<circle cx="401.50800000000004" cy="208.30400000000003" r="2" style="fill:#000" class="net_9"/>
<line x1="401.50800000000004" x2="531.6600000000001" y1="208.30400000000003" y2="208.30400000000006" class="net_9"/>
<line x1="197.59" x2="197.59" y1="494.45599999999996" y2="504.456" class="net_9"/>
<line x1="197.59" x2="12" y1="504.456" y2="504.456" class="net_9"/>
<line x1="12" x2="12" y1="504.456" y2="411.304" class="net_9"/>
<line x1="12" x2="31.753999999999998" y1="411.304" y2="411.304" class="net_9"/>
<line x1="31.753999999999998" x2="31.753999999999998" y1="411.304" y2="358.804" class="net_9"/>
<circle cx="197.59" cy="504.456" r="2" style="fill:#000" class="net_9"/>
<circle cx="31.753999999999998" cy="358.804" r="2" style="fill:#000" class="net_9"/>
<line x1="31.753999999999998" x2="230.59" y1="358.804" y2="358.804" class="net_9"/>
<line x1="531.6600000000001" x2="401.50800000000004" y1="208.30400000000006" y2="208.30400000000003" class="net_9"/>
<line x1="401.50800000000004" x2="401.50800000000004" y1="208.30400000000003" y2="95.152" class="net_9"/>
<line x1="401.50800000000004" x2="150.275" y1="95.152" y2="95.152" class="net_9"/>
<circle cx="150.275" cy="95.152" r="2" style="fill:#000" class="net_9"/>
<circle cx="401.50800000000004" cy="208.30400000000003" r="2" style="fill:#000" class="net_9"/>
<line x1="150.275" x2="150.275" y1="95.152" y2="105.152" class="net_9"/>
<line x1="531.6600000000001" x2="401.50800000000004" y1="208.30400000000006" y2="208.30400000000003" class="net_9"/>
<line x1="401.50800000000004" x2="401.50800000000004" y1="208.30400000000003" y2="95.152" class="net_9"/>
<line x1="401.50800000000004" x2="61.260999999999996" y1="95.152" y2="95.152" class="net_9"/>
<circle cx="61.260999999999996" cy="95.152" r="2" style="fill:#000" class="net_9"/>
<circle cx="401.50800000000004" cy="208.30400000000003" r="2" style="fill:#000" class="net_9"/>
<line x1="61.260999999999996" x2="61.260999999999996" y1="95.152" y2="105.152" class="net_9"/>
<line x1="531.6600000000001" x2="401.50800000000004" y1="208.30400000000006" y2="208.30400000000003" class="net_9"/>
<line x1="401.50800000000004" x2="401.50800000000004" y1="208.30400000000003" y2="411.304" class="net_9"/>
<line x1="401.50800000000004" x2="31.753999999999998" y1="411.304" y2="411.304" class="net_9"/>
<circle cx="401.50800000000004" cy="411.304" r="2" style="fill:#000" class="net_9"/>
<circle cx="401.50800000000004" cy="208.30400000000003" r="2" style="fill:#000" class="net_9"/>
<line x1="31.753999999999998" x2="31.753999999999998" y1="411.304" y2="421.304" class="net_9"/>
<line x1="531.6600000000001" x2="401.50800000000004" y1="208.30400000000006" y2="208.30400000000003" class="net_9"/>
<line x1="401.50800000000004" x2="401.50800000000004" y1="208.30400000000003" y2="411.304" class="net_9"/>
<line x1="401.50800000000004" x2="114.67200000000001" y1="411.304" y2="411.304" class="net_9"/>
<circle cx="401.50800000000004" cy="208.30400000000003" r="2" style="fill:#000" class="net_9"/>
<line x1="114.67200000000001" x2="114.67200000000001" y1="411.304" y2="421.304" class="net_9"/>
<line x1="230.59" x2="31.753999999999998" y1="358.804" y2="358.804" class="net_9"/>
<line x1="31.753999999999998" x2="31.753999999999998" y1="358.804" y2="95.152" class="net_9"/>
<line x1="31.753999999999998" x2="150.275" y1="95.152" y2="95.152" class="net_9"/>
<circle cx="31.753999999999998" cy="358.804" r="2" style="fill:#000" class="net_9"/>
<line x1="150.275" x2="150.275" y1="95.152" y2="105.152" class="net_9"/>
<line x1="230.59" x2="31.753999999999998" y1="358.804" y2="358.804" class="net_9"/>
<line x1="31.753999999999998" x2="31.753999999999998" y1="358.804" y2="95.152" class="net_9"/>
<line x1="31.753999999999998" x2="61.260999999999996" y1="95.152" y2="95.152" class="net_9"/>
<circle cx="31.753999999999998" cy="358.804" r="2" style="fill:#000" class="net_9"/>
<line x1="61.260999999999996" x2="61.260999999999996" y1="95.152" y2="105.152" class="net_9"/>
<line x1="230.59" x2="31.753999999999998" y1="358.804" y2="358.804" class="net_9"/>
<circle cx="31.753999999999998" cy="358.804" r="2" style="fill:#000" class="net_9"/>
<line x1="31.753999999999998" x2="31.753999999999998" y1="358.804" y2="421.304" class="net_9"/>
<line x1="230.59" x2="31.753999999999998" y1="358.804" y2="358.804" class="net_9"/>
<line x1="31.753999999999998" x2="31.753999999999998" y1="358.804" y2="411.304" class="net_9"/>
<line x1="31.753999999999998" x2="114.67200000000001" y1="411.304" y2="411.304" class="net_9"/>
<circle cx="31.753999999999998" cy="411.304" r="2" style="fill:#000" class="net_9"/>
<circle cx="114.67200000000001" cy="411.304" r="2" style="fill:#000" class="net_9"/>
<circle cx="31.753999999999998" cy="358.804" r="2" style="fill:#000" class="net_9"/>
<line x1="114.67200000000001" x2="114.67200000000001" y1="411.304" y2="421.304" class="net_9"/>
<line x1="562.6600000000001" x2="579.6600000000001" y1="148.30400000000006" y2="148.30400000000006" class="net_10"/>
<line x1="579.6600000000001" x2="579.6600000000001" y1="148.30400000000006" y2="401.304" class="net_10"/>
<line x1="579.6600000000001" x2="197.59" y1="401.304" y2="401.304" class="net_10"/>
<circle cx="197.59" cy="401.304" r="2" style="fill:#000" class="net_10"/>
<line x1="197.59" x2="197.59" y1="401.304" y2="421.304" class="net_10"/>
<line x1="230.59" x2="197.59" y1="258.804" y2="258.80400000000003" class="net_10"/>
<line x1="197.59" x2="197.59" y1="258.80400000000003" y2="421.304" class="net_10"/>
<line x1="261.59000000000003" x2="298.59000000000003" y1="238.804" y2="238.80400000000003" class="net_14"/>
<line x1="298.59000000000003" x2="298.59000000000003" y1="238.80400000000003" y2="421.304" class="net_14"/>
<line x1="261.59000000000003" x2="381.50800000000004" y1="278.804" y2="278.804" class="net_4"/>
<line x1="381.50800000000004" x2="381.50800000000004" y1="278.804" y2="421.304" class="net_4"/>
<line x1="150.275" x2="150.275" y1="178.304" y2="188.304" class="net_13"/>
<line x1="150.275" x2="391.50800000000004" y1="188.304" y2="188.304" class="net_13"/>
<line x1="391.50800000000004" x2="391.50800000000004" y1="188.304" y2="188.30400000000003" class="net_13"/>
<line x1="391.50800000000004" x2="531.6600000000001" y1="188.30400000000003" y2="188.30400000000006" class="net_13"/>
<line x1="150.275" x2="150.275" y1="178.304" y2="278.804" class="net_13"/>
<line x1="150.275" x2="230.59" y1="278.804" y2="278.804" class="net_13"/>
<line x1="150.275" x2="150.275" y1="178.304" y2="188.304" class="net_13"/>
<line x1="150.275" x2="61.260999999999996" y1="188.304" y2="188.304" class="net_13"/>
<line x1="61.260999999999996" x2="61.260999999999996" y1="188.304" y2="318.804" class="net_13"/>
<line x1="61.260999999999996" x2="230.59" y1="318.804" y2="318.804" class="net_13"/>
<line x1="150.275" x2="150.275" y1="178.304" y2="188.304" class="net_13"/>
<line x1="150.275" x2="51.260999999999996" y1="188.304" y2="188.304" class="net_13"/>
<line x1="51.260999999999996" x2="51.260999999999996" y1="188.304" y2="338.804" class="net_13"/>
<line x1="51.260999999999996" x2="230.59" y1="338.804" y2="338.804" class="net_13"/>
<line x1="61.260999999999996" x2="61.260999999999996" y1="178.304" y2="188.304" class="net_13"/>
<line x1="61.260999999999996" x2="391.50800000000004" y1="188.304" y2="188.304" class="net_13"/>
<line x1="391.50800000000004" x2="391.50800000000004" y1="188.304" y2="188.30400000000003" class="net_13"/>
<circle cx="61.260999999999996" cy="188.304" r="2" style="fill:#000" class="net_13"/>
<line x1="391.50800000000004" x2="531.6600000000001" y1="188.30400000000003" y2="188.30400000000006" class="net_13"/>
<line x1="61.260999999999996" x2="61.260999999999996" y1="178.304" y2="188.304" class="net_13"/>
<line x1="61.260999999999996" x2="150.275" y1="188.304" y2="188.304" class="net_13"/>
<line x1="150.275" x2="150.275" y1="188.304" y2="278.804" class="net_13"/>
<circle cx="150.275" cy="188.304" r="2" style="fill:#000" class="net_13"/>
<line x1="150.275" x2="230.59" y1="278.804" y2="278.804" class="net_13"/>
<line x1="61.260999999999996" x2="61.260999999999996" y1="178.304" y2="318.804" class="net_13"/>
<line x1="61.260999999999996" x2="230.59" y1="318.804" y2="318.804" class="net_13"/>
<line x1="61.260999999999996" x2="61.260999999999996" y1="178.304" y2="188.304" class="net_13"/>
<line x1="61.260999999999996" x2="51.260999999999996" y1="188.304" y2="188.304" class="net_13"/>
<line x1="51.260999999999996" x2="51.260999999999996" y1="188.304" y2="338.804" class="net_13"/>
<line x1="51.260999999999996" x2="230.59" y1="338.804" y2="338.804" class="net_13"/>
<line x1="411.50800000000004" x2="411.50800000000004" y1="85.152" y2="148.92000000000002" class="net_2"/>
<line x1="411.50800000000004" x2="421.50800000000004" y1="148.92000000000002" y2="148.92000000000002" class="net_2"/>
<line x1="411.50800000000004" x2="411.50800000000004" y1="85.152" y2="108.53600000000003" class="net_2"/>
<line x1="411.50800000000004" x2="514.6600000000001" y1="108.53600000000003" y2="108.53600000000003" class="net_2"/>
<line x1="514.6600000000001" x2="514.6600000000001" y1="108.53600000000003" y2="148.30400000000006" class="net_2"/>
<circle cx="411.50800000000004" cy="108.53600000000003" r="2" style="fill:#000" class="net_2"/>
<line x1="514.6600000000001" x2="531.6600000000001" y1="148.30400000000006" y2="148.30400000000006" class="net_2"/>
<line x1="494.66" x2="504.66" y1="148.92000000000002" y2="148.92000000000002" class="net_7"/>
<line x1="504.66" x2="504.66" y1="148.92000000000002" y2="168.30400000000003" class="net_7"/>
<line x1="504.66" x2="531.6600000000001" y1="168.30400000000003" y2="168.30400000000006" class="net_7"/>
<line x1="230.59" x2="160.275" y1="238.804" y2="238.80400000000003" class="net_3"/>
<line x1="160.275" x2="160.275" y1="238.80400000000003" y2="391.304" class="net_3"/>
<line x1="160.275" x2="288.59000000000003" y1="391.304" y2="391.304" class="net_3"/>
<line x1="288.59000000000003" x2="288.59000000000003" y1="391.304" y2="218.80400000000003" class="net_3"/>
<line x1="288.59000000000003" x2="261.59000000000003" y1="218.80400000000003" y2="218.804" class="net_3"/>
<line x1="230.59" x2="207.59" y1="298.804" y2="298.804" class="net_12"/>
<line x1="207.59" x2="207.59" y1="298.804" y2="381.304" class="net_12"/>
<line x1="207.59" x2="278.59000000000003" y1="381.304" y2="381.304" class="net_12"/>
<line x1="278.59000000000003" x2="278.59000000000003" y1="381.304" y2="258.80400000000003" class="net_12"/>
<line x1="278.59000000000003" x2="261.59000000000003" y1="258.80400000000003" y2="258.804" class="net_12"/>
</svg>

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 216 KiB

View file

@ -0,0 +1,269 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:s="https://github.com/nturley/netlistsvg" width="480.201" height="520.108"><rect id="pm_k1-bg" x="0" y="0" width="100%" height="100%" fill="#ffffff"/>
<style>svg {
stroke: #000;
fill: none;
stroke-linejoin: round;
stroke-linecap: round;
}
text {
fill: #000;
stroke: none;
font-size: 10px;
font-weight: bold;
font-family: "Courier New", monospace;
}
.skidl_text {
fill: #999;
stroke: none;
font-weight: bold;
font-family: consolas, "Courier New", monospace;
}
.pin_num_text {
fill: #840000;
}
.pin_name_text {
fill: #008484;
}
.net_name_text {
font-style: italic;
fill: #840084;
}
.part_text {
fill: #840000;
}
.part_ref_text {
fill: #008484;
}
.part_name_text {
fill: #008484;
}
.pen_fill {
fill: #840000;
}
.background_fill {
fill: #FFFFC2
}
.nodelabel {
text-anchor: middle;
}
.inputPortLabel {
text-anchor: end;
}
.splitjoinBody {
fill: #000;
}
.symbol {
stroke-linejoin: round;
stroke-linecap: round;
stroke: #840000;
}
.detail {
stroke-linejoin: round;
stroke-linecap: round;
fill: #000;
}</style>
<g s:type="Battery_Cell_1_" s:width="134.112" s:height="73.152" transform="translate(12,91.768)" id="cell_BT1">
<s:alias val="Battery_Cell_1_"/>
<rect x="0.000" y="31.699" width="43.891" height="2.438" style="stroke-width:0.960" class="cell_BT1 symbol outline"/>
<rect x="7.315" y="39.014" width="29.261" height="4.877" style="stroke-width:0.960" class="cell_BT1 symbol outline"/>
<polyline points="21.946,31.699 21.946,24.384" style="stroke-width:0.960" class="cell_BT1 symbol none"/>
<polyline points="21.946,41.453 21.946,48.768" style="stroke-width:0.960" class="cell_BT1 symbol none"/>
<polyline points="29.261,19.507 39.014,19.507" style="stroke-width:2.438" class="cell_BT1 symbol none"/>
<polyline points="34.138,14.630 34.138,24.384" style="stroke-width:2.438" class="cell_BT1 symbol none"/>
<polyline points="21.946,0.000 21.946,24.384" style="stroke-width:0.960" class="cell_BT1 symbol none"/>
<text class="pin_num_text" x="21.946" y="24.384" transform="rotate(-90 21.946 24.384)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="21.946" y="24.384" transform="rotate(-90 21.946 24.384)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">+</text>
<g s:x="21.946" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="21.946,73.152 21.946,48.768" style="stroke-width:0.960" class="cell_BT1 symbol none"/>
<text class="pin_num_text" x="21.946" y="48.768" transform="rotate(-90 21.946 48.768)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="21.946" y="48.768" transform="rotate(-90 21.946 48.768)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">-</text>
<g s:x="21.946" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="46.330" y="24.384" transform="rotate(0 46.330 24.384)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">BT1</text>
<text class="part_name_text" x="46.330" y="48.768" transform="rotate(0 46.330 48.768)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">CR2032</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(33.946,424.956)" id="cell_C1">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C1 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C1 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C1 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C1 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C1</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="D_Schottky_1_" s:width="109.728" s:height="56.083" transform="translate(74.453,362.53)" id="cell_D1">
<s:alias val="D_Schottky_1_"/>
<polyline points="18.288,21.946 18.288,15.850 24.384,15.850 24.384,40.234 30.480,40.234 30.480,34.138" style="stroke-width:2.438" class="cell_D1 symbol none"/>
<polyline points="48.768,15.850 48.768,40.234 24.384,28.042 48.768,15.850" style="stroke-width:2.438" class="cell_D1 symbol none"/>
<polyline points="48.768,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D1 symbol none"/>
<polyline points="0.000,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D1 symbol none"/>
<text class="pin_num_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="end">1</text>
<text class="pin_name_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">K</text>
<g s:x="0.000" s:y="28.042" s:pid="1" s:position="left"/>
<polyline points="73.152,28.042 48.768,28.042" style="stroke-width:0.960" class="cell_D1 symbol none"/>
<text class="pin_num_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="start">2</text>
<text class="pin_name_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">A</text>
<g s:x="73.152" s:y="28.042" s:pid="2" s:position="right"/>
<text class="part_ref_text" x="36.576" y="3.658" transform="rotate(0 36.576 3.658)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">D1</text>
<text class="part_name_text" x="36.576" y="52.426" transform="rotate(0 36.576 52.426)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">BAT54</text>
</g>
<g s:type="D_Schottky_1_" s:width="109.728" s:height="56.083" transform="translate(166.112,14.342)" id="cell_D2">
<s:alias val="D_Schottky_1_"/>
<polyline points="18.288,21.946 18.288,15.850 24.384,15.850 24.384,40.234 30.480,40.234 30.480,34.138" style="stroke-width:2.438" class="cell_D2 symbol none"/>
<polyline points="48.768,15.850 48.768,40.234 24.384,28.042 48.768,15.850" style="stroke-width:2.438" class="cell_D2 symbol none"/>
<polyline points="48.768,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D2 symbol none"/>
<polyline points="0.000,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D2 symbol none"/>
<text class="pin_num_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="end">1</text>
<text class="pin_name_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">K</text>
<g s:x="0.000" s:y="28.042" s:pid="1" s:position="left"/>
<polyline points="73.152,28.042 48.768,28.042" style="stroke-width:0.960" class="cell_D2 symbol none"/>
<text class="pin_num_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="start">2</text>
<text class="pin_name_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">A</text>
<g s:x="73.152" s:y="28.042" s:pid="2" s:position="right"/>
<text class="part_ref_text" x="36.576" y="3.658" transform="rotate(0 36.576 3.658)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">D2</text>
<text class="part_name_text" x="36.576" y="52.426" transform="rotate(0 36.576 52.426)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">BAT54</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(267.345,307.42)" id="cell_R1">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R1 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R1 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R1 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R1</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">4.7k</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(184.42700000000002,282.036)" id="cell_R2">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R2 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R2 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R2 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R2</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">4.7k</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(350.26300000000003,307.42)" id="cell_R3">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R3 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R3 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R3 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R3</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">10k</text>
</g>
<g s:type="generic" s:width="30" s:height="40" transform="translate(410.18100000000004,174.42000000000002)" id="cell_U7">
<text x="15" y="-4" class="nodelabel cell_U7" s:attribute="ref">RV-8803-C7_1_</text>
<rect width="30" height="100" x="0" y="0" s:generic="body" class="cell_U7"/>
<g transform="translate(0,10)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U7~3">
<text x="-3" y="-4" class="inputPortLabel cell_U7">3</text>
</g>
<g transform="translate(0,30)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U7~4">
<text x="-3" y="-4" class="inputPortLabel cell_U7">4</text>
</g>
<g transform="translate(0,50)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U7~5">
<text x="-3" y="-4" class="inputPortLabel cell_U7">5</text>
</g>
<g transform="translate(0,70)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U7~7">
<text x="-3" y="-4" class="inputPortLabel cell_U7">7</text>
</g>
<g transform="translate(0,90)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U7~8">
<text x="-3" y="-4" class="inputPortLabel cell_U7">8</text>
</g>
<g transform="translate(30,10)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U7~1">
<text x="5" y="-4" class="cell_U7">1</text>
</g>
<g transform="translate(30,30)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U7~6">
<text x="5" y="-4" class="cell_U7">6</text>
</g>
</g>
<line x1="239.264" x2="285.84000000000003" y1="42.384" y2="42.384" class="net_1"/>
<line x1="285.84000000000003" x2="285.84000000000003" y1="42.384" y2="81.768" class="net_1"/>
<line x1="285.84000000000003" x2="33.946" y1="81.768" y2="81.768" class="net_1"/>
<line x1="33.946" x2="33.946" y1="81.768" y2="91.768" class="net_1"/>
<line x1="74.453" x2="64.453" y1="390.572" y2="390.572" class="net_3"/>
<line x1="64.453" x2="64.453" y1="390.572" y2="414.956" class="net_3"/>
<line x1="64.453" x2="53.453" y1="414.956" y2="414.956" class="net_3"/>
<circle cx="53.453" cy="414.956" r="2" style="fill:#000" class="net_3"/>
<line x1="53.453" x2="53.453" y1="414.956" y2="424.956" class="net_3"/>
<line x1="166.112" x2="156.112" y1="42.384" y2="42.384" class="net_3"/>
<line x1="156.112" x2="156.112" y1="42.384" y2="350.188" class="net_3"/>
<line x1="156.112" x2="53.453" y1="350.188" y2="350.188" class="net_3"/>
<circle cx="53.453" cy="350.188" r="2" style="fill:#000" class="net_3"/>
<line x1="53.453" x2="53.453" y1="350.188" y2="424.956" class="net_3"/>
<line x1="410.18100000000004" x2="53.453" y1="184.92000000000002" y2="184.92000000000002" class="net_3"/>
<line x1="53.453" x2="53.453" y1="184.92000000000002" y2="424.956" class="net_3"/>
<line x1="441.18100000000004" x2="458.18100000000004" y1="184.92000000000002" y2="184.92000000000002" class="net_7"/>
<line x1="458.18100000000004" x2="458.18100000000004" y1="184.92000000000002" y2="287.42" class="net_7"/>
<line x1="458.18100000000004" x2="277.09900000000005" y1="287.42" y2="287.42" class="net_7"/>
<line x1="277.09900000000005" x2="277.09900000000005" y1="287.42" y2="307.42" class="net_7"/>
<line x1="410.18100000000004" x2="194.181" y1="264.92" y2="264.92" class="net_6"/>
<line x1="194.181" x2="194.181" y1="264.92" y2="282.036" class="net_6"/>
<line x1="441.18100000000004" x2="468.18100000000004" y1="204.92000000000002" y2="204.92000000000002" class="net_4"/>
<line x1="468.18100000000004" x2="468.18100000000004" y1="204.92000000000002" y2="297.42" class="net_4"/>
<line x1="468.18100000000004" x2="360.01700000000005" y1="297.42" y2="297.42" class="net_4"/>
<line x1="360.01700000000005" x2="360.01700000000005" y1="297.42" y2="307.42" class="net_4"/>
<line x1="33.946" x2="33.946" y1="164.92000000000002" y2="204.92000000000002" class="net_2"/>
<circle cx="33.946" cy="204.92000000000002" r="2" style="fill:#000" class="net_2"/>
<line x1="33.946" x2="410.18100000000004" y1="204.92000000000002" y2="204.92000000000002" class="net_2"/>
<line x1="33.946" x2="33.946" y1="164.92000000000002" y2="174.92000000000002" class="net_2"/>
<line x1="33.946" x2="393.18100000000004" y1="174.92000000000002" y2="174.92000000000002" class="net_2"/>
<line x1="393.18100000000004" x2="393.18100000000004" y1="174.92000000000002" y2="224.92000000000002" class="net_2"/>
<circle cx="33.946" cy="174.92000000000002" r="2" style="fill:#000" class="net_2"/>
<circle cx="393.18100000000004" cy="224.92000000000002" r="2" style="fill:#000" class="net_2"/>
<line x1="393.18100000000004" x2="410.18100000000004" y1="224.92000000000002" y2="224.92000000000002" class="net_2"/>
<line x1="33.946" x2="33.946" y1="164.92000000000002" y2="174.92000000000002" class="net_2"/>
<line x1="33.946" x2="23.946" y1="174.92000000000002" y2="174.92000000000002" class="net_2"/>
<line x1="23.946" x2="23.946" y1="174.92000000000002" y2="244.92000000000002" class="net_2"/>
<circle cx="23.946" cy="244.92000000000002" r="2" style="fill:#000" class="net_2"/>
<line x1="23.946" x2="410.18100000000004" y1="244.92000000000002" y2="244.92000000000002" class="net_2"/>
<line x1="53.453" x2="53.453" y1="498.108" y2="508.108" class="net_2"/>
<line x1="53.453" x2="23.946" y1="508.108" y2="508.108" class="net_2"/>
<line x1="23.946" x2="23.946" y1="508.108" y2="414.956" class="net_2"/>
<line x1="23.946" x2="33.946" y1="414.956" y2="414.956" class="net_2"/>
<line x1="33.946" x2="33.946" y1="414.956" y2="204.92000000000002" class="net_2"/>
<circle cx="23.946" cy="414.956" r="2" style="fill:#000" class="net_2"/>
<circle cx="33.946" cy="204.92000000000002" r="2" style="fill:#000" class="net_2"/>
<line x1="33.946" x2="410.18100000000004" y1="204.92000000000002" y2="204.92000000000002" class="net_2"/>
<line x1="53.453" x2="53.453" y1="498.108" y2="508.108" class="net_2"/>
<line x1="53.453" x2="393.18100000000004" y1="508.108" y2="508.108" class="net_2"/>
<line x1="393.18100000000004" x2="393.18100000000004" y1="508.108" y2="224.92000000000002" class="net_2"/>
<circle cx="393.18100000000004" cy="224.92000000000002" r="2" style="fill:#000" class="net_2"/>
<line x1="393.18100000000004" x2="410.18100000000004" y1="224.92000000000002" y2="224.92000000000002" class="net_2"/>
<line x1="53.453" x2="53.453" y1="498.108" y2="508.108" class="net_2"/>
<line x1="53.453" x2="23.946" y1="508.108" y2="508.108" class="net_2"/>
<line x1="23.946" x2="23.946" y1="508.108" y2="244.92000000000002" class="net_2"/>
<circle cx="53.453" cy="508.108" r="2" style="fill:#000" class="net_2"/>
<circle cx="23.946" cy="244.92000000000002" r="2" style="fill:#000" class="net_2"/>
<line x1="23.946" x2="410.18100000000004" y1="244.92000000000002" y2="244.92000000000002" class="net_2"/>
<line x1="277.09900000000005" x2="277.09900000000005" y1="380.572" y2="390.572" class="net_5"/>
<line x1="277.09900000000005" x2="194.181" y1="390.572" y2="390.572" class="net_5"/>
<line x1="194.181" x2="194.181" y1="390.572" y2="390.572" class="net_5"/>
<circle cx="277.09900000000005" cy="390.572" r="2" style="fill:#000" class="net_5"/>
<circle cx="194.181" cy="390.572" r="2" style="fill:#000" class="net_5"/>
<line x1="194.181" x2="147.60500000000002" y1="390.572" y2="390.572" class="net_5"/>
<line x1="194.181" x2="194.181" y1="355.188" y2="390.572" class="net_5"/>
<line x1="194.181" x2="147.60500000000002" y1="390.572" y2="390.572" class="net_5"/>
<line x1="360.01700000000005" x2="360.01700000000005" y1="380.572" y2="390.572" class="net_5"/>
<line x1="360.01700000000005" x2="194.181" y1="390.572" y2="390.572" class="net_5"/>
<line x1="194.181" x2="194.181" y1="390.572" y2="390.572" class="net_5"/>
<line x1="194.181" x2="147.60500000000002" y1="390.572" y2="390.572" class="net_5"/>
</svg>

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -0,0 +1,215 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:s="https://github.com/nturley/netlistsvg" width="260.47900000000004" height="391.456"><rect id="pm_k1-bg" x="0" y="0" width="100%" height="100%" fill="#ffffff"/>
<style>svg {
stroke: #000;
fill: none;
stroke-linejoin: round;
stroke-linecap: round;
}
text {
fill: #000;
stroke: none;
font-size: 10px;
font-weight: bold;
font-family: "Courier New", monospace;
}
.skidl_text {
fill: #999;
stroke: none;
font-weight: bold;
font-family: consolas, "Courier New", monospace;
}
.pin_num_text {
fill: #840000;
}
.pin_name_text {
fill: #008484;
}
.net_name_text {
font-style: italic;
fill: #840084;
}
.part_text {
fill: #840000;
}
.part_ref_text {
fill: #008484;
}
.part_name_text {
fill: #008484;
}
.pen_fill {
fill: #840000;
}
.background_fill {
fill: #FFFFC2
}
.nodelabel {
text-anchor: middle;
}
.inputPortLabel {
text-anchor: end;
}
.splitjoinBody {
fill: #000;
}
.symbol {
stroke-linejoin: round;
stroke-linecap: round;
stroke: #840000;
}
.detail {
stroke-linejoin: round;
stroke-linecap: round;
fill: #000;
}</style>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(51.547000000000004,105.65200000000003)" id="cell_C1">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C1 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C1 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C1 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C1 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C1</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1uF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(22.04,258.8040000000001)" id="cell_C2">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C2 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C2 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C2 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C2 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C2</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(173.972,12)" id="cell_C3">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C3 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C3 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C3 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C3 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C3</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1uF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(163.972,286.304)" id="cell_C4">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C4 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C4 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C4 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C4 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C4</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1uF</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(81.054,278.804)" id="cell_R1">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R1 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R1 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R1 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R1</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100k</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(183.72500000000002,90.152)" id="cell_R2">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R2 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R2 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R2 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R2</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">68k</text>
</g>
<g s:type="generic" s:width="30" s:height="40" transform="translate(210.479,178.304)" id="cell_U12">
<text x="15" y="-4" class="nodelabel cell_U12" s:attribute="ref">PAM8302A_1_</text>
<rect width="30" height="100" x="0" y="0" s:generic="body" class="cell_U12"/>
<g transform="translate(0,10)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U12~1">
<text x="-3" y="-4" class="inputPortLabel cell_U12">1</text>
</g>
<g transform="translate(0,30)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U12~3">
<text x="-3" y="-4" class="inputPortLabel cell_U12">3</text>
</g>
<g transform="translate(0,50)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U12~4">
<text x="-3" y="-4" class="inputPortLabel cell_U12">4</text>
</g>
<g transform="translate(0,70)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U12~6">
<text x="-3" y="-4" class="inputPortLabel cell_U12">6</text>
</g>
<g transform="translate(0,90)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U12~7">
<text x="-3" y="-4" class="inputPortLabel cell_U12">7</text>
</g>
<g transform="translate(30,10)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U12~5">
<text x="5" y="-4" class="cell_U12">5</text>
</g>
<g transform="translate(30,30)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U12~8">
<text x="5" y="-4" class="cell_U12">8</text>
</g>
</g>
<line x1="90.808" x2="90.80800000000002" y1="351.95599999999996" y2="379.456" class="net_8"/>
<line x1="90.80800000000002" x2="12.04" y1="379.456" y2="379.456" class="net_8"/>
<line x1="12.04" x2="12.04" y1="379.456" y2="248.80400000000003" class="net_8"/>
<circle cx="12.04" cy="248.80400000000003" r="2" style="fill:#000" class="net_8"/>
<line x1="12.04" x2="210.479" y1="248.80400000000003" y2="248.804" class="net_8"/>
<line x1="210.479" x2="12.04" y1="248.804" y2="248.80400000000003" class="net_8"/>
<line x1="12.04" x2="12.04" y1="248.80400000000003" y2="95.65200000000003" class="net_8"/>
<line x1="12.04" x2="71.054" y1="95.65200000000003" y2="95.65200000000003" class="net_8"/>
<circle cx="12.04" cy="248.80400000000003" r="2" style="fill:#000" class="net_8"/>
<line x1="71.054" x2="71.054" y1="95.65200000000003" y2="105.65200000000003" class="net_8"/>
<line x1="210.479" x2="12.04" y1="248.804" y2="248.80400000000003" class="net_8"/>
<line x1="12.04" x2="12.04" y1="248.80400000000003" y2="248.80400000000006" class="net_8"/>
<line x1="12.04" x2="41.547" y1="248.80400000000006" y2="248.80400000000006" class="net_8"/>
<circle cx="12.04" cy="248.80400000000006" r="2" style="fill:#000" class="net_8"/>
<circle cx="12.04" cy="248.80400000000003" r="2" style="fill:#000" class="net_8"/>
<line x1="41.547" x2="41.547" y1="248.80400000000006" y2="258.8040000000001" class="net_8"/>
<line x1="210.479" x2="183.479" y1="228.804" y2="228.80400000000003" class="net_7"/>
<line x1="183.479" x2="183.479" y1="228.80400000000003" y2="286.304" class="net_7"/>
<line x1="210.479" x2="90.80800000000002" y1="188.804" y2="188.80400000000003" class="net_5"/>
<line x1="90.80800000000002" x2="90.808" y1="188.80400000000003" y2="278.804" class="net_5"/>
<line x1="193.479" x2="193.479" y1="85.152" y2="90.152" class="net_1"/>
<line x1="71.054" x2="71.054" y1="178.80400000000003" y2="268.804" class="net_9"/>
<circle cx="71.054" cy="268.804" r="2" style="fill:#000" class="net_9"/>
<line x1="71.054" x2="210.479" y1="268.804" y2="268.804" class="net_9"/>
<line x1="41.547" x2="41.547" y1="331.9560000000001" y2="369.456" class="net_9"/>
<line x1="41.547" x2="71.054" y1="369.456" y2="369.456" class="net_9"/>
<line x1="71.054" x2="71.054" y1="369.456" y2="268.804" class="net_9"/>
<circle cx="71.054" cy="369.456" r="2" style="fill:#000" class="net_9"/>
<circle cx="71.054" cy="268.804" r="2" style="fill:#000" class="net_9"/>
<line x1="71.054" x2="210.479" y1="268.804" y2="268.804" class="net_9"/>
<line x1="183.479" x2="183.479" y1="359.45599999999996" y2="369.456" class="net_9"/>
<line x1="183.479" x2="71.054" y1="369.456" y2="369.456" class="net_9"/>
<line x1="71.054" x2="71.054" y1="369.456" y2="268.804" class="net_9"/>
<circle cx="71.054" cy="268.804" r="2" style="fill:#000" class="net_9"/>
<line x1="71.054" x2="210.479" y1="268.804" y2="268.804" class="net_9"/>
<line x1="193.479" x2="193.479" y1="163.304" y2="208.80400000000003" class="net_6"/>
<line x1="193.479" x2="210.479" y1="208.80400000000003" y2="208.804" class="net_6"/>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -0,0 +1,379 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:s="https://github.com/nturley/netlistsvg" width="571.4359999999999" height="613.876"><rect id="pm_k1-bg" x="0" y="0" width="100%" height="100%" fill="#ffffff"/>
<style>svg {
stroke: #000;
fill: none;
stroke-linejoin: round;
stroke-linecap: round;
}
text {
fill: #000;
stroke: none;
font-size: 10px;
font-weight: bold;
font-family: "Courier New", monospace;
}
.skidl_text {
fill: #999;
stroke: none;
font-weight: bold;
font-family: consolas, "Courier New", monospace;
}
.pin_num_text {
fill: #840000;
}
.pin_name_text {
fill: #008484;
}
.net_name_text {
font-style: italic;
fill: #840084;
}
.part_text {
fill: #840000;
}
.part_ref_text {
fill: #008484;
}
.part_name_text {
fill: #008484;
}
.pen_fill {
fill: #840000;
}
.background_fill {
fill: #FFFFC2
}
.nodelabel {
text-anchor: middle;
}
.inputPortLabel {
text-anchor: end;
}
.splitjoinBody {
fill: #000;
}
.symbol {
stroke-linejoin: round;
stroke-linecap: round;
stroke: #840000;
}
.detail {
stroke-linejoin: round;
stroke-linecap: round;
fill: #000;
}</style>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(464.88899999999995,22)" id="cell_C1">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C1 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C1 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C1 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C1 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C1</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">2.2uF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(13.266999999999998,12)" id="cell_C2">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C2 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C2 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C2 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C2 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C2</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">2.2uF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(165.43300000000002,293.072)" id="cell_C3">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C3 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C3 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C3 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C3 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C3</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(106.419,293.072)" id="cell_C4">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C4 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C4 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C4 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C4 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C4</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="D_1_" s:width="73.152" s:height="56.083" transform="translate(401.24399999999997,205.64600000000002)" id="cell_D1">
<s:alias val="D_1_"/>
<polyline points="24.384,15.850 24.384,40.234" style="stroke-width:2.438" class="cell_D1 symbol none"/>
<polyline points="48.768,15.850 48.768,40.234 24.384,28.042 48.768,15.850" style="stroke-width:2.438" class="cell_D1 symbol none"/>
<polyline points="48.768,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D1 symbol none"/>
<polyline points="0.000,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D1 symbol none"/>
<text class="pin_num_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="end">1</text>
<text class="pin_name_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">K</text>
<g s:x="0.000" s:y="28.042" s:pid="1" s:position="left"/>
<polyline points="73.152,28.042 48.768,28.042" style="stroke-width:0.960" class="cell_D1 symbol none"/>
<text class="pin_num_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="start">2</text>
<text class="pin_name_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">A</text>
<g s:x="73.152" s:y="28.042" s:pid="2" s:position="right"/>
<text class="part_ref_text" x="36.576" y="3.658" transform="rotate(0 36.576 3.658)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">D1</text>
<text class="part_name_text" x="36.576" y="52.426" transform="rotate(0 36.576 52.426)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1N4148WS</text>
</g>
<g s:type="D_1_" s:width="73.152" s:height="56.083" transform="translate(298.092,195.64600000000002)" id="cell_D2">
<s:alias val="D_1_"/>
<polyline points="24.384,15.850 24.384,40.234" style="stroke-width:2.438" class="cell_D2 symbol none"/>
<polyline points="48.768,15.850 48.768,40.234 24.384,28.042 48.768,15.850" style="stroke-width:2.438" class="cell_D2 symbol none"/>
<polyline points="48.768,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D2 symbol none"/>
<polyline points="0.000,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D2 symbol none"/>
<text class="pin_num_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="end">1</text>
<text class="pin_name_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">K</text>
<g s:x="0.000" s:y="28.042" s:pid="1" s:position="left"/>
<polyline points="73.152,28.042 48.768,28.042" style="stroke-width:0.960" class="cell_D2 symbol none"/>
<text class="pin_num_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="start">2</text>
<text class="pin_name_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">A</text>
<g s:x="73.152" s:y="28.042" s:pid="2" s:position="right"/>
<text class="part_ref_text" x="36.576" y="3.658" transform="rotate(0 36.576 3.658)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">D2</text>
<text class="part_name_text" x="36.576" y="52.426" transform="rotate(0 36.576 52.426)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1N4148WS</text>
</g>
<g s:type="D_1_" s:width="73.152" s:height="56.083" transform="translate(194.94000000000003,185.64600000000002)" id="cell_D3">
<s:alias val="D_1_"/>
<polyline points="24.384,15.850 24.384,40.234" style="stroke-width:2.438" class="cell_D3 symbol none"/>
<polyline points="48.768,15.850 48.768,40.234 24.384,28.042 48.768,15.850" style="stroke-width:2.438" class="cell_D3 symbol none"/>
<polyline points="48.768,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D3 symbol none"/>
<polyline points="0.000,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D3 symbol none"/>
<text class="pin_num_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="end">1</text>
<text class="pin_name_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">K</text>
<g s:x="0.000" s:y="28.042" s:pid="1" s:position="left"/>
<polyline points="73.152,28.042 48.768,28.042" style="stroke-width:0.960" class="cell_D3 symbol none"/>
<text class="pin_num_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="start">2</text>
<text class="pin_name_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">A</text>
<g s:x="73.152" s:y="28.042" s:pid="2" s:position="right"/>
<text class="part_ref_text" x="36.576" y="3.658" transform="rotate(0 36.576 3.658)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">D3</text>
<text class="part_name_text" x="36.576" y="52.426" transform="rotate(0 36.576 52.426)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1N4148WS</text>
</g>
<g s:type="D_1_" s:width="73.152" s:height="56.083" transform="translate(42.774,185.64600000000002)" id="cell_D4">
<s:alias val="D_1_"/>
<polyline points="24.384,15.850 24.384,40.234" style="stroke-width:2.438" class="cell_D4 symbol none"/>
<polyline points="48.768,15.850 48.768,40.234 24.384,28.042 48.768,15.850" style="stroke-width:2.438" class="cell_D4 symbol none"/>
<polyline points="48.768,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D4 symbol none"/>
<polyline points="0.000,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D4 symbol none"/>
<text class="pin_num_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="end">1</text>
<text class="pin_name_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">K</text>
<g s:x="0.000" s:y="28.042" s:pid="1" s:position="left"/>
<polyline points="73.152,28.042 48.768,28.042" style="stroke-width:0.960" class="cell_D4 symbol none"/>
<text class="pin_num_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="start">2</text>
<text class="pin_name_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">A</text>
<g s:x="73.152" s:y="28.042" s:pid="2" s:position="right"/>
<text class="part_ref_text" x="36.576" y="3.658" transform="rotate(0 36.576 3.658)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">D4</text>
<text class="part_name_text" x="36.576" y="52.426" transform="rotate(0 36.576 52.426)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1N4148WS</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(474.64199999999994,100.152)" id="cell_R1">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R1 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R1 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R1 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R1</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1k</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(474.64199999999994,518.7239999999999)" id="cell_R2">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R2 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R2 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R2 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R2</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1Meg</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(23.02,90.152)" id="cell_R3">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R3 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R3 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R3 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R3</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1k</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(23.02,293.072)" id="cell_R4">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R4 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R4 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R4 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R4</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1Meg</text>
</g>
<g s:type="generic" s:width="30" s:height="40" transform="translate(501.39599999999996,365.724)" id="cell_U1">
<text x="15" y="-4" class="nodelabel cell_U1" s:attribute="ref">THAT1240_1_</text>
<rect width="30" height="120" x="0" y="0" s:generic="body" class="cell_U1"/>
<g transform="translate(0,10)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U1~1">
<text x="-3" y="-4" class="inputPortLabel cell_U1">1</text>
</g>
<g transform="translate(0,30)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U1~2">
<text x="-3" y="-4" class="inputPortLabel cell_U1">2</text>
</g>
<g transform="translate(0,50)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U1~3">
<text x="-3" y="-4" class="inputPortLabel cell_U1">3</text>
</g>
<g transform="translate(0,70)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U1~4">
<text x="-3" y="-4" class="inputPortLabel cell_U1">4</text>
</g>
<g transform="translate(0,90)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U1~5">
<text x="-3" y="-4" class="inputPortLabel cell_U1">5</text>
</g>
<g transform="translate(0,110)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U1~7">
<text x="-3" y="-4" class="inputPortLabel cell_U1">7</text>
</g>
<g transform="translate(30,10)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U1~6">
<text x="5" y="-4" class="cell_U1">6</text>
</g>
</g>
<line x1="401.24399999999997" x2="391.24399999999997" y1="233.68800000000002" y2="233.68800000000002" class="net_6"/>
<line x1="391.24399999999997" x2="391.24399999999997" y1="233.68800000000002" y2="283.072" class="net_6"/>
<line x1="391.24399999999997" x2="184.94000000000003" y1="283.072" y2="283.072" class="net_6"/>
<line x1="184.94000000000003" x2="184.94000000000003" y1="283.072" y2="293.072" class="net_6"/>
<line x1="194.94000000000003" x2="184.94000000000003" y1="213.68800000000002" y2="213.68800000000002" class="net_6"/>
<line x1="184.94000000000003" x2="184.94000000000003" y1="213.68800000000002" y2="293.072" class="net_6"/>
<line x1="501.39599999999996" x2="214.44700000000003" y1="476.224" y2="476.224" class="net_6"/>
<line x1="214.44700000000003" x2="214.44700000000003" y1="476.224" y2="283.072" class="net_6"/>
<line x1="214.44700000000003" x2="184.94000000000003" y1="283.072" y2="283.072" class="net_6"/>
<circle cx="184.94000000000003" cy="283.072" r="2" style="fill:#000" class="net_6"/>
<circle cx="214.44700000000003" cy="283.072" r="2" style="fill:#000" class="net_6"/>
<line x1="184.94000000000003" x2="184.94000000000003" y1="283.072" y2="293.072" class="net_6"/>
<line x1="371.24399999999997" x2="381.24399999999997" y1="223.68800000000002" y2="223.68800000000002" class="net_5"/>
<line x1="381.24399999999997" x2="381.24399999999997" y1="223.68800000000002" y2="263.072" class="net_5"/>
<line x1="381.24399999999997" x2="125.926" y1="263.072" y2="263.072" class="net_5"/>
<line x1="125.926" x2="125.926" y1="263.072" y2="293.072" class="net_5"/>
<line x1="115.926" x2="125.926" y1="213.68800000000002" y2="213.68800000000002" class="net_5"/>
<line x1="125.926" x2="125.926" y1="213.68800000000002" y2="293.072" class="net_5"/>
<line x1="501.39599999999996" x2="155.43300000000002" y1="436.224" y2="436.224" class="net_5"/>
<line x1="155.43300000000002" x2="155.43300000000002" y1="436.224" y2="263.072" class="net_5"/>
<line x1="155.43300000000002" x2="125.926" y1="263.072" y2="263.072" class="net_5"/>
<circle cx="125.926" cy="263.072" r="2" style="fill:#000" class="net_5"/>
<circle cx="155.43300000000002" cy="263.072" r="2" style="fill:#000" class="net_5"/>
<line x1="125.926" x2="125.926" y1="263.072" y2="293.072" class="net_5"/>
<line x1="484.39599999999996" x2="484.39599999999996" y1="95.152" y2="100.152" class="net_7"/>
<line x1="484.39599999999996" x2="484.39599999999996" y1="173.304" y2="183.304" class="net_8"/>
<line x1="484.39599999999996" x2="559.396" y1="183.304" y2="183.304" class="net_8"/>
<line x1="559.396" x2="559.396" y1="183.304" y2="233.68800000000002" class="net_8"/>
<circle cx="484.39599999999996" cy="183.304" r="2" style="fill:#000" class="net_8"/>
<circle cx="559.396" cy="233.68800000000002" r="2" style="fill:#000" class="net_8"/>
<line x1="559.396" x2="474.39599999999996" y1="233.68800000000002" y2="233.68800000000002" class="net_8"/>
<line x1="484.39599999999996" x2="484.39599999999996" y1="173.304" y2="183.304" class="net_8"/>
<line x1="484.39599999999996" x2="288.092" y1="183.304" y2="183.304" class="net_8"/>
<line x1="288.092" x2="288.092" y1="183.304" y2="223.68800000000002" class="net_8"/>
<circle cx="288.092" cy="223.68800000000002" r="2" style="fill:#000" class="net_8"/>
<line x1="288.092" x2="298.092" y1="223.68800000000002" y2="223.68800000000002" class="net_8"/>
<line x1="484.39599999999996" x2="484.39599999999996" y1="173.304" y2="416.224" class="net_8"/>
<circle cx="484.39599999999996" cy="416.224" r="2" style="fill:#000" class="net_8"/>
<line x1="484.39599999999996" x2="501.39599999999996" y1="416.224" y2="416.224" class="net_8"/>
<line x1="474.39599999999996" x2="559.396" y1="233.68800000000002" y2="233.68800000000002" class="net_8"/>
<line x1="559.396" x2="559.396" y1="233.68800000000002" y2="508.724" class="net_8"/>
<line x1="559.396" x2="484.39599999999996" y1="508.724" y2="508.724" class="net_8"/>
<circle cx="559.396" cy="233.68800000000002" r="2" style="fill:#000" class="net_8"/>
<line x1="484.39599999999996" x2="484.39599999999996" y1="508.724" y2="518.7239999999999" class="net_8"/>
<line x1="298.092" x2="288.092" y1="223.68800000000002" y2="223.68800000000002" class="net_8"/>
<line x1="288.092" x2="288.092" y1="223.68800000000002" y2="273.072" class="net_8"/>
<line x1="288.092" x2="559.396" y1="273.072" y2="273.072" class="net_8"/>
<line x1="559.396" x2="559.396" y1="273.072" y2="508.724" class="net_8"/>
<line x1="559.396" x2="484.39599999999996" y1="508.724" y2="508.724" class="net_8"/>
<circle cx="559.396" cy="273.072" r="2" style="fill:#000" class="net_8"/>
<circle cx="484.39599999999996" cy="508.724" r="2" style="fill:#000" class="net_8"/>
<circle cx="288.092" cy="223.68800000000002" r="2" style="fill:#000" class="net_8"/>
<line x1="484.39599999999996" x2="484.39599999999996" y1="508.724" y2="518.7239999999999" class="net_8"/>
<line x1="501.39599999999996" x2="484.39599999999996" y1="416.224" y2="416.224" class="net_8"/>
<circle cx="484.39599999999996" cy="416.224" r="2" style="fill:#000" class="net_8"/>
<line x1="484.39599999999996" x2="484.39599999999996" y1="416.224" y2="518.7239999999999" class="net_8"/>
<line x1="32.774" x2="32.774" y1="85.152" y2="90.152" class="net_10"/>
<line x1="32.774" x2="32.774" y1="163.304" y2="173.304" class="net_4"/>
<line x1="32.774" x2="278.092" y1="173.304" y2="173.304" class="net_4"/>
<line x1="278.092" x2="278.092" y1="173.304" y2="213.68800000000002" class="net_4"/>
<circle cx="32.774" cy="173.304" r="2" style="fill:#000" class="net_4"/>
<circle cx="278.092" cy="213.68800000000002" r="2" style="fill:#000" class="net_4"/>
<line x1="278.092" x2="268.09200000000004" y1="213.68800000000002" y2="213.68800000000002" class="net_4"/>
<line x1="32.774" x2="32.774" y1="163.304" y2="213.68800000000002" class="net_4"/>
<circle cx="32.774" cy="213.68800000000002" r="2" style="fill:#000" class="net_4"/>
<line x1="32.774" x2="42.774" y1="213.68800000000002" y2="213.68800000000002" class="net_4"/>
<line x1="32.774" x2="32.774" y1="163.304" y2="173.304" class="net_4"/>
<line x1="32.774" x2="12.02" y1="173.304" y2="173.304" class="net_4"/>
<line x1="12.02" x2="12.02" y1="173.304" y2="396.224" class="net_4"/>
<line x1="12.02" x2="501.39599999999996" y1="396.224" y2="396.224" class="net_4"/>
<line x1="268.09200000000004" x2="278.092" y1="213.68800000000002" y2="213.68800000000002" class="net_4"/>
<line x1="278.092" x2="278.092" y1="213.68800000000002" y2="253.072" class="net_4"/>
<line x1="278.092" x2="32.774" y1="253.072" y2="253.072" class="net_4"/>
<circle cx="278.092" cy="213.68800000000002" r="2" style="fill:#000" class="net_4"/>
<line x1="32.774" x2="32.774" y1="253.072" y2="293.072" class="net_4"/>
<line x1="42.774" x2="32.774" y1="213.68800000000002" y2="213.68800000000002" class="net_4"/>
<circle cx="32.774" cy="213.68800000000002" r="2" style="fill:#000" class="net_4"/>
<line x1="32.774" x2="32.774" y1="213.68800000000002" y2="293.072" class="net_4"/>
<line x1="501.39599999999996" x2="12.02" y1="396.224" y2="396.224" class="net_4"/>
<line x1="12.02" x2="12.02" y1="396.224" y2="253.072" class="net_4"/>
<line x1="12.02" x2="32.774" y1="253.072" y2="253.072" class="net_4"/>
<circle cx="32.774" cy="253.072" r="2" style="fill:#000" class="net_4"/>
<circle cx="12.02" cy="253.072" r="2" style="fill:#000" class="net_4"/>
<line x1="32.774" x2="32.774" y1="253.072" y2="293.072" class="net_4"/>
<line x1="184.94000000000003" x2="184.94000000000003" y1="366.224" y2="376.224" class="net_3"/>
<line x1="184.94000000000003" x2="32.774" y1="376.224" y2="376.224" class="net_3"/>
<line x1="32.774" x2="32.774" y1="376.224" y2="376.224" class="net_3"/>
<circle cx="32.774" cy="376.224" r="2" style="fill:#000" class="net_3"/>
<circle cx="32.774" cy="376.224" r="2" style="fill:#000" class="net_3"/>
<line x1="32.774" x2="501.39599999999996" y1="376.224" y2="376.224" class="net_3"/>
<line x1="125.926" x2="125.926" y1="366.224" y2="376.224" class="net_3"/>
<line x1="125.926" x2="32.774" y1="376.224" y2="376.224" class="net_3"/>
<line x1="32.774" x2="32.774" y1="376.224" y2="376.224" class="net_3"/>
<circle cx="125.926" cy="376.224" r="2" style="fill:#000" class="net_3"/>
<circle cx="32.774" cy="376.224" r="2" style="fill:#000" class="net_3"/>
<line x1="32.774" x2="501.39599999999996" y1="376.224" y2="376.224" class="net_3"/>
<line x1="484.39599999999996" x2="484.39599999999996" y1="591.876" y2="601.876" class="net_3"/>
<line x1="484.39599999999996" x2="32.774" y1="601.876" y2="601.876" class="net_3"/>
<line x1="32.774" x2="32.774" y1="601.876" y2="376.224" class="net_3"/>
<circle cx="32.774" cy="376.224" r="2" style="fill:#000" class="net_3"/>
<line x1="32.774" x2="501.39599999999996" y1="376.224" y2="376.224" class="net_3"/>
<line x1="32.774" x2="32.774" y1="366.224" y2="376.224" class="net_3"/>
<circle cx="32.774" cy="376.224" r="2" style="fill:#000" class="net_3"/>
<line x1="32.774" x2="501.39599999999996" y1="376.224" y2="376.224" class="net_3"/>
<line x1="501.39599999999996" x2="165.43300000000002" y1="456.224" y2="456.224" class="net_2"/>
<line x1="165.43300000000002" x2="165.43300000000002" y1="456.224" y2="498.724" class="net_2"/>
<line x1="165.43300000000002" x2="549.396" y1="498.724" y2="498.724" class="net_2"/>
<line x1="549.396" x2="549.396" y1="498.724" y2="376.224" class="net_2"/>
<line x1="549.396" x2="532.396" y1="376.224" y2="376.224" class="net_2"/>
</svg>

After

Width:  |  Height:  |  Size: 33 KiB

View file

@ -0,0 +1,301 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:s="https://github.com/nturley/netlistsvg" width="549.681" height="531.304"><rect id="pm_k1-bg" x="0" y="0" width="100%" height="100%" fill="#ffffff"/>
<style>svg {
stroke: #000;
fill: none;
stroke-linejoin: round;
stroke-linecap: round;
}
text {
fill: #000;
stroke: none;
font-size: 10px;
font-weight: bold;
font-family: "Courier New", monospace;
}
.skidl_text {
fill: #999;
stroke: none;
font-weight: bold;
font-family: consolas, "Courier New", monospace;
}
.pin_num_text {
fill: #840000;
}
.pin_name_text {
fill: #008484;
}
.net_name_text {
font-style: italic;
fill: #840084;
}
.part_text {
fill: #840000;
}
.part_ref_text {
fill: #008484;
}
.part_name_text {
fill: #008484;
}
.pen_fill {
fill: #840000;
}
.background_fill {
fill: #FFFFC2
}
.nodelabel {
text-anchor: middle;
}
.inputPortLabel {
text-anchor: end;
}
.splitjoinBody {
fill: #000;
}
.symbol {
stroke-linejoin: round;
stroke-linecap: round;
stroke: #840000;
}
.detail {
stroke-linejoin: round;
stroke-linecap: round;
fill: #000;
}</style>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(246.85000000000002,190)" id="cell_C1">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C1 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C1 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C1 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C1 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C1</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(147.836,416.152)" id="cell_C2">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C2 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C2 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C2 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C2 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C2</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(236.85000000000002,426.152)" id="cell_C3">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C3 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C3 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C3 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C3 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C3</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="D_1_" s:width="73.152" s:height="56.083" transform="translate(351.357,328.726)" id="cell_D1">
<s:alias val="D_1_"/>
<polyline points="24.384,15.850 24.384,40.234" style="stroke-width:2.438" class="cell_D1 symbol none"/>
<polyline points="48.768,15.850 48.768,40.234 24.384,28.042 48.768,15.850" style="stroke-width:2.438" class="cell_D1 symbol none"/>
<polyline points="48.768,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D1 symbol none"/>
<polyline points="0.000,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D1 symbol none"/>
<text class="pin_num_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="end">1</text>
<text class="pin_name_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">K</text>
<g s:x="0.000" s:y="28.042" s:pid="1" s:position="left"/>
<polyline points="73.152,28.042 48.768,28.042" style="stroke-width:0.960" class="cell_D1 symbol none"/>
<text class="pin_num_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="start">2</text>
<text class="pin_name_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">A</text>
<g s:x="73.152" s:y="28.042" s:pid="2" s:position="right"/>
<text class="part_ref_text" x="36.576" y="3.658" transform="rotate(0 36.576 3.658)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">D1</text>
<text class="part_name_text" x="36.576" y="52.426" transform="rotate(0 36.576 52.426)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1N4148WS</text>
</g>
<g s:type="D_1_" s:width="73.152" s:height="56.083" transform="translate(454.509,348.726)" id="cell_D2">
<s:alias val="D_1_"/>
<polyline points="24.384,15.850 24.384,40.234" style="stroke-width:2.438" class="cell_D2 symbol none"/>
<polyline points="48.768,15.850 48.768,40.234 24.384,28.042 48.768,15.850" style="stroke-width:2.438" class="cell_D2 symbol none"/>
<polyline points="48.768,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D2 symbol none"/>
<polyline points="0.000,28.042 24.384,28.042" style="stroke-width:0.960" class="cell_D2 symbol none"/>
<text class="pin_num_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="end">1</text>
<text class="pin_name_text" x="24.384" y="28.042" transform="rotate(0 24.384 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">K</text>
<g s:x="0.000" s:y="28.042" s:pid="1" s:position="left"/>
<polyline points="73.152,28.042 48.768,28.042" style="stroke-width:0.960" class="cell_D2 symbol none"/>
<text class="pin_num_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="" text-anchor="start">2</text>
<text class="pin_name_text" x="48.768" y="28.042" transform="rotate(0 48.768 28.042)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">A</text>
<g s:x="73.152" s:y="28.042" s:pid="2" s:position="right"/>
<text class="part_ref_text" x="36.576" y="3.658" transform="rotate(0 36.576 3.658)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">D2</text>
<text class="part_name_text" x="36.576" y="52.426" transform="rotate(0 36.576 52.426)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1N4148WS</text>
</g>
<g s:type="generic" s:width="30" s:height="40" transform="translate(289.357,22)" id="cell_K1">
<text x="15" y="-4" class="nodelabel cell_K1" s:attribute="ref">TQ2SA-5V_1_</text>
<rect width="30" height="160" x="0" y="0" s:generic="body" class="cell_K1"/>
<g transform="translate(0,10)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_K1~1">
<text x="-3" y="-4" class="inputPortLabel cell_K1">1</text>
</g>
<g transform="translate(0,30)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_K1~2">
<text x="-3" y="-4" class="inputPortLabel cell_K1">2</text>
</g>
<g transform="translate(0,50)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_K1~3">
<text x="-3" y="-4" class="inputPortLabel cell_K1">3</text>
</g>
<g transform="translate(0,70)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_K1~4">
<text x="-3" y="-4" class="inputPortLabel cell_K1">4</text>
</g>
<g transform="translate(0,90)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_K1~7">
<text x="-3" y="-4" class="inputPortLabel cell_K1">7</text>
</g>
<g transform="translate(0,110)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_K1~8">
<text x="-3" y="-4" class="inputPortLabel cell_K1">8</text>
</g>
<g transform="translate(0,130)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_K1~9">
<text x="-3" y="-4" class="inputPortLabel cell_K1">9</text>
</g>
<g transform="translate(0,150)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_K1~10">
<text x="-3" y="-4" class="inputPortLabel cell_K1">10</text>
</g>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(424.755,426.152)" id="cell_R1">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R1 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R1 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R1 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R1</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1Meg</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(12,396.152)" id="cell_R2">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R2 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R2 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R2 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R2</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">3k</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(64.918,426.152)" id="cell_R3">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R3 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R3 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R3 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R3</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1k</text>
</g>
<g s:type="generic" s:width="30" s:height="40" transform="translate(283.357,293.152)" id="cell_U2">
<text x="15" y="-4" class="nodelabel cell_U2" s:attribute="ref">OPA1641_1_</text>
<rect width="30" height="80" x="0" y="0" s:generic="body" class="cell_U2"/>
<g transform="translate(0,10)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U2~2">
<text x="-3" y="-4" class="inputPortLabel cell_U2">2</text>
</g>
<g transform="translate(0,30)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U2~3">
<text x="-3" y="-4" class="inputPortLabel cell_U2">3</text>
</g>
<g transform="translate(0,50)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U2~4">
<text x="-3" y="-4" class="inputPortLabel cell_U2">4</text>
</g>
<g transform="translate(0,70)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U2~7">
<text x="-3" y="-4" class="inputPortLabel cell_U2">7</text>
</g>
<g transform="translate(30,10)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U2~6">
<text x="5" y="-4" class="cell_U2">6</text>
</g>
</g>
<line x1="289.357" x2="266.357" y1="52.5" y2="52.5" class="net_3"/>
<line x1="266.357" x2="266.357" y1="52.5" y2="190" class="net_3"/>
<line x1="351.357" x2="341.357" y1="356.76800000000003" y2="356.76800000000003" class="net_1"/>
<line x1="341.357" x2="341.357" y1="356.76800000000003" y2="406.152" class="net_1"/>
<line x1="341.357" x2="167.34300000000002" y1="406.152" y2="406.152" class="net_1"/>
<circle cx="167.34300000000002" cy="406.152" r="2" style="fill:#000" class="net_1"/>
<line x1="167.34300000000002" x2="167.34300000000002" y1="406.152" y2="416.152" class="net_1"/>
<line x1="283.357" x2="167.34300000000002" y1="363.652" y2="363.652" class="net_1"/>
<line x1="167.34300000000002" x2="167.34300000000002" y1="363.652" y2="416.152" class="net_1"/>
<line x1="527.6610000000001" x2="537.6610000000001" y1="376.76800000000003" y2="376.76800000000003" class="net_2"/>
<line x1="537.6610000000001" x2="537.6610000000001" y1="376.76800000000003" y2="416.152" class="net_2"/>
<line x1="537.6610000000001" x2="256.357" y1="416.152" y2="416.152" class="net_2"/>
<circle cx="256.357" cy="416.152" r="2" style="fill:#000" class="net_2"/>
<line x1="256.357" x2="256.357" y1="416.152" y2="426.152" class="net_2"/>
<line x1="283.357" x2="256.357" y1="343.652" y2="343.652" class="net_2"/>
<line x1="256.357" x2="256.357" y1="343.652" y2="426.152" class="net_2"/>
<line x1="266.357" x2="266.357" y1="263.152" y2="273.152" class="net_13"/>
<line x1="266.357" x2="434.509" y1="273.152" y2="273.152" class="net_13"/>
<line x1="434.509" x2="434.509" y1="273.152" y2="356.76800000000003" class="net_13"/>
<circle cx="266.357" cy="273.152" r="2" style="fill:#000" class="net_13"/>
<circle cx="434.509" cy="273.152" r="2" style="fill:#000" class="net_13"/>
<circle cx="434.509" cy="356.76800000000003" r="2" style="fill:#000" class="net_13"/>
<line x1="434.509" x2="424.509" y1="356.76800000000003" y2="356.76800000000003" class="net_13"/>
<line x1="266.357" x2="266.357" y1="263.152" y2="273.152" class="net_13"/>
<line x1="266.357" x2="444.509" y1="273.152" y2="273.152" class="net_13"/>
<line x1="444.509" x2="444.509" y1="273.152" y2="376.76800000000003" class="net_13"/>
<circle cx="444.509" cy="376.76800000000003" r="2" style="fill:#000" class="net_13"/>
<line x1="444.509" x2="454.509" y1="376.76800000000003" y2="376.76800000000003" class="net_13"/>
<line x1="266.357" x2="266.357" y1="263.152" y2="323.652" class="net_13"/>
<circle cx="266.357" cy="323.652" r="2" style="fill:#000" class="net_13"/>
<line x1="266.357" x2="283.357" y1="323.652" y2="323.652" class="net_13"/>
<line x1="424.509" x2="434.509" y1="356.76800000000003" y2="356.76800000000003" class="net_13"/>
<circle cx="434.509" cy="356.76800000000003" r="2" style="fill:#000" class="net_13"/>
<line x1="434.509" x2="434.509" y1="356.76800000000003" y2="426.152" class="net_13"/>
<line x1="454.509" x2="444.509" y1="376.76800000000003" y2="376.76800000000003" class="net_13"/>
<line x1="444.509" x2="444.509" y1="376.76800000000003" y2="396.152" class="net_13"/>
<line x1="444.509" x2="434.509" y1="396.152" y2="396.152" class="net_13"/>
<circle cx="444.509" cy="376.76800000000003" r="2" style="fill:#000" class="net_13"/>
<line x1="434.509" x2="434.509" y1="396.152" y2="426.152" class="net_13"/>
<line x1="283.357" x2="266.357" y1="323.652" y2="323.652" class="net_13"/>
<line x1="266.357" x2="266.357" y1="323.652" y2="396.152" class="net_13"/>
<line x1="266.357" x2="434.509" y1="396.152" y2="396.152" class="net_13"/>
<circle cx="434.509" cy="396.152" r="2" style="fill:#000" class="net_13"/>
<circle cx="266.357" cy="323.652" r="2" style="fill:#000" class="net_13"/>
<line x1="434.509" x2="434.509" y1="396.152" y2="426.152" class="net_13"/>
<line x1="289.357" x2="21.753999999999998" y1="152.5" y2="152.5" class="net_4"/>
<line x1="21.753999999999998" x2="21.753999999999998" y1="152.5" y2="396.152" class="net_4"/>
<line x1="314.357" x2="331.357" y1="303.652" y2="303.652" class="net_4"/>
<line x1="331.357" x2="331.357" y1="303.652" y2="386.152" class="net_4"/>
<line x1="331.357" x2="21.753999999999998" y1="386.152" y2="386.152" class="net_4"/>
<circle cx="21.753999999999998" cy="386.152" r="2" style="fill:#000" class="net_4"/>
<line x1="21.753999999999998" x2="21.753999999999998" y1="386.152" y2="396.152" class="net_4"/>
<line x1="21.753999999999998" x2="21.753999999999998" y1="469.304" y2="519.304" class="net_9"/>
<line x1="21.753999999999998" x2="54.918" y1="519.304" y2="519.304" class="net_9"/>
<line x1="54.918" x2="54.918" y1="519.304" y2="303.652" class="net_9"/>
<line x1="54.918" x2="283.357" y1="303.652" y2="303.652" class="net_9"/>
<line x1="283.357" x2="54.918" y1="303.652" y2="303.652" class="net_9"/>
<line x1="54.918" x2="54.918" y1="303.652" y2="416.152" class="net_9"/>
<line x1="54.918" x2="74.672" y1="416.152" y2="416.152" class="net_9"/>
<circle cx="54.918" cy="416.152" r="2" style="fill:#000" class="net_9"/>
<line x1="74.672" x2="74.672" y1="416.152" y2="426.152" class="net_9"/>
<line x1="167.34300000000002" x2="167.34300000000002" y1="489.304" y2="509.304" class="net_6"/>
<line x1="256.357" x2="256.357" y1="499.304" y2="509.304" class="net_6"/>
<circle cx="256.357" cy="509.304" r="2" style="fill:#000" class="net_6"/>
<line x1="256.357" x2="167.34300000000002" y1="509.304" y2="509.304" class="net_6"/>
<line x1="434.509" x2="434.509" y1="499.304" y2="509.304" class="net_6"/>
<line x1="434.509" x2="167.34300000000002" y1="509.304" y2="509.304" class="net_6"/>
<line x1="74.672" x2="74.672" y1="499.304" y2="509.304" class="net_6"/>
<circle cx="167.34300000000002" cy="509.304" r="2" style="fill:#000" class="net_6"/>
<line x1="74.672" x2="167.34300000000002" y1="509.304" y2="509.304" class="net_6"/>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -0,0 +1,821 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:s="https://github.com/nturley/netlistsvg" width="1207.571" height="680.304"><rect id="pm_k1-bg" x="0" y="0" width="100%" height="100%" fill="#ffffff"/>
<style>svg {
stroke: #000;
fill: none;
stroke-linejoin: round;
stroke-linecap: round;
}
text {
fill: #000;
stroke: none;
font-size: 10px;
font-weight: bold;
font-family: "Courier New", monospace;
}
.skidl_text {
fill: #999;
stroke: none;
font-weight: bold;
font-family: consolas, "Courier New", monospace;
}
.pin_num_text {
fill: #840000;
}
.pin_name_text {
fill: #008484;
}
.net_name_text {
font-style: italic;
fill: #840084;
}
.part_text {
fill: #840000;
}
.part_ref_text {
fill: #008484;
}
.part_name_text {
fill: #008484;
}
.pen_fill {
fill: #840000;
}
.background_fill {
fill: #FFFFC2
}
.nodelabel {
text-anchor: middle;
}
.inputPortLabel {
text-anchor: end;
}
.splitjoinBody {
fill: #000;
}
.symbol {
stroke-linejoin: round;
stroke-linecap: round;
stroke: #840000;
}
.detail {
stroke-linejoin: round;
stroke-linecap: round;
fill: #000;
}</style>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(235.95199999999977,82)" id="cell_C1">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C1 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C1 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C1 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C1 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C1</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(413.9799999999998,82)" id="cell_C10">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C10 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C10 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C10 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C10 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C10</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(502.9939999999998,82)" id="cell_C11">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C11 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C11 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C11 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C11 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C11</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(324.9659999999998,82)" id="cell_C2">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C2 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C2 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C2 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C2 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C2</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(829.0499999999998,82)" id="cell_C3">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C3 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C3 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C3 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C3 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C3</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(146.93799999999976,82)" id="cell_C4">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C4 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C4 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C4 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C4 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C4</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">10uF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(921.4749999999999,512.652)" id="cell_C5">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C5 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C5 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C5 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C5 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C5</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">2.2uF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(681.0219999999998,82)" id="cell_C6">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C6 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C6 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C6 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C6 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C6</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">2.2uF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(592.0079999999998,82)" id="cell_C7">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C7 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C7 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C7 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C7 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C7</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1uF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(1152.8999999999999,432.152)" id="cell_C8">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C8 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C8 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C8 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C8 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C8</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">2.2nF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(770.0359999999998,82)" id="cell_C9">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C9 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C9 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C9 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C9 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C9</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1nF</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(888.0639999999999,92)" id="cell_R1">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R1 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R1 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R1 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R1</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">10k</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(64.01999999999998,82)" id="cell_R2">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R2 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R2 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R2 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R2</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">2.2k</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(1162.6529999999998,339)" id="cell_R3">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R3 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R3 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R3 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R3</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1.5k</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(1069.982,432.152)" id="cell_R4">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R4 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R4 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R4 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R4</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">1.5k</text>
</g>
<g s:type="generic" s:width="30" s:height="40" transform="translate(973.9819999999999,164.652)" id="cell_U3">
<text x="15" y="-4" class="nodelabel cell_U3" s:attribute="ref">PCM5102A_1_</text>
<rect width="30" height="340" x="0" y="0" s:generic="body" class="cell_U3"/>
<g transform="translate(0,10)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U3~1">
<text x="-3" y="-4" class="inputPortLabel cell_U3">1</text>
</g>
<g transform="translate(0,30)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U3~2">
<text x="-3" y="-4" class="inputPortLabel cell_U3">2</text>
</g>
<g transform="translate(0,50)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U3~3">
<text x="-3" y="-4" class="inputPortLabel cell_U3">3</text>
</g>
<g transform="translate(0,70)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U3~4">
<text x="-3" y="-4" class="inputPortLabel cell_U3">4</text>
</g>
<g transform="translate(0,90)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U3~5">
<text x="-3" y="-4" class="inputPortLabel cell_U3">5</text>
</g>
<g transform="translate(0,110)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U3~8">
<text x="-3" y="-4" class="inputPortLabel cell_U3">8</text>
</g>
<g transform="translate(0,130)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U3~9">
<text x="-3" y="-4" class="inputPortLabel cell_U3">9</text>
</g>
<g transform="translate(0,150)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U3~10">
<text x="-3" y="-4" class="inputPortLabel cell_U3">10</text>
</g>
<g transform="translate(0,170)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U3~11">
<text x="-3" y="-4" class="inputPortLabel cell_U3">11</text>
</g>
<g transform="translate(0,190)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U3~12">
<text x="-3" y="-4" class="inputPortLabel cell_U3">12</text>
</g>
<g transform="translate(0,210)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U3~13">
<text x="-3" y="-4" class="inputPortLabel cell_U3">13</text>
</g>
<g transform="translate(0,230)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U3~14">
<text x="-3" y="-4" class="inputPortLabel cell_U3">14</text>
</g>
<g transform="translate(0,250)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U3~15">
<text x="-3" y="-4" class="inputPortLabel cell_U3">15</text>
</g>
<g transform="translate(0,270)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U3~16">
<text x="-3" y="-4" class="inputPortLabel cell_U3">16</text>
</g>
<g transform="translate(0,290)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U3~17">
<text x="-3" y="-4" class="inputPortLabel cell_U3">17</text>
</g>
<g transform="translate(0,310)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U3~19">
<text x="-3" y="-4" class="inputPortLabel cell_U3">19</text>
</g>
<g transform="translate(0,330)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U3~20">
<text x="-3" y="-4" class="inputPortLabel cell_U3">20</text>
</g>
<g transform="translate(30,10)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U3~6">
<text x="5" y="-4" class="cell_U3">6</text>
</g>
<g transform="translate(30,30)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U3~7">
<text x="5" y="-4" class="cell_U3">7</text>
</g>
<g transform="translate(30,50)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U3~18">
<text x="5" y="-4" class="cell_U3">18</text>
</g>
</g>
<g s:type="generic" s:width="30" s:height="40" transform="translate(1114.407,535.304)" id="cell_U4">
<text x="15" y="-4" class="nodelabel cell_U4" s:attribute="ref">OPA1612_1_</text>
<rect width="30" height="120" x="0" y="0" s:generic="body" class="cell_U4"/>
<g transform="translate(0,10)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U4~2">
<text x="-3" y="-4" class="inputPortLabel cell_U4">2</text>
</g>
<g transform="translate(0,30)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U4~3">
<text x="-3" y="-4" class="inputPortLabel cell_U4">3</text>
</g>
<g transform="translate(0,50)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U4~4">
<text x="-3" y="-4" class="inputPortLabel cell_U4">4</text>
</g>
<g transform="translate(0,70)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U4~5">
<text x="-3" y="-4" class="inputPortLabel cell_U4">5</text>
</g>
<g transform="translate(0,90)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U4~6">
<text x="-3" y="-4" class="inputPortLabel cell_U4">6</text>
</g>
<g transform="translate(0,110)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U4~8">
<text x="-3" y="-4" class="inputPortLabel cell_U4">8</text>
</g>
<g transform="translate(30,10)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U4~1">
<text x="5" y="-4" class="cell_U4">1</text>
</g>
<g transform="translate(30,30)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U4~7">
<text x="5" y="-4" class="cell_U4">7</text>
</g>
</g>
<line x1="897.8179999999999" x2="897.8179999999999" y1="165.152" y2="175.152" class="net_9"/>
<line x1="897.8179999999999" x2="502.50099999999975" y1="175.152" y2="175.152" class="net_9"/>
<line x1="502.50099999999975" x2="502.50099999999975" y1="175.152" y2="175.152" class="net_9"/>
<circle cx="897.8179999999999" cy="175.152" r="2" style="fill:#000" class="net_9"/>
<circle cx="502.50099999999975" cy="175.152" r="2" style="fill:#000" class="net_9"/>
<line x1="502.50099999999975" x2="973.9819999999999" y1="175.152" y2="175.152" class="net_9"/>
<line x1="897.8179999999999" x2="897.8179999999999" y1="165.152" y2="175.152" class="net_9"/>
<line x1="897.8179999999999" x2="54.01999999999998" y1="175.152" y2="175.152" class="net_9"/>
<line x1="54.01999999999998" x2="54.01999999999998" y1="175.152" y2="275.152" class="net_9"/>
<circle cx="54.01999999999998" cy="175.152" r="2" style="fill:#000" class="net_9"/>
<line x1="54.01999999999998" x2="973.9819999999999" y1="275.152" y2="275.152" class="net_9"/>
<line x1="897.8179999999999" x2="897.8179999999999" y1="165.152" y2="175.152" class="net_9"/>
<line x1="897.8179999999999" x2="930.9819999999999" y1="175.152" y2="175.152" class="net_9"/>
<line x1="930.9819999999999" x2="930.9819999999999" y1="175.152" y2="495.15200000000004" class="net_9"/>
<circle cx="930.9819999999999" cy="175.152" r="2" style="fill:#000" class="net_9"/>
<line x1="930.9819999999999" x2="973.9819999999999" y1="495.15200000000004" y2="495.152" class="net_9"/>
<line x1="973.9819999999999" x2="502.50099999999975" y1="175.152" y2="175.152" class="net_9"/>
<line x1="502.50099999999975" x2="502.50099999999975" y1="175.152" y2="175.152" class="net_9"/>
<line x1="502.50099999999975" x2="819.0499999999998" y1="175.152" y2="175.152" class="net_9"/>
<line x1="819.0499999999998" x2="819.0499999999998" y1="175.152" y2="72" class="net_9"/>
<line x1="819.0499999999998" x2="255.45899999999972" y1="72" y2="72" class="net_9"/>
<circle cx="255.45899999999972" cy="72" r="2" style="fill:#000" class="net_9"/>
<line x1="255.45899999999972" x2="255.45899999999978" y1="72" y2="82" class="net_9"/>
<line x1="973.9819999999999" x2="502.50099999999975" y1="175.152" y2="175.152" class="net_9"/>
<line x1="502.50099999999975" x2="502.50099999999975" y1="175.152" y2="175.152" class="net_9"/>
<line x1="502.50099999999975" x2="819.0499999999998" y1="175.152" y2="175.152" class="net_9"/>
<line x1="819.0499999999998" x2="819.0499999999998" y1="175.152" y2="72" class="net_9"/>
<line x1="819.0499999999998" x2="344.47299999999973" y1="72" y2="72" class="net_9"/>
<circle cx="344.47299999999973" cy="72" r="2" style="fill:#000" class="net_9"/>
<circle cx="819.0499999999998" cy="175.152" r="2" style="fill:#000" class="net_9"/>
<line x1="344.47299999999973" x2="344.4729999999998" y1="72" y2="82" class="net_9"/>
<line x1="973.9819999999999" x2="502.50099999999975" y1="175.152" y2="175.152" class="net_9"/>
<line x1="502.50099999999975" x2="502.50099999999975" y1="175.152" y2="175.152" class="net_9"/>
<line x1="502.50099999999975" x2="878.0639999999999" y1="175.152" y2="175.152" class="net_9"/>
<line x1="878.0639999999999" x2="878.0639999999999" y1="175.152" y2="72" class="net_9"/>
<line x1="878.0639999999999" x2="848.5569999999998" y1="72" y2="72" class="net_9"/>
<circle cx="848.5569999999998" cy="72" r="2" style="fill:#000" class="net_9"/>
<circle cx="878.0639999999999" cy="72" r="2" style="fill:#000" class="net_9"/>
<circle cx="878.0639999999999" cy="175.152" r="2" style="fill:#000" class="net_9"/>
<line x1="848.5569999999998" x2="848.5569999999998" y1="72" y2="82" class="net_9"/>
<line x1="973.9819999999999" x2="502.50099999999975" y1="175.152" y2="175.152" class="net_9"/>
<line x1="502.50099999999975" x2="502.50099999999975" y1="175.152" y2="175.152" class="net_9"/>
<line x1="502.50099999999975" x2="819.0499999999998" y1="175.152" y2="175.152" class="net_9"/>
<line x1="819.0499999999998" x2="819.0499999999998" y1="175.152" y2="72" class="net_9"/>
<line x1="819.0499999999998" x2="166.4449999999997" y1="72" y2="72" class="net_9"/>
<circle cx="166.4449999999997" cy="72" r="2" style="fill:#000" class="net_9"/>
<circle cx="819.0499999999998" cy="72" r="2" style="fill:#000" class="net_9"/>
<line x1="166.4449999999997" x2="166.44499999999977" y1="72" y2="82" class="net_9"/>
<line x1="973.9819999999999" x2="54.01999999999998" y1="275.152" y2="275.152" class="net_9"/>
<line x1="54.01999999999998" x2="54.01999999999998" y1="275.152" y2="72" class="net_9"/>
<line x1="54.01999999999998" x2="255.45899999999972" y1="72" y2="72" class="net_9"/>
<line x1="255.45899999999972" x2="255.45899999999978" y1="72" y2="82" class="net_9"/>
<line x1="973.9819999999999" x2="54.01999999999998" y1="275.152" y2="275.152" class="net_9"/>
<line x1="54.01999999999998" x2="54.01999999999998" y1="275.152" y2="72" class="net_9"/>
<line x1="54.01999999999998" x2="344.47299999999973" y1="72" y2="72" class="net_9"/>
<line x1="344.47299999999973" x2="344.4729999999998" y1="72" y2="82" class="net_9"/>
<line x1="973.9819999999999" x2="54.01999999999998" y1="275.152" y2="275.152" class="net_9"/>
<line x1="54.01999999999998" x2="54.01999999999998" y1="275.152" y2="72" class="net_9"/>
<line x1="54.01999999999998" x2="848.5569999999998" y1="72" y2="72" class="net_9"/>
<line x1="848.5569999999998" x2="848.5569999999998" y1="72" y2="82" class="net_9"/>
<line x1="973.9819999999999" x2="54.01999999999998" y1="275.152" y2="275.152" class="net_9"/>
<line x1="54.01999999999998" x2="54.01999999999998" y1="275.152" y2="72" class="net_9"/>
<line x1="54.01999999999998" x2="166.4449999999997" y1="72" y2="72" class="net_9"/>
<line x1="166.4449999999997" x2="166.44499999999977" y1="72" y2="82" class="net_9"/>
<line x1="973.9819999999999" x2="930.9819999999999" y1="495.152" y2="495.15200000000004" class="net_9"/>
<line x1="930.9819999999999" x2="930.9819999999999" y1="495.15200000000004" y2="72" class="net_9"/>
<line x1="930.9819999999999" x2="255.45899999999972" y1="72" y2="72" class="net_9"/>
<line x1="255.45899999999972" x2="255.45899999999978" y1="72" y2="82" class="net_9"/>
<line x1="973.9819999999999" x2="930.9819999999999" y1="495.152" y2="495.15200000000004" class="net_9"/>
<line x1="930.9819999999999" x2="930.9819999999999" y1="495.15200000000004" y2="72" class="net_9"/>
<line x1="930.9819999999999" x2="344.47299999999973" y1="72" y2="72" class="net_9"/>
<line x1="344.47299999999973" x2="344.4729999999998" y1="72" y2="82" class="net_9"/>
<line x1="973.9819999999999" x2="930.9819999999999" y1="495.152" y2="495.15200000000004" class="net_9"/>
<line x1="930.9819999999999" x2="930.9819999999999" y1="495.15200000000004" y2="72" class="net_9"/>
<line x1="930.9819999999999" x2="848.5569999999998" y1="72" y2="72" class="net_9"/>
<line x1="848.5569999999998" x2="848.5569999999998" y1="72" y2="82" class="net_9"/>
<line x1="973.9819999999999" x2="930.9819999999999" y1="495.152" y2="495.15200000000004" class="net_9"/>
<line x1="930.9819999999999" x2="930.9819999999999" y1="495.15200000000004" y2="72" class="net_9"/>
<line x1="930.9819999999999" x2="166.4449999999997" y1="72" y2="72" class="net_9"/>
<line x1="166.4449999999997" x2="166.44499999999977" y1="72" y2="82" class="net_9"/>
<line x1="1114.407" x2="22.019999999999982" y1="645.804" y2="645.804" class="net_2"/>
<line x1="22.019999999999982" x2="22.019999999999982" y1="645.804" y2="32" class="net_2"/>
<line x1="22.019999999999982" x2="433.48699999999974" y1="32" y2="32" class="net_2"/>
<line x1="433.48699999999974" x2="433.4869999999998" y1="32" y2="82" class="net_2"/>
<line x1="1114.407" x2="1047.982" y1="585.804" y2="585.804" class="net_4"/>
<line x1="1047.982" x2="1047.982" y1="585.804" y2="22" class="net_4"/>
<line x1="1047.982" x2="522.5009999999997" y1="22" y2="22" class="net_4"/>
<line x1="522.5009999999997" x2="522.5009999999997" y1="22" y2="82" class="net_4"/>
<line x1="973.9819999999999" x2="940.9819999999999" y1="195.152" y2="195.152" class="net_10"/>
<line x1="940.9819999999999" x2="940.9819999999999" y1="195.152" y2="512.652" class="net_10"/>
<line x1="973.9819999999999" x2="950.9819999999999" y1="255.152" y2="255.152" class="net_3"/>
<line x1="950.9819999999999" x2="950.9819999999999" y1="255.152" y2="62" class="net_3"/>
<line x1="950.9819999999999" x2="700.5289999999998" y1="62" y2="62" class="net_3"/>
<line x1="700.5289999999998" x2="700.5289999999998" y1="62" y2="82" class="net_3"/>
<line x1="1004.9819999999999" x2="1037.982" y1="215.152" y2="215.152" class="net_7"/>
<line x1="1037.982" x2="1037.982" y1="215.152" y2="52" class="net_7"/>
<line x1="1037.982" x2="611.5149999999998" y1="52" y2="52" class="net_7"/>
<line x1="611.5149999999998" x2="611.5149999999998" y1="52" y2="82" class="net_7"/>
<line x1="1172.4069999999997" x2="1172.407" y1="412.152" y2="432.152" class="net_15"/>
<line x1="1172.4069999999997" x2="1172.407" y1="412.152" y2="422.152" class="net_15"/>
<line x1="1172.407" x2="1079.7359999999999" y1="422.152" y2="422.152" class="net_15"/>
<circle cx="1172.407" cy="422.152" r="2" style="fill:#000" class="net_15"/>
<line x1="1079.7359999999999" x2="1079.7359999999999" y1="422.152" y2="432.152" class="net_15"/>
<line x1="1079.7359999999999" x2="1079.7359999999999" y1="505.304" y2="565.804" class="net_19"/>
<line x1="1079.7359999999999" x2="1114.407" y1="565.804" y2="565.804" class="net_19"/>
<line x1="1114.407" x2="1079.7359999999999" y1="565.804" y2="565.804" class="net_19"/>
<line x1="1079.7359999999999" x2="1079.7359999999999" y1="565.804" y2="565.804" class="net_19"/>
<line x1="1079.7359999999999" x2="1058.982" y1="565.804" y2="565.804" class="net_19"/>
<line x1="1058.982" x2="1058.982" y1="565.804" y2="32" class="net_19"/>
<line x1="1058.982" x2="789.5429999999999" y1="32" y2="32" class="net_19"/>
<circle cx="1079.7359999999999" cy="565.804" r="2" style="fill:#000" class="net_19"/>
<line x1="789.5429999999999" x2="789.5429999999998" y1="32" y2="82" class="net_19"/>
<line x1="973.9819999999999" x2="44.01999999999998" y1="455.152" y2="455.152" class="net_14"/>
<line x1="44.01999999999998" x2="44.01999999999998" y1="455.152" y2="42" class="net_14"/>
<line x1="44.01999999999998" x2="897.8179999999999" y1="42" y2="42" class="net_14"/>
<line x1="897.8179999999999" x2="897.8179999999999" y1="42" y2="92" class="net_14"/>
<line x1="1004.9819999999999" x2="1027.982" y1="195.152" y2="195.152" class="net_16"/>
<line x1="1027.982" x2="1027.982" y1="195.152" y2="12" class="net_16"/>
<line x1="1027.982" x2="73.77399999999989" y1="12" y2="12" class="net_16"/>
<line x1="73.77399999999989" x2="73.77399999999997" y1="12" y2="82" class="net_16"/>
<line x1="1004.9819999999999" x2="1172.407" y1="175.152" y2="175.152" class="net_8"/>
<line x1="1172.407" x2="1172.4069999999997" y1="175.152" y2="339" class="net_8"/>
<line x1="255.45899999999978" x2="255.45899999999972" y1="155.152" y2="165.152" class="net_13"/>
<line x1="255.45899999999972" x2="522.5009999999997" y1="165.152" y2="165.152" class="net_13"/>
<line x1="522.5009999999997" x2="522.5009999999997" y1="165.152" y2="215.152" class="net_13"/>
<line x1="522.5009999999997" x2="973.9819999999999" y1="215.152" y2="215.152" class="net_13"/>
<line x1="255.45899999999978" x2="255.45899999999972" y1="155.152" y2="165.152" class="net_13"/>
<line x1="255.45899999999972" x2="433.48699999999974" y1="165.152" y2="165.152" class="net_13"/>
<line x1="433.48699999999974" x2="433.48699999999974" y1="165.152" y2="295.152" class="net_13"/>
<line x1="433.48699999999974" x2="973.9819999999999" y1="295.152" y2="295.152" class="net_13"/>
<line x1="255.45899999999978" x2="255.45899999999972" y1="155.152" y2="315.152" class="net_13"/>
<line x1="255.45899999999972" x2="973.9819999999999" y1="315.152" y2="315.152" class="net_13"/>
<line x1="255.45899999999978" x2="255.45899999999972" y1="155.152" y2="165.152" class="net_13"/>
<line x1="255.45899999999972" x2="224.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="224.45899999999972" x2="224.45899999999972" y1="165.152" y2="335.152" class="net_13"/>
<line x1="224.45899999999972" x2="973.9819999999999" y1="335.152" y2="335.152" class="net_13"/>
<line x1="255.45899999999978" x2="255.45899999999972" y1="155.152" y2="165.152" class="net_13"/>
<line x1="255.45899999999972" x2="214.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="214.45899999999972" x2="214.45899999999972" y1="165.152" y2="435.152" class="net_13"/>
<line x1="214.45899999999972" x2="973.9819999999999" y1="435.152" y2="435.152" class="net_13"/>
<line x1="255.45899999999978" x2="255.45899999999972" y1="155.152" y2="165.152" class="net_13"/>
<line x1="255.45899999999972" x2="245.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="245.45899999999972" x2="245.45899999999972" y1="165.152" y2="475.152" class="net_13"/>
<line x1="245.45899999999972" x2="973.9819999999999" y1="475.152" y2="475.152" class="net_13"/>
<line x1="255.45899999999978" x2="255.45899999999972" y1="155.152" y2="165.152" class="net_13"/>
<line x1="255.45899999999972" x2="33.01999999999998" y1="165.152" y2="165.152" class="net_13"/>
<line x1="33.01999999999998" x2="33.01999999999998" y1="165.152" y2="485.152" class="net_13"/>
<line x1="33.01999999999998" x2="234.45899999999972" y1="485.152" y2="485.152" class="net_13"/>
<line x1="234.45899999999972" x2="234.45899999999972" y1="485.152" y2="605.804" class="net_13"/>
<line x1="234.45899999999972" x2="1114.407" y1="605.804" y2="605.804" class="net_13"/>
<line x1="433.4869999999998" x2="433.48699999999974" y1="155.152" y2="165.152" class="net_13"/>
<line x1="433.48699999999974" x2="522.5009999999997" y1="165.152" y2="165.152" class="net_13"/>
<line x1="522.5009999999997" x2="522.5009999999997" y1="165.152" y2="215.152" class="net_13"/>
<line x1="522.5009999999997" x2="973.9819999999999" y1="215.152" y2="215.152" class="net_13"/>
<line x1="433.4869999999998" x2="433.48699999999974" y1="155.152" y2="295.152" class="net_13"/>
<line x1="433.48699999999974" x2="973.9819999999999" y1="295.152" y2="295.152" class="net_13"/>
<line x1="433.4869999999998" x2="433.48699999999974" y1="155.152" y2="165.152" class="net_13"/>
<line x1="433.48699999999974" x2="255.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="255.45899999999972" x2="255.45899999999972" y1="165.152" y2="315.152" class="net_13"/>
<line x1="255.45899999999972" x2="973.9819999999999" y1="315.152" y2="315.152" class="net_13"/>
<line x1="433.4869999999998" x2="433.48699999999974" y1="155.152" y2="165.152" class="net_13"/>
<line x1="433.48699999999974" x2="224.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="224.45899999999972" x2="224.45899999999972" y1="165.152" y2="335.152" class="net_13"/>
<line x1="224.45899999999972" x2="973.9819999999999" y1="335.152" y2="335.152" class="net_13"/>
<line x1="433.4869999999998" x2="433.48699999999974" y1="155.152" y2="165.152" class="net_13"/>
<line x1="433.48699999999974" x2="214.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="214.45899999999972" x2="214.45899999999972" y1="165.152" y2="435.152" class="net_13"/>
<line x1="214.45899999999972" x2="973.9819999999999" y1="435.152" y2="435.152" class="net_13"/>
<line x1="433.4869999999998" x2="433.48699999999974" y1="155.152" y2="165.152" class="net_13"/>
<line x1="433.48699999999974" x2="245.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="245.45899999999972" x2="245.45899999999972" y1="165.152" y2="475.152" class="net_13"/>
<line x1="245.45899999999972" x2="973.9819999999999" y1="475.152" y2="475.152" class="net_13"/>
<line x1="433.4869999999998" x2="433.48699999999974" y1="155.152" y2="165.152" class="net_13"/>
<line x1="433.48699999999974" x2="234.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="234.45899999999972" x2="234.45899999999972" y1="165.152" y2="605.804" class="net_13"/>
<circle cx="234.45899999999972" cy="165.152" r="2" style="fill:#000" class="net_13"/>
<line x1="234.45899999999972" x2="1114.407" y1="605.804" y2="605.804" class="net_13"/>
<line x1="522.5009999999997" x2="522.5009999999997" y1="155.152" y2="215.152" class="net_13"/>
<line x1="522.5009999999997" x2="973.9819999999999" y1="215.152" y2="215.152" class="net_13"/>
<line x1="522.5009999999997" x2="522.5009999999997" y1="155.152" y2="165.152" class="net_13"/>
<line x1="522.5009999999997" x2="433.48699999999974" y1="165.152" y2="165.152" class="net_13"/>
<line x1="433.48699999999974" x2="433.48699999999974" y1="165.152" y2="295.152" class="net_13"/>
<line x1="433.48699999999974" x2="973.9819999999999" y1="295.152" y2="295.152" class="net_13"/>
<line x1="522.5009999999997" x2="522.5009999999997" y1="155.152" y2="165.152" class="net_13"/>
<line x1="522.5009999999997" x2="255.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="255.45899999999972" x2="255.45899999999972" y1="165.152" y2="315.152" class="net_13"/>
<line x1="255.45899999999972" x2="973.9819999999999" y1="315.152" y2="315.152" class="net_13"/>
<line x1="522.5009999999997" x2="522.5009999999997" y1="155.152" y2="165.152" class="net_13"/>
<line x1="522.5009999999997" x2="224.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="224.45899999999972" x2="224.45899999999972" y1="165.152" y2="335.152" class="net_13"/>
<line x1="224.45899999999972" x2="973.9819999999999" y1="335.152" y2="335.152" class="net_13"/>
<line x1="522.5009999999997" x2="522.5009999999997" y1="155.152" y2="165.152" class="net_13"/>
<line x1="522.5009999999997" x2="214.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="214.45899999999972" x2="214.45899999999972" y1="165.152" y2="435.152" class="net_13"/>
<line x1="214.45899999999972" x2="973.9819999999999" y1="435.152" y2="435.152" class="net_13"/>
<line x1="522.5009999999997" x2="522.5009999999997" y1="155.152" y2="165.152" class="net_13"/>
<line x1="522.5009999999997" x2="245.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="245.45899999999972" x2="245.45899999999972" y1="165.152" y2="475.152" class="net_13"/>
<line x1="245.45899999999972" x2="973.9819999999999" y1="475.152" y2="475.152" class="net_13"/>
<line x1="522.5009999999997" x2="522.5009999999997" y1="155.152" y2="165.152" class="net_13"/>
<line x1="522.5009999999997" x2="234.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="234.45899999999972" x2="234.45899999999972" y1="165.152" y2="605.804" class="net_13"/>
<line x1="234.45899999999972" x2="1114.407" y1="605.804" y2="605.804" class="net_13"/>
<line x1="344.4729999999998" x2="344.47299999999973" y1="155.152" y2="165.152" class="net_13"/>
<line x1="344.47299999999973" x2="522.5009999999997" y1="165.152" y2="165.152" class="net_13"/>
<line x1="522.5009999999997" x2="522.5009999999997" y1="165.152" y2="215.152" class="net_13"/>
<circle cx="344.47299999999973" cy="165.152" r="2" style="fill:#000" class="net_13"/>
<line x1="522.5009999999997" x2="973.9819999999999" y1="215.152" y2="215.152" class="net_13"/>
<line x1="344.4729999999998" x2="344.47299999999973" y1="155.152" y2="165.152" class="net_13"/>
<line x1="344.47299999999973" x2="433.48699999999974" y1="165.152" y2="165.152" class="net_13"/>
<line x1="433.48699999999974" x2="433.48699999999974" y1="165.152" y2="295.152" class="net_13"/>
<line x1="433.48699999999974" x2="973.9819999999999" y1="295.152" y2="295.152" class="net_13"/>
<line x1="344.4729999999998" x2="344.47299999999973" y1="155.152" y2="165.152" class="net_13"/>
<line x1="344.47299999999973" x2="255.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="255.45899999999972" x2="255.45899999999972" y1="165.152" y2="315.152" class="net_13"/>
<line x1="255.45899999999972" x2="973.9819999999999" y1="315.152" y2="315.152" class="net_13"/>
<line x1="344.4729999999998" x2="344.47299999999973" y1="155.152" y2="165.152" class="net_13"/>
<line x1="344.47299999999973" x2="224.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="224.45899999999972" x2="224.45899999999972" y1="165.152" y2="335.152" class="net_13"/>
<line x1="224.45899999999972" x2="973.9819999999999" y1="335.152" y2="335.152" class="net_13"/>
<line x1="344.4729999999998" x2="344.47299999999973" y1="155.152" y2="165.152" class="net_13"/>
<line x1="344.47299999999973" x2="214.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="214.45899999999972" x2="214.45899999999972" y1="165.152" y2="435.152" class="net_13"/>
<line x1="214.45899999999972" x2="973.9819999999999" y1="435.152" y2="435.152" class="net_13"/>
<line x1="344.4729999999998" x2="344.47299999999973" y1="155.152" y2="165.152" class="net_13"/>
<line x1="344.47299999999973" x2="245.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="245.45899999999972" x2="245.45899999999972" y1="165.152" y2="475.152" class="net_13"/>
<line x1="245.45899999999972" x2="973.9819999999999" y1="475.152" y2="475.152" class="net_13"/>
<line x1="344.4729999999998" x2="344.47299999999973" y1="155.152" y2="165.152" class="net_13"/>
<line x1="344.47299999999973" x2="33.01999999999998" y1="165.152" y2="165.152" class="net_13"/>
<line x1="33.01999999999998" x2="33.01999999999998" y1="165.152" y2="485.152" class="net_13"/>
<line x1="33.01999999999998" x2="234.45899999999972" y1="485.152" y2="485.152" class="net_13"/>
<line x1="234.45899999999972" x2="234.45899999999972" y1="485.152" y2="605.804" class="net_13"/>
<line x1="234.45899999999972" x2="1114.407" y1="605.804" y2="605.804" class="net_13"/>
<line x1="848.5569999999998" x2="848.5569999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="848.5569999999998" x2="522.5009999999997" y1="165.152" y2="165.152" class="net_13"/>
<line x1="522.5009999999997" x2="522.5009999999997" y1="165.152" y2="215.152" class="net_13"/>
<circle cx="848.5569999999998" cy="165.152" r="2" style="fill:#000" class="net_13"/>
<line x1="522.5009999999997" x2="973.9819999999999" y1="215.152" y2="215.152" class="net_13"/>
<line x1="848.5569999999998" x2="848.5569999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="848.5569999999998" x2="433.48699999999974" y1="165.152" y2="165.152" class="net_13"/>
<line x1="433.48699999999974" x2="433.48699999999974" y1="165.152" y2="295.152" class="net_13"/>
<line x1="433.48699999999974" x2="973.9819999999999" y1="295.152" y2="295.152" class="net_13"/>
<line x1="848.5569999999998" x2="848.5569999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="848.5569999999998" x2="255.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="255.45899999999972" x2="255.45899999999972" y1="165.152" y2="315.152" class="net_13"/>
<line x1="255.45899999999972" x2="973.9819999999999" y1="315.152" y2="315.152" class="net_13"/>
<line x1="848.5569999999998" x2="848.5569999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="848.5569999999998" x2="224.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="224.45899999999972" x2="224.45899999999972" y1="165.152" y2="335.152" class="net_13"/>
<line x1="224.45899999999972" x2="973.9819999999999" y1="335.152" y2="335.152" class="net_13"/>
<line x1="848.5569999999998" x2="848.5569999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="848.5569999999998" x2="214.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="214.45899999999972" x2="214.45899999999972" y1="165.152" y2="435.152" class="net_13"/>
<line x1="214.45899999999972" x2="973.9819999999999" y1="435.152" y2="435.152" class="net_13"/>
<line x1="848.5569999999998" x2="848.5569999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="848.5569999999998" x2="245.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="245.45899999999972" x2="245.45899999999972" y1="165.152" y2="475.152" class="net_13"/>
<line x1="245.45899999999972" x2="973.9819999999999" y1="475.152" y2="475.152" class="net_13"/>
<line x1="848.5569999999998" x2="848.5569999999998" y1="155.152" y2="605.804" class="net_13"/>
<line x1="848.5569999999998" x2="234.45899999999972" y1="605.804" y2="605.804" class="net_13"/>
<line x1="234.45899999999972" x2="234.45899999999972" y1="605.804" y2="605.804" class="net_13"/>
<circle cx="234.45899999999972" cy="605.804" r="2" style="fill:#000" class="net_13"/>
<line x1="234.45899999999972" x2="1114.407" y1="605.804" y2="605.804" class="net_13"/>
<line x1="166.44499999999977" x2="166.4449999999997" y1="155.152" y2="165.152" class="net_13"/>
<line x1="166.4449999999997" x2="522.5009999999997" y1="165.152" y2="165.152" class="net_13"/>
<line x1="522.5009999999997" x2="522.5009999999997" y1="165.152" y2="215.152" class="net_13"/>
<circle cx="166.4449999999997" cy="165.152" r="2" style="fill:#000" class="net_13"/>
<line x1="522.5009999999997" x2="973.9819999999999" y1="215.152" y2="215.152" class="net_13"/>
<line x1="166.44499999999977" x2="166.4449999999997" y1="155.152" y2="165.152" class="net_13"/>
<line x1="166.4449999999997" x2="433.48699999999974" y1="165.152" y2="165.152" class="net_13"/>
<line x1="433.48699999999974" x2="433.48699999999974" y1="165.152" y2="295.152" class="net_13"/>
<line x1="433.48699999999974" x2="973.9819999999999" y1="295.152" y2="295.152" class="net_13"/>
<line x1="166.44499999999977" x2="166.4449999999997" y1="155.152" y2="165.152" class="net_13"/>
<line x1="166.4449999999997" x2="255.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="255.45899999999972" x2="255.45899999999972" y1="165.152" y2="315.152" class="net_13"/>
<line x1="255.45899999999972" x2="973.9819999999999" y1="315.152" y2="315.152" class="net_13"/>
<line x1="166.44499999999977" x2="166.4449999999997" y1="155.152" y2="165.152" class="net_13"/>
<line x1="166.4449999999997" x2="224.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="224.45899999999972" x2="224.45899999999972" y1="165.152" y2="335.152" class="net_13"/>
<line x1="224.45899999999972" x2="973.9819999999999" y1="335.152" y2="335.152" class="net_13"/>
<line x1="166.44499999999977" x2="166.4449999999997" y1="155.152" y2="165.152" class="net_13"/>
<line x1="166.4449999999997" x2="214.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="214.45899999999972" x2="214.45899999999972" y1="165.152" y2="435.152" class="net_13"/>
<line x1="214.45899999999972" x2="973.9819999999999" y1="435.152" y2="435.152" class="net_13"/>
<line x1="166.44499999999977" x2="166.4449999999997" y1="155.152" y2="165.152" class="net_13"/>
<line x1="166.4449999999997" x2="245.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="245.45899999999972" x2="245.45899999999972" y1="165.152" y2="475.152" class="net_13"/>
<line x1="245.45899999999972" x2="973.9819999999999" y1="475.152" y2="475.152" class="net_13"/>
<line x1="166.44499999999977" x2="166.4449999999997" y1="155.152" y2="165.152" class="net_13"/>
<line x1="166.4449999999997" x2="33.01999999999998" y1="165.152" y2="165.152" class="net_13"/>
<line x1="33.01999999999998" x2="33.01999999999998" y1="165.152" y2="485.152" class="net_13"/>
<line x1="33.01999999999998" x2="234.45899999999972" y1="485.152" y2="485.152" class="net_13"/>
<line x1="234.45899999999972" x2="234.45899999999972" y1="485.152" y2="605.804" class="net_13"/>
<line x1="234.45899999999972" x2="1114.407" y1="605.804" y2="605.804" class="net_13"/>
<line x1="700.5289999999998" x2="700.5289999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="700.5289999999998" x2="522.5009999999997" y1="165.152" y2="165.152" class="net_13"/>
<line x1="522.5009999999997" x2="522.5009999999997" y1="165.152" y2="215.152" class="net_13"/>
<circle cx="700.5289999999998" cy="165.152" r="2" style="fill:#000" class="net_13"/>
<line x1="522.5009999999997" x2="973.9819999999999" y1="215.152" y2="215.152" class="net_13"/>
<line x1="700.5289999999998" x2="700.5289999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="700.5289999999998" x2="433.48699999999974" y1="165.152" y2="165.152" class="net_13"/>
<line x1="433.48699999999974" x2="433.48699999999974" y1="165.152" y2="295.152" class="net_13"/>
<line x1="433.48699999999974" x2="973.9819999999999" y1="295.152" y2="295.152" class="net_13"/>
<line x1="700.5289999999998" x2="700.5289999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="700.5289999999998" x2="255.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="255.45899999999972" x2="255.45899999999972" y1="165.152" y2="315.152" class="net_13"/>
<line x1="255.45899999999972" x2="973.9819999999999" y1="315.152" y2="315.152" class="net_13"/>
<line x1="700.5289999999998" x2="700.5289999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="700.5289999999998" x2="224.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="224.45899999999972" x2="224.45899999999972" y1="165.152" y2="335.152" class="net_13"/>
<line x1="224.45899999999972" x2="973.9819999999999" y1="335.152" y2="335.152" class="net_13"/>
<line x1="700.5289999999998" x2="700.5289999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="700.5289999999998" x2="214.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="214.45899999999972" x2="214.45899999999972" y1="165.152" y2="435.152" class="net_13"/>
<line x1="214.45899999999972" x2="973.9819999999999" y1="435.152" y2="435.152" class="net_13"/>
<line x1="700.5289999999998" x2="700.5289999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="700.5289999999998" x2="245.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="245.45899999999972" x2="245.45899999999972" y1="165.152" y2="475.152" class="net_13"/>
<line x1="245.45899999999972" x2="973.9819999999999" y1="475.152" y2="475.152" class="net_13"/>
<line x1="700.5289999999998" x2="700.5289999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="700.5289999999998" x2="491.50099999999975" y1="165.152" y2="165.152" class="net_13"/>
<line x1="491.50099999999975" x2="491.50099999999975" y1="165.152" y2="485.152" class="net_13"/>
<line x1="491.50099999999975" x2="234.45899999999972" y1="485.152" y2="485.152" class="net_13"/>
<line x1="234.45899999999972" x2="234.45899999999972" y1="485.152" y2="605.804" class="net_13"/>
<circle cx="491.50099999999975" cy="165.152" r="2" style="fill:#000" class="net_13"/>
<line x1="234.45899999999972" x2="1114.407" y1="605.804" y2="605.804" class="net_13"/>
<line x1="611.5149999999998" x2="611.5149999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="611.5149999999998" x2="522.5009999999997" y1="165.152" y2="165.152" class="net_13"/>
<line x1="522.5009999999997" x2="522.5009999999997" y1="165.152" y2="215.152" class="net_13"/>
<circle cx="611.5149999999998" cy="165.152" r="2" style="fill:#000" class="net_13"/>
<line x1="522.5009999999997" x2="973.9819999999999" y1="215.152" y2="215.152" class="net_13"/>
<line x1="611.5149999999998" x2="611.5149999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="611.5149999999998" x2="433.48699999999974" y1="165.152" y2="165.152" class="net_13"/>
<line x1="433.48699999999974" x2="433.48699999999974" y1="165.152" y2="295.152" class="net_13"/>
<line x1="433.48699999999974" x2="973.9819999999999" y1="295.152" y2="295.152" class="net_13"/>
<line x1="611.5149999999998" x2="611.5149999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="611.5149999999998" x2="255.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="255.45899999999972" x2="255.45899999999972" y1="165.152" y2="315.152" class="net_13"/>
<line x1="255.45899999999972" x2="973.9819999999999" y1="315.152" y2="315.152" class="net_13"/>
<line x1="611.5149999999998" x2="611.5149999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="611.5149999999998" x2="224.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="224.45899999999972" x2="224.45899999999972" y1="165.152" y2="335.152" class="net_13"/>
<line x1="224.45899999999972" x2="973.9819999999999" y1="335.152" y2="335.152" class="net_13"/>
<line x1="611.5149999999998" x2="611.5149999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="611.5149999999998" x2="214.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="214.45899999999972" x2="214.45899999999972" y1="165.152" y2="435.152" class="net_13"/>
<line x1="214.45899999999972" x2="973.9819999999999" y1="435.152" y2="435.152" class="net_13"/>
<line x1="611.5149999999998" x2="611.5149999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="611.5149999999998" x2="245.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="245.45899999999972" x2="245.45899999999972" y1="165.152" y2="475.152" class="net_13"/>
<line x1="245.45899999999972" x2="973.9819999999999" y1="475.152" y2="475.152" class="net_13"/>
<line x1="611.5149999999998" x2="611.5149999999998" y1="155.152" y2="165.152" class="net_13"/>
<line x1="611.5149999999998" x2="491.50099999999975" y1="165.152" y2="165.152" class="net_13"/>
<line x1="491.50099999999975" x2="491.50099999999975" y1="165.152" y2="485.152" class="net_13"/>
<line x1="491.50099999999975" x2="234.45899999999972" y1="485.152" y2="485.152" class="net_13"/>
<line x1="234.45899999999972" x2="234.45899999999972" y1="485.152" y2="605.804" class="net_13"/>
<line x1="234.45899999999972" x2="1114.407" y1="605.804" y2="605.804" class="net_13"/>
<line x1="789.5429999999998" x2="789.5429999999999" y1="155.152" y2="165.152" class="net_13"/>
<line x1="789.5429999999999" x2="522.5009999999997" y1="165.152" y2="165.152" class="net_13"/>
<line x1="522.5009999999997" x2="522.5009999999997" y1="165.152" y2="215.152" class="net_13"/>
<circle cx="789.5429999999999" cy="165.152" r="2" style="fill:#000" class="net_13"/>
<line x1="522.5009999999997" x2="973.9819999999999" y1="215.152" y2="215.152" class="net_13"/>
<line x1="789.5429999999998" x2="789.5429999999999" y1="155.152" y2="165.152" class="net_13"/>
<line x1="789.5429999999999" x2="433.48699999999974" y1="165.152" y2="165.152" class="net_13"/>
<line x1="433.48699999999974" x2="433.48699999999974" y1="165.152" y2="295.152" class="net_13"/>
<line x1="433.48699999999974" x2="973.9819999999999" y1="295.152" y2="295.152" class="net_13"/>
<line x1="789.5429999999998" x2="789.5429999999999" y1="155.152" y2="165.152" class="net_13"/>
<line x1="789.5429999999999" x2="255.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="255.45899999999972" x2="255.45899999999972" y1="165.152" y2="315.152" class="net_13"/>
<line x1="255.45899999999972" x2="973.9819999999999" y1="315.152" y2="315.152" class="net_13"/>
<line x1="789.5429999999998" x2="789.5429999999999" y1="155.152" y2="165.152" class="net_13"/>
<line x1="789.5429999999999" x2="224.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="224.45899999999972" x2="224.45899999999972" y1="165.152" y2="335.152" class="net_13"/>
<line x1="224.45899999999972" x2="973.9819999999999" y1="335.152" y2="335.152" class="net_13"/>
<line x1="789.5429999999998" x2="789.5429999999999" y1="155.152" y2="165.152" class="net_13"/>
<line x1="789.5429999999999" x2="214.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="214.45899999999972" x2="214.45899999999972" y1="165.152" y2="435.152" class="net_13"/>
<line x1="214.45899999999972" x2="973.9819999999999" y1="435.152" y2="435.152" class="net_13"/>
<line x1="789.5429999999998" x2="789.5429999999999" y1="155.152" y2="165.152" class="net_13"/>
<line x1="789.5429999999999" x2="245.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="245.45899999999972" x2="245.45899999999972" y1="165.152" y2="475.152" class="net_13"/>
<line x1="245.45899999999972" x2="973.9819999999999" y1="475.152" y2="475.152" class="net_13"/>
<line x1="789.5429999999998" x2="789.5429999999999" y1="155.152" y2="165.152" class="net_13"/>
<line x1="789.5429999999999" x2="491.50099999999975" y1="165.152" y2="165.152" class="net_13"/>
<line x1="491.50099999999975" x2="491.50099999999975" y1="165.152" y2="485.152" class="net_13"/>
<line x1="491.50099999999975" x2="234.45899999999972" y1="485.152" y2="485.152" class="net_13"/>
<line x1="234.45899999999972" x2="234.45899999999972" y1="485.152" y2="605.804" class="net_13"/>
<line x1="234.45899999999972" x2="1114.407" y1="605.804" y2="605.804" class="net_13"/>
<line x1="73.77399999999997" x2="73.77399999999989" y1="155.152" y2="165.152" class="net_13"/>
<line x1="73.77399999999989" x2="522.5009999999997" y1="165.152" y2="165.152" class="net_13"/>
<line x1="522.5009999999997" x2="522.5009999999997" y1="165.152" y2="215.152" class="net_13"/>
<circle cx="73.77399999999989" cy="165.152" r="2" style="fill:#000" class="net_13"/>
<circle cx="522.5009999999997" cy="165.152" r="2" style="fill:#000" class="net_13"/>
<line x1="522.5009999999997" x2="973.9819999999999" y1="215.152" y2="215.152" class="net_13"/>
<line x1="73.77399999999997" x2="73.77399999999989" y1="155.152" y2="165.152" class="net_13"/>
<line x1="73.77399999999989" x2="433.48699999999974" y1="165.152" y2="165.152" class="net_13"/>
<line x1="433.48699999999974" x2="433.48699999999974" y1="165.152" y2="295.152" class="net_13"/>
<circle cx="433.48699999999974" cy="165.152" r="2" style="fill:#000" class="net_13"/>
<line x1="433.48699999999974" x2="973.9819999999999" y1="295.152" y2="295.152" class="net_13"/>
<line x1="73.77399999999997" x2="73.77399999999989" y1="155.152" y2="165.152" class="net_13"/>
<line x1="73.77399999999989" x2="255.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="255.45899999999972" x2="255.45899999999972" y1="165.152" y2="315.152" class="net_13"/>
<circle cx="255.45899999999972" cy="165.152" r="2" style="fill:#000" class="net_13"/>
<line x1="255.45899999999972" x2="973.9819999999999" y1="315.152" y2="315.152" class="net_13"/>
<line x1="73.77399999999997" x2="73.77399999999989" y1="155.152" y2="165.152" class="net_13"/>
<line x1="73.77399999999989" x2="224.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="224.45899999999972" x2="224.45899999999972" y1="165.152" y2="335.152" class="net_13"/>
<circle cx="224.45899999999972" cy="165.152" r="2" style="fill:#000" class="net_13"/>
<line x1="224.45899999999972" x2="973.9819999999999" y1="335.152" y2="335.152" class="net_13"/>
<line x1="73.77399999999997" x2="73.77399999999989" y1="155.152" y2="165.152" class="net_13"/>
<line x1="73.77399999999989" x2="214.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="214.45899999999972" x2="214.45899999999972" y1="165.152" y2="435.152" class="net_13"/>
<circle cx="214.45899999999972" cy="165.152" r="2" style="fill:#000" class="net_13"/>
<line x1="214.45899999999972" x2="973.9819999999999" y1="435.152" y2="435.152" class="net_13"/>
<line x1="73.77399999999997" x2="73.77399999999989" y1="155.152" y2="165.152" class="net_13"/>
<line x1="73.77399999999989" x2="245.45899999999972" y1="165.152" y2="165.152" class="net_13"/>
<line x1="245.45899999999972" x2="245.45899999999972" y1="165.152" y2="475.152" class="net_13"/>
<circle cx="245.45899999999972" cy="165.152" r="2" style="fill:#000" class="net_13"/>
<line x1="245.45899999999972" x2="973.9819999999999" y1="475.152" y2="475.152" class="net_13"/>
<line x1="73.77399999999997" x2="73.77399999999989" y1="155.152" y2="165.152" class="net_13"/>
<line x1="73.77399999999989" x2="33.01999999999998" y1="165.152" y2="165.152" class="net_13"/>
<line x1="33.01999999999998" x2="33.01999999999998" y1="165.152" y2="485.152" class="net_13"/>
<line x1="33.01999999999998" x2="234.45899999999972" y1="485.152" y2="485.152" class="net_13"/>
<line x1="234.45899999999972" x2="234.45899999999972" y1="485.152" y2="605.804" class="net_13"/>
<circle cx="234.45899999999972" cy="485.152" r="2" style="fill:#000" class="net_13"/>
<line x1="234.45899999999972" x2="1114.407" y1="605.804" y2="605.804" class="net_13"/>
<line x1="940.9819999999999" x2="940.9819999999999" y1="585.804" y2="595.804" class="net_20"/>
<line x1="940.9819999999999" x2="512.5009999999997" y1="595.804" y2="595.804" class="net_20"/>
<line x1="512.5009999999997" x2="512.5009999999997" y1="595.804" y2="235.152" class="net_20"/>
<line x1="512.5009999999997" x2="973.9819999999999" y1="235.152" y2="235.152" class="net_20"/>
<line x1="1172.407" x2="1172.407" y1="505.304" y2="515.304" class="net_17"/>
<line x1="1172.407" x2="1097.407" y1="515.304" y2="515.304" class="net_17"/>
<line x1="1097.407" x2="1097.407" y1="515.304" y2="545.804" class="net_17"/>
<circle cx="1172.407" cy="515.304" r="2" style="fill:#000" class="net_17"/>
<line x1="1097.407" x2="1114.407" y1="545.804" y2="545.804" class="net_17"/>
<line x1="1172.407" x2="1172.407" y1="505.304" y2="545.804" class="net_17"/>
<line x1="1172.407" x2="1145.407" y1="545.804" y2="545.804" class="net_17"/>
<line x1="1114.407" x2="12.019999999999982" y1="625.804" y2="625.804" class="net_11"/>
<line x1="12.019999999999982" x2="12.019999999999982" y1="625.804" y2="668.304" class="net_11"/>
<line x1="12.019999999999982" x2="1162.407" y1="668.304" y2="668.304" class="net_11"/>
<line x1="1162.407" x2="1162.407" y1="668.304" y2="565.804" class="net_11"/>
<line x1="1162.407" x2="1145.407" y1="565.804" y2="565.804" class="net_11"/>
</svg>

After

Width:  |  Height:  |  Size: 73 KiB

View file

@ -0,0 +1,208 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:s="https://github.com/nturley/netlistsvg" width="412.92400000000004" height="332.80400000000003"><rect id="pm_k1-bg" x="0" y="0" width="100%" height="100%" fill="#ffffff"/>
<style>svg {
stroke: #000;
fill: none;
stroke-linejoin: round;
stroke-linecap: round;
}
text {
fill: #000;
stroke: none;
font-size: 10px;
font-weight: bold;
font-family: "Courier New", monospace;
}
.skidl_text {
fill: #999;
stroke: none;
font-weight: bold;
font-family: consolas, "Courier New", monospace;
}
.pin_num_text {
fill: #840000;
}
.pin_name_text {
fill: #008484;
}
.net_name_text {
font-style: italic;
fill: #840084;
}
.part_text {
fill: #840000;
}
.part_ref_text {
fill: #008484;
}
.part_name_text {
fill: #008484;
}
.pen_fill {
fill: #840000;
}
.background_fill {
fill: #FFFFC2
}
.nodelabel {
text-anchor: middle;
}
.inputPortLabel {
text-anchor: end;
}
.splitjoinBody {
fill: #000;
}
.symbol {
stroke-linejoin: round;
stroke-linecap: round;
stroke: #840000;
}
.detail {
stroke-linejoin: round;
stroke-linecap: round;
fill: #000;
}</style>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(22.04,42)" id="cell_C1">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C1 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C1 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C1 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C1 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C1</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(111.054,42)" id="cell_C2">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C2 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C2 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C2 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C2 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C2</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(200.068,22)" id="cell_R1">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R1 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R1 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R1 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R1</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">10k</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(282.98600000000005,22)" id="cell_R2">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R2 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R2 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R2 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R2</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">10k</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(200.068,237.65200000000004)" id="cell_R3">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R3 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R3 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R3 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R3</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">10k</text>
</g>
<g s:type="generic" s:width="30" s:height="40" transform="translate(342.90400000000005,94.65200000000002)" id="cell_U5">
<text x="15" y="-4" class="nodelabel cell_U5" s:attribute="ref">OPA1612_1_</text>
<rect width="30" height="120" x="0" y="0" s:generic="body" class="cell_U5"/>
<g transform="translate(0,10)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U5~2">
<text x="-3" y="-4" class="inputPortLabel cell_U5">2</text>
</g>
<g transform="translate(0,30)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U5~3">
<text x="-3" y="-4" class="inputPortLabel cell_U5">3</text>
</g>
<g transform="translate(0,50)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U5~4">
<text x="-3" y="-4" class="inputPortLabel cell_U5">4</text>
</g>
<g transform="translate(0,70)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U5~5">
<text x="-3" y="-4" class="inputPortLabel cell_U5">5</text>
</g>
<g transform="translate(0,90)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U5~6">
<text x="-3" y="-4" class="inputPortLabel cell_U5">6</text>
</g>
<g transform="translate(0,110)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U5~8">
<text x="-3" y="-4" class="inputPortLabel cell_U5">8</text>
</g>
<g transform="translate(30,10)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U5~1">
<text x="5" y="-4" class="cell_U5">1</text>
</g>
<g transform="translate(30,30)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U5~7">
<text x="5" y="-4" class="cell_U5">7</text>
</g>
</g>
<line x1="342.90400000000005" x2="12.04" y1="205.15200000000002" y2="205.152" class="net_2"/>
<line x1="12.04" x2="12.04" y1="205.152" y2="32" class="net_2"/>
<line x1="12.04" x2="41.547" y1="32" y2="32" class="net_2"/>
<line x1="41.547" x2="41.547" y1="32" y2="42" class="net_2"/>
<line x1="342.90400000000005" x2="325.90400000000005" y1="145.15200000000002" y2="145.152" class="net_7"/>
<line x1="325.90400000000005" x2="325.90400000000005" y1="145.152" y2="12" class="net_7"/>
<line x1="325.90400000000005" x2="130.561" y1="12" y2="12" class="net_7"/>
<line x1="130.561" x2="130.561" y1="12" y2="42" class="net_7"/>
<line x1="209.822" x2="209.822" y1="95.152" y2="105.152" class="net_4"/>
<circle cx="209.822" cy="105.152" r="2" style="fill:#000" class="net_4"/>
<line x1="209.822" x2="342.90400000000005" y1="105.152" y2="105.15200000000002" class="net_4"/>
<line x1="292.74000000000007" x2="292.74000000000007" y1="95.152" y2="105.152" class="net_4"/>
<line x1="292.74000000000007" x2="209.822" y1="105.152" y2="105.152" class="net_4"/>
<line x1="209.822" x2="209.822" y1="105.152" y2="105.152" class="net_4"/>
<circle cx="209.822" cy="105.152" r="2" style="fill:#000" class="net_4"/>
<circle cx="209.822" cy="105.152" r="2" style="fill:#000" class="net_4"/>
<line x1="209.822" x2="342.90400000000005" y1="105.152" y2="105.15200000000002" class="net_4"/>
<line x1="342.90400000000005" x2="209.822" y1="105.15200000000002" y2="105.152" class="net_4"/>
<circle cx="209.822" cy="105.152" r="2" style="fill:#000" class="net_4"/>
<line x1="209.822" x2="209.822" y1="105.152" y2="237.65200000000004" class="net_4"/>
<line x1="41.547" x2="41.547" y1="115.152" y2="125.152" class="net_8"/>
<line x1="41.547" x2="130.561" y1="125.152" y2="125.152" class="net_8"/>
<line x1="130.561" x2="130.561" y1="125.152" y2="125.152" class="net_8"/>
<circle cx="41.547" cy="125.152" r="2" style="fill:#000" class="net_8"/>
<circle cx="130.561" cy="125.152" r="2" style="fill:#000" class="net_8"/>
<line x1="130.561" x2="342.90400000000005" y1="125.152" y2="125.15200000000002" class="net_8"/>
<line x1="41.547" x2="41.547" y1="115.152" y2="165.152" class="net_8"/>
<line x1="41.547" x2="342.90400000000005" y1="165.152" y2="165.15200000000002" class="net_8"/>
<line x1="130.561" x2="130.561" y1="115.152" y2="125.152" class="net_8"/>
<line x1="130.561" x2="342.90400000000005" y1="125.152" y2="125.15200000000002" class="net_8"/>
<line x1="130.561" x2="130.561" y1="115.152" y2="125.152" class="net_8"/>
<line x1="130.561" x2="41.547" y1="125.152" y2="125.152" class="net_8"/>
<line x1="41.547" x2="41.547" y1="125.152" y2="165.152" class="net_8"/>
<line x1="41.547" x2="342.90400000000005" y1="165.152" y2="165.15200000000002" class="net_8"/>
<line x1="209.822" x2="209.822" y1="310.80400000000003" y2="320.80400000000003" class="net_3"/>
<line x1="209.822" x2="400.90400000000005" y1="320.80400000000003" y2="320.80400000000003" class="net_3"/>
<line x1="400.90400000000005" x2="400.90400000000005" y1="320.80400000000003" y2="105.152" class="net_3"/>
<line x1="400.90400000000005" x2="373.90400000000005" y1="105.152" y2="105.15200000000002" class="net_3"/>
<line x1="342.90400000000005" x2="219.822" y1="185.15200000000002" y2="185.152" class="net_9"/>
<line x1="219.822" x2="219.822" y1="185.152" y2="227.65200000000004" class="net_9"/>
<line x1="219.822" x2="390.90400000000005" y1="227.65200000000004" y2="227.65200000000004" class="net_9"/>
<line x1="390.90400000000005" x2="390.90400000000005" y1="227.65200000000004" y2="125.152" class="net_9"/>
<line x1="390.90400000000005" x2="373.90400000000005" y1="125.152" y2="125.15200000000002" class="net_9"/>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,406 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:s="https://github.com/nturley/netlistsvg" width="532.7560000000001" height="691.956"><rect id="pm_k1-bg" x="0" y="0" width="100%" height="100%" fill="#ffffff"/>
<style>svg {
stroke: #000;
fill: none;
stroke-linejoin: round;
stroke-linecap: round;
}
text {
fill: #000;
stroke: none;
font-size: 10px;
font-weight: bold;
font-family: "Courier New", monospace;
}
.skidl_text {
fill: #999;
stroke: none;
font-weight: bold;
font-family: consolas, "Courier New", monospace;
}
.pin_num_text {
fill: #840000;
}
.pin_name_text {
fill: #008484;
}
.net_name_text {
font-style: italic;
fill: #840084;
}
.part_text {
fill: #840000;
}
.part_ref_text {
fill: #008484;
}
.part_name_text {
fill: #008484;
}
.pen_fill {
fill: #840000;
}
.background_fill {
fill: #FFFFC2
}
.nodelabel {
text-anchor: middle;
}
.inputPortLabel {
text-anchor: end;
}
.splitjoinBody {
fill: #000;
}
.symbol {
stroke-linejoin: round;
stroke-linecap: round;
stroke: #840000;
}
.detail {
stroke-linejoin: round;
stroke-linecap: round;
fill: #000;
}</style>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(167.66000000000003,165)" id="cell_C1">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C1 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C1 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C1 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C1 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C1</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(260.66,165)" id="cell_C2">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C2 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C2 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C2 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C2 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C2</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100nF</text>
</g>
<g s:type="C_1_" s:width="39.014" s:height="73.152" transform="translate(167.66000000000003,431.152)" id="cell_C3">
<s:alias val="C_1_"/>
<polyline points="0.000,29.261 39.014,29.261" style="stroke-width:4.877" class="cell_C3 symbol none"/>
<polyline points="0.000,43.891 39.014,43.891" style="stroke-width:4.877" class="cell_C3 symbol none"/>
<polyline points="19.507,0.000 19.507,26.822" style="stroke-width:0.960" class="cell_C3 symbol none"/>
<text class="pin_num_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="19.507" y="26.822" transform="rotate(-90 19.507 26.822)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="19.507" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="19.507,73.152 19.507,46.330" style="stroke-width:0.960" class="cell_C3 symbol none"/>
<text class="pin_num_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="19.507" y="46.330" transform="rotate(-90 19.507 46.330)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="19.507" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="25.603" y="12.192" transform="rotate(0 25.603 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">C3</text>
<text class="part_name_text" x="25.603" y="60.960" transform="rotate(0 25.603 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">10nF</text>
</g>
<g s:type="generic" s:width="30" s:height="40" transform="translate(240.16700000000003,288.152)" id="cell_K2">
<text x="15" y="-4" class="nodelabel cell_K2" s:attribute="ref">TQ2SA-5V_1_</text>
<rect width="30" height="120" x="0" y="0" s:generic="body" class="cell_K2"/>
<g transform="translate(0,10)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_K2~1">
<text x="-3" y="-4" class="inputPortLabel cell_K2">1</text>
</g>
<g transform="translate(0,30)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_K2~3">
<text x="-3" y="-4" class="inputPortLabel cell_K2">3</text>
</g>
<g transform="translate(0,50)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_K2~4">
<text x="-3" y="-4" class="inputPortLabel cell_K2">4</text>
</g>
<g transform="translate(0,70)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_K2~7">
<text x="-3" y="-4" class="inputPortLabel cell_K2">7</text>
</g>
<g transform="translate(0,90)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_K2~8">
<text x="-3" y="-4" class="inputPortLabel cell_K2">8</text>
</g>
<g transform="translate(0,110)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_K2~10">
<text x="-3" y="-4" class="inputPortLabel cell_K2">10</text>
</g>
</g>
<g s:type="generic" s:width="30" s:height="40" transform="translate(269.67400000000004,483.804)" id="cell_K3">
<text x="15" y="-4" class="nodelabel cell_K3" s:attribute="ref">TQ2SA-5V_1_</text>
<rect width="30" height="80" x="0" y="0" s:generic="body" class="cell_K3"/>
<g transform="translate(0,10)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_K3~1">
<text x="-3" y="-4" class="inputPortLabel cell_K3">1</text>
</g>
<g transform="translate(0,30)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_K3~3">
<text x="-3" y="-4" class="inputPortLabel cell_K3">3</text>
</g>
<g transform="translate(0,50)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_K3~4">
<text x="-3" y="-4" class="inputPortLabel cell_K3">4</text>
</g>
<g transform="translate(0,70)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_K3~10">
<text x="-3" y="-4" class="inputPortLabel cell_K3">10</text>
</g>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(487.838,165)" id="cell_R1">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R1 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R1 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R1 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R1</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">47</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(404.92,175)" id="cell_R2">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R2 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R2 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R2 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R2</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">47</text>
</g>
<g s:type="R_1_" s:width="32.918" s:height="73.152" transform="translate(135.906,586.804)" id="cell_R3">
<s:alias val="R_1_"/>
<rect x="0.000" y="12.192" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_R3 symbol none"/>
<polyline points="9.754,0.000 9.754,12.192" style="stroke-width:0.960" class="cell_R3 symbol none"/>
<text class="pin_num_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="9.754" y="12.192" transform="rotate(-90 9.754 12.192)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">~</text>
<g s:x="9.754" s:y="0.000" s:pid="1" s:position="top"/>
<polyline points="9.754,73.152 9.754,60.960" style="stroke-width:0.960" class="cell_R3 symbol none"/>
<text class="pin_num_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="" text-anchor="end">2</text>
<text class="pin_name_text" x="9.754" y="60.960" transform="rotate(-90 9.754 60.960)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">~</text>
<g s:x="9.754" s:y="73.152" s:pid="2" s:position="bottom"/>
<text class="part_ref_text" x="29.261" y="36.576" transform="rotate(-90 29.261 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">R3</text>
<text class="part_name_text" x="9.754" y="36.576" transform="rotate(-90 9.754 36.576)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">100</text>
</g>
<g s:type="R_Potentiometer_1_" s:width="82.906" s:height="146.304" transform="translate(22,431.15200000000004)" id="cell_RV1">
<s:alias val="R_Potentiometer_1_"/>
<rect x="36.576" y="85.344" width="19.507" height="48.768" style="stroke-width:2.438" class="cell_RV1 symbol none"/>
<polyline points="57.302,109.728 68.275,104.851 68.275,114.605 57.302,109.728" style="stroke-width:0.960" class="cell_RV1 symbol outline"/>
<polyline points="70.714,109.728 60.960,109.728" style="stroke-width:0.960" class="cell_RV1 symbol none"/>
<polyline points="46.330,73.152 46.330,85.344" style="stroke-width:0.960" class="cell_RV1 symbol none"/>
<text class="pin_num_text" x="46.330" y="85.344" transform="rotate(-90 46.330 85.344)" style="font-size:12.192" dominant-baseline="" text-anchor="start">1</text>
<text class="pin_name_text" x="46.330" y="85.344" transform="rotate(-90 46.330 85.344)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">1</text>
<g s:x="46.330" s:y="73.152" s:pid="1" s:position="top"/>
<polyline points="46.330,146.304 46.330,134.112" style="stroke-width:0.960" class="cell_RV1 symbol none"/>
<text class="pin_num_text" x="46.330" y="134.112" transform="rotate(-90 46.330 134.112)" style="font-size:12.192" dominant-baseline="" text-anchor="end">3</text>
<text class="pin_name_text" x="46.330" y="134.112" transform="rotate(-90 46.330 134.112)" style="font-size:12.192" dominant-baseline="central" text-anchor="start">3</text>
<g s:x="46.330" s:y="146.304" s:pid="3" s:position="bottom"/>
<polyline points="82.906,109.728 70.714,109.728" style="stroke-width:0.960" class="cell_RV1 symbol none"/>
<text class="pin_num_text" x="70.714" y="109.728" transform="rotate(0 70.714 109.728)" style="font-size:12.192" dominant-baseline="" text-anchor="start">2</text>
<text class="pin_name_text" x="70.714" y="109.728" transform="rotate(0 70.714 109.728)" style="font-size:12.192" dominant-baseline="central" text-anchor="end">2</text>
<g s:x="82.906" s:y="109.728" s:pid="2" s:position="right"/>
<text class="part_ref_text" x="3.658" y="109.728" transform="rotate(-90 3.658 109.728)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="ref">RV1</text>
<text class="part_name_text" x="21.946" y="109.728" transform="rotate(-90 21.946 109.728)" style="font-size:12.192" dominant-baseline="central" text-anchor="start" s:attribute="value">10k</text>
</g>
<g s:type="generic" s:width="30" s:height="40" transform="translate(366.67400000000004,22)" id="cell_U6">
<text x="15" y="-4" class="nodelabel cell_U6" s:attribute="ref">THAT1646_1_</text>
<rect width="30" height="120" x="0" y="0" s:generic="body" class="cell_U6"/>
<g transform="translate(0,10)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U6~2">
<text x="-3" y="-4" class="inputPortLabel cell_U6">2</text>
</g>
<g transform="translate(0,30)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U6~3">
<text x="-3" y="-4" class="inputPortLabel cell_U6">3</text>
</g>
<g transform="translate(0,50)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U6~4">
<text x="-3" y="-4" class="inputPortLabel cell_U6">4</text>
</g>
<g transform="translate(0,70)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U6~5">
<text x="-3" y="-4" class="inputPortLabel cell_U6">5</text>
</g>
<g transform="translate(0,90)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U6~6">
<text x="-3" y="-4" class="inputPortLabel cell_U6">6</text>
</g>
<g transform="translate(0,110)" s:x="0" s:y="10" s:pid="in0" s:position="left" id="port_U6~7">
<text x="-3" y="-4" class="inputPortLabel cell_U6">7</text>
</g>
<g transform="translate(30,10)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U6~1">
<text x="5" y="-4" class="cell_U6">1</text>
</g>
<g transform="translate(30,30)" s:x="30" s:y="10" s:pid="out0" s:position="right" id="port_U6~8">
<text x="5" y="-4" class="cell_U6">8</text>
</g>
</g>
<line x1="366.67400000000004" x2="319.67400000000004" y1="112.5" y2="112.5" class="net_11"/>
<line x1="319.67400000000004" x2="319.67400000000004" y1="112.5" y2="145" class="net_11"/>
<line x1="319.67400000000004" x2="187.16700000000003" y1="145" y2="145" class="net_11"/>
<line x1="187.16700000000003" x2="187.16700000000003" y1="145" y2="165" class="net_11"/>
<line x1="366.67400000000004" x2="329.67400000000004" y1="92.5" y2="92.5" class="net_2"/>
<line x1="329.67400000000004" x2="329.67400000000004" y1="92.5" y2="155" class="net_2"/>
<line x1="329.67400000000004" x2="280.16700000000003" y1="155" y2="155" class="net_2"/>
<line x1="280.16700000000003" x2="280.16700000000003" y1="155" y2="165" class="net_2"/>
<line x1="187.16700000000003" x2="187.16700000000003" y1="238.152" y2="248.152" class="net_3"/>
<line x1="187.16700000000003" x2="177.16700000000003" y1="248.152" y2="248.152" class="net_3"/>
<line x1="177.16700000000003" x2="177.16700000000003" y1="248.152" y2="338.652" class="net_3"/>
<circle cx="177.16700000000003" cy="248.152" r="2" style="fill:#000" class="net_3"/>
<circle cx="177.16700000000003" cy="338.652" r="2" style="fill:#000" class="net_3"/>
<line x1="177.16700000000003" x2="240.16700000000003" y1="338.652" y2="338.652" class="net_3"/>
<line x1="187.16700000000003" x2="187.16700000000003" y1="238.152" y2="358.652" class="net_3"/>
<circle cx="187.16700000000003" cy="358.652" r="2" style="fill:#000" class="net_3"/>
<line x1="187.16700000000003" x2="240.16700000000003" y1="358.652" y2="358.652" class="net_3"/>
<line x1="187.16700000000003" x2="187.16700000000003" y1="238.152" y2="248.152" class="net_3"/>
<line x1="187.16700000000003" x2="156.66000000000003" y1="248.152" y2="248.152" class="net_3"/>
<line x1="156.66000000000003" x2="156.66000000000003" y1="248.152" y2="514.304" class="net_3"/>
<circle cx="156.66000000000003" cy="248.152" r="2" style="fill:#000" class="net_3"/>
<circle cx="156.66000000000003" cy="514.304" r="2" style="fill:#000" class="net_3"/>
<line x1="156.66000000000003" x2="269.67400000000004" y1="514.304" y2="514.304" class="net_3"/>
<line x1="187.16700000000003" x2="187.16700000000003" y1="238.152" y2="248.152" class="net_3"/>
<line x1="187.16700000000003" x2="145.66000000000003" y1="248.152" y2="248.152" class="net_3"/>
<line x1="145.66000000000003" x2="145.66000000000003" y1="248.152" y2="135" class="net_3"/>
<line x1="145.66000000000003" x2="309.67400000000004" y1="135" y2="135" class="net_3"/>
<line x1="309.67400000000004" x2="309.67400000000004" y1="135" y2="52.5" class="net_3"/>
<circle cx="145.66000000000003" cy="248.152" r="2" style="fill:#000" class="net_3"/>
<circle cx="187.16700000000003" cy="248.152" r="2" style="fill:#000" class="net_3"/>
<line x1="309.67400000000004" x2="366.67400000000004" y1="52.5" y2="52.5" class="net_3"/>
<line x1="280.16700000000003" x2="280.16700000000003" y1="238.152" y2="248.152" class="net_3"/>
<line x1="280.16700000000003" x2="177.16700000000003" y1="248.152" y2="248.152" class="net_3"/>
<line x1="177.16700000000003" x2="177.16700000000003" y1="248.152" y2="338.652" class="net_3"/>
<circle cx="280.16700000000003" cy="248.152" r="2" style="fill:#000" class="net_3"/>
<circle cx="177.16700000000003" cy="338.652" r="2" style="fill:#000" class="net_3"/>
<line x1="177.16700000000003" x2="240.16700000000003" y1="338.652" y2="338.652" class="net_3"/>
<line x1="280.16700000000003" x2="280.16700000000003" y1="238.152" y2="248.152" class="net_3"/>
<line x1="280.16700000000003" x2="187.16700000000003" y1="248.152" y2="248.152" class="net_3"/>
<line x1="187.16700000000003" x2="187.16700000000003" y1="248.152" y2="358.652" class="net_3"/>
<circle cx="187.16700000000003" cy="358.652" r="2" style="fill:#000" class="net_3"/>
<line x1="187.16700000000003" x2="240.16700000000003" y1="358.652" y2="358.652" class="net_3"/>
<line x1="280.16700000000003" x2="280.16700000000003" y1="238.152" y2="421.152" class="net_3"/>
<line x1="280.16700000000003" x2="156.66000000000003" y1="421.152" y2="421.152" class="net_3"/>
<line x1="156.66000000000003" x2="156.66000000000003" y1="421.152" y2="514.304" class="net_3"/>
<circle cx="280.16700000000003" cy="421.152" r="2" style="fill:#000" class="net_3"/>
<circle cx="156.66000000000003" cy="514.304" r="2" style="fill:#000" class="net_3"/>
<line x1="156.66000000000003" x2="269.67400000000004" y1="514.304" y2="514.304" class="net_3"/>
<line x1="280.16700000000003" x2="280.16700000000003" y1="238.152" y2="248.152" class="net_3"/>
<line x1="280.16700000000003" x2="250.66000000000003" y1="248.152" y2="248.152" class="net_3"/>
<line x1="250.66000000000003" x2="250.66000000000003" y1="248.152" y2="135" class="net_3"/>
<line x1="250.66000000000003" x2="309.67400000000004" y1="135" y2="135" class="net_3"/>
<line x1="309.67400000000004" x2="309.67400000000004" y1="135" y2="52.5" class="net_3"/>
<circle cx="250.66000000000003" cy="135" r="2" style="fill:#000" class="net_3"/>
<circle cx="250.66000000000003" cy="248.152" r="2" style="fill:#000" class="net_3"/>
<line x1="309.67400000000004" x2="366.67400000000004" y1="52.5" y2="52.5" class="net_3"/>
<line x1="240.16700000000003" x2="177.16700000000003" y1="338.652" y2="338.652" class="net_3"/>
<line x1="177.16700000000003" x2="177.16700000000003" y1="338.652" y2="421.152" class="net_3"/>
<line x1="177.16700000000003" x2="187.16700000000003" y1="421.152" y2="421.152" class="net_3"/>
<circle cx="177.16700000000003" cy="421.152" r="2" style="fill:#000" class="net_3"/>
<circle cx="177.16700000000003" cy="338.652" r="2" style="fill:#000" class="net_3"/>
<line x1="187.16700000000003" x2="187.16700000000003" y1="421.152" y2="431.152" class="net_3"/>
<line x1="240.16700000000003" x2="177.16700000000003" y1="338.652" y2="338.652" class="net_3"/>
<line x1="177.16700000000003" x2="177.16700000000003" y1="338.652" y2="421.152" class="net_3"/>
<line x1="177.16700000000003" x2="125.906" y1="421.152" y2="421.152" class="net_3"/>
<line x1="125.906" x2="125.906" y1="421.152" y2="576.804" class="net_3"/>
<line x1="125.906" x2="145.66000000000003" y1="576.804" y2="576.804" class="net_3"/>
<circle cx="125.906" cy="421.152" r="2" style="fill:#000" class="net_3"/>
<circle cx="125.906" cy="576.804" r="2" style="fill:#000" class="net_3"/>
<circle cx="145.66000000000003" cy="576.804" r="2" style="fill:#000" class="net_3"/>
<circle cx="177.16700000000003" cy="338.652" r="2" style="fill:#000" class="net_3"/>
<line x1="145.66000000000003" x2="145.66" y1="576.804" y2="586.804" class="net_3"/>
<line x1="240.16700000000003" x2="177.16700000000003" y1="338.652" y2="338.652" class="net_3"/>
<line x1="177.16700000000003" x2="177.16700000000003" y1="338.652" y2="421.152" class="net_3"/>
<line x1="177.16700000000003" x2="125.906" y1="421.152" y2="421.152" class="net_3"/>
<line x1="125.906" x2="125.906" y1="421.152" y2="679.956" class="net_3"/>
<line x1="125.906" x2="68.32999999999998" y1="679.956" y2="679.956" class="net_3"/>
<circle cx="125.906" cy="679.956" r="2" style="fill:#000" class="net_3"/>
<circle cx="177.16700000000003" cy="338.652" r="2" style="fill:#000" class="net_3"/>
<line x1="68.32999999999998" x2="68.33" y1="679.956" y2="577.456" class="net_3"/>
<line x1="240.16700000000003" x2="187.16700000000003" y1="358.652" y2="358.652" class="net_3"/>
<circle cx="187.16700000000003" cy="358.652" r="2" style="fill:#000" class="net_3"/>
<line x1="187.16700000000003" x2="187.16700000000003" y1="358.652" y2="431.152" class="net_3"/>
<line x1="240.16700000000003" x2="187.16700000000003" y1="358.652" y2="358.652" class="net_3"/>
<line x1="187.16700000000003" x2="187.16700000000003" y1="358.652" y2="421.152" class="net_3"/>
<line x1="187.16700000000003" x2="145.66000000000003" y1="421.152" y2="421.152" class="net_3"/>
<circle cx="187.16700000000003" cy="421.152" r="2" style="fill:#000" class="net_3"/>
<circle cx="145.66000000000003" cy="421.152" r="2" style="fill:#000" class="net_3"/>
<circle cx="187.16700000000003" cy="358.652" r="2" style="fill:#000" class="net_3"/>
<line x1="145.66000000000003" x2="145.66" y1="421.152" y2="586.804" class="net_3"/>
<line x1="240.16700000000003" x2="187.16700000000003" y1="358.652" y2="358.652" class="net_3"/>
<line x1="187.16700000000003" x2="187.16700000000003" y1="358.652" y2="421.152" class="net_3"/>
<line x1="187.16700000000003" x2="12" y1="421.152" y2="421.152" class="net_3"/>
<line x1="12" x2="12" y1="421.152" y2="679.956" class="net_3"/>
<line x1="12" x2="68.32999999999998" y1="679.956" y2="679.956" class="net_3"/>
<circle cx="68.32999999999998" cy="679.956" r="2" style="fill:#000" class="net_3"/>
<circle cx="187.16700000000003" cy="358.652" r="2" style="fill:#000" class="net_3"/>
<line x1="68.32999999999998" x2="68.33" y1="679.956" y2="577.456" class="net_3"/>
<line x1="269.67400000000004" x2="156.66000000000003" y1="514.304" y2="514.304" class="net_3"/>
<line x1="156.66000000000003" x2="156.66000000000003" y1="514.304" y2="421.152" class="net_3"/>
<line x1="156.66000000000003" x2="187.16700000000003" y1="421.152" y2="421.152" class="net_3"/>
<circle cx="156.66000000000003" cy="421.152" r="2" style="fill:#000" class="net_3"/>
<circle cx="156.66000000000003" cy="514.304" r="2" style="fill:#000" class="net_3"/>
<line x1="187.16700000000003" x2="187.16700000000003" y1="421.152" y2="431.152" class="net_3"/>
<line x1="269.67400000000004" x2="156.66000000000003" y1="514.304" y2="514.304" class="net_3"/>
<line x1="156.66000000000003" x2="156.66000000000003" y1="514.304" y2="576.804" class="net_3"/>
<line x1="156.66000000000003" x2="145.66000000000003" y1="576.804" y2="576.804" class="net_3"/>
<circle cx="156.66000000000003" cy="576.804" r="2" style="fill:#000" class="net_3"/>
<circle cx="156.66000000000003" cy="514.304" r="2" style="fill:#000" class="net_3"/>
<line x1="145.66000000000003" x2="145.66" y1="576.804" y2="586.804" class="net_3"/>
<line x1="269.67400000000004" x2="156.66000000000003" y1="514.304" y2="514.304" class="net_3"/>
<line x1="156.66000000000003" x2="156.66000000000003" y1="514.304" y2="576.804" class="net_3"/>
<line x1="156.66000000000003" x2="309.67400000000004" y1="576.804" y2="576.804" class="net_3"/>
<line x1="309.67400000000004" x2="309.67400000000004" y1="576.804" y2="679.956" class="net_3"/>
<line x1="309.67400000000004" x2="68.32999999999998" y1="679.956" y2="679.956" class="net_3"/>
<circle cx="309.67400000000004" cy="576.804" r="2" style="fill:#000" class="net_3"/>
<circle cx="156.66000000000003" cy="514.304" r="2" style="fill:#000" class="net_3"/>
<line x1="68.32999999999998" x2="68.33" y1="679.956" y2="577.456" class="net_3"/>
<line x1="366.67400000000004" x2="309.67400000000004" y1="52.5" y2="52.5" class="net_3"/>
<line x1="309.67400000000004" x2="309.67400000000004" y1="52.5" y2="421.152" class="net_3"/>
<line x1="309.67400000000004" x2="187.16700000000003" y1="421.152" y2="421.152" class="net_3"/>
<circle cx="309.67400000000004" cy="421.152" r="2" style="fill:#000" class="net_3"/>
<line x1="187.16700000000003" x2="187.16700000000003" y1="421.152" y2="431.152" class="net_3"/>
<line x1="366.67400000000004" x2="309.67400000000004" y1="52.5" y2="52.5" class="net_3"/>
<line x1="309.67400000000004" x2="309.67400000000004" y1="52.5" y2="135" class="net_3"/>
<line x1="309.67400000000004" x2="145.66000000000003" y1="135" y2="135" class="net_3"/>
<circle cx="309.67400000000004" cy="135" r="2" style="fill:#000" class="net_3"/>
<line x1="145.66000000000003" x2="145.66" y1="135" y2="586.804" class="net_3"/>
<line x1="366.67400000000004" x2="309.67400000000004" y1="52.5" y2="52.5" class="net_3"/>
<line x1="309.67400000000004" x2="309.67400000000004" y1="52.5" y2="679.956" class="net_3"/>
<line x1="309.67400000000004" x2="68.32999999999998" y1="679.956" y2="679.956" class="net_3"/>
<line x1="68.32999999999998" x2="68.33" y1="679.956" y2="577.456" class="net_3"/>
<line x1="366.67400000000004" x2="349.67400000000004" y1="32.5" y2="32.5" class="net_10"/>
<line x1="349.67400000000004" x2="349.67400000000004" y1="32.5" y2="155" class="net_10"/>
<line x1="349.67400000000004" x2="497.59200000000004" y1="155" y2="155" class="net_10"/>
<circle cx="497.59200000000004" cy="155" r="2" style="fill:#000" class="net_10"/>
<line x1="497.59200000000004" x2="497.59200000000004" y1="155" y2="165" class="net_10"/>
<line x1="397.67400000000004" x2="497.59200000000004" y1="32.5" y2="32.5" class="net_10"/>
<line x1="497.59200000000004" x2="497.59200000000004" y1="32.5" y2="165" class="net_10"/>
<line x1="366.67400000000004" x2="339.67400000000004" y1="132.5" y2="132.5" class="net_5"/>
<line x1="339.67400000000004" x2="339.67400000000004" y1="132.5" y2="165" class="net_5"/>
<line x1="339.67400000000004" x2="414.67400000000004" y1="165" y2="165" class="net_5"/>
<circle cx="414.67400000000004" cy="165" r="2" style="fill:#000" class="net_5"/>
<line x1="414.67400000000004" x2="414.67400000000004" y1="165" y2="175" class="net_5"/>
<line x1="397.67400000000004" x2="414.67400000000004" y1="52.5" y2="52.5" class="net_5"/>
<line x1="414.67400000000004" x2="414.67400000000004" y1="52.5" y2="175" class="net_5"/>
<line x1="187.16700000000003" x2="187.16700000000003" y1="504.304" y2="534.304" class="net_1"/>
<circle cx="187.16700000000003" cy="534.304" r="2" style="fill:#000" class="net_1"/>
<line x1="187.16700000000003" x2="269.67400000000004" y1="534.304" y2="534.304" class="net_1"/>
<line x1="145.66" x2="145.66000000000003" y1="659.956" y2="669.956" class="net_1"/>
<line x1="145.66000000000003" x2="187.16700000000003" y1="669.956" y2="669.956" class="net_1"/>
<line x1="187.16700000000003" x2="187.16700000000003" y1="669.956" y2="534.304" class="net_1"/>
<circle cx="187.16700000000003" cy="534.304" r="2" style="fill:#000" class="net_1"/>
<line x1="187.16700000000003" x2="269.67400000000004" y1="534.304" y2="534.304" class="net_1"/>
<line x1="497.59200000000004" x2="497.59200000000004" y1="238.152" y2="268.152" class="net_9"/>
<line x1="497.59200000000004" x2="207.16700000000003" y1="268.152" y2="268.152" class="net_9"/>
<line x1="207.16700000000003" x2="207.16700000000003" y1="268.152" y2="318.652" class="net_9"/>
<line x1="207.16700000000003" x2="240.16700000000003" y1="318.652" y2="318.652" class="net_9"/>
<line x1="414.67400000000004" x2="414.67400000000004" y1="248.152" y2="258.152" class="net_4"/>
<line x1="414.67400000000004" x2="197.16700000000003" y1="258.152" y2="258.152" class="net_4"/>
<line x1="197.16700000000003" x2="197.16700000000003" y1="258.152" y2="378.652" class="net_4"/>
<line x1="197.16700000000003" x2="240.16700000000003" y1="378.652" y2="378.652" class="net_4"/>
<line x1="240.16700000000003" x2="217.16700000000003" y1="298.652" y2="298.652" class="net_6"/>
<line x1="217.16700000000003" x2="217.16700000000003" y1="298.652" y2="494.304" class="net_6"/>
<line x1="217.16700000000003" x2="269.67400000000004" y1="494.304" y2="494.304" class="net_6"/>
<line x1="104.906" x2="114.906" y1="540.88" y2="540.8799999999999" class="net_8"/>
<line x1="114.906" x2="114.906" y1="540.8799999999999" y2="72.5" class="net_8"/>
<line x1="114.906" x2="366.67400000000004" y1="72.5" y2="72.5" class="net_8"/>
</svg>

After

Width:  |  Height:  |  Size: 33 KiB

1
hardware/eda/sim/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.csv

View file

@ -0,0 +1,47 @@
* PM_K-1 : input loading -- line receiver (25k) vs Hi-Z instrument buffer (1M)
*
* This is the circuit-level proof of a decision we made by hand: why a guitar/bass
* needs a HIGH-impedance input. A passive pickup is inductive, so the load impedance
* it sees shapes its tone. Plug it into a low impedance and you damp its resonance
* and lose treble + level; a high-Z buffer preserves it.
*
* Pickup model: open-circuit source + winding resistance + inductance + self-capacitance.
* Plus a typical ~300 pF guitar cable. We drive two identical pickup+cable networks
* from one ideal source and load them differently, then compare.
*
* Run: ngspice -b sim/input_loading.cir (from inside the EDA container)
.title PM_K-1 input loading: line 25k vs instrument 1M
Vpu src 0 AC 1
* --- Branch A: LINE input, ~25k receiver impedance ---
RdcA src a1 6k
LpuA a1 a2 3
CpuA a2 0 150p
CcabA a2 0 300p
RinA a2 0 25k
* --- Branch B: INSTRUMENT input, 1M Hi-Z buffer ---
RdcB src b1 6k
LpuB b1 b2 3
CpuB b2 0 150p
CcabB b2 0 300p
RinB b2 0 1meg
.ac dec 100 10 100k
.control
run
meas ac a_1k find vdb(a2) at=1000
meas ac b_1k find vdb(b2) at=1000
meas ac a_peak max vdb(a2) from=1000 to=20000
meas ac b_peak max vdb(b2) from=1000 to=20000
echo
echo " Level @1kHz : line(25k)= $&a_1k dB inst(1M)= $&b_1k dB"
echo " Resonant peak : line(25k)= $&a_peak dB inst(1M)= $&b_peak dB"
echo " -> the 25k load drags the signal down and flattens the pickup's natural peak;"
echo " the 1M input preserves level and tone. Hence the switchable Hi-Z front end."
wrdata ../eda/sim/input_loading.csv vdb(a2) vdb(b2)
.endc
.end

39
hardware/eda/sim/plots.gp Normal file
View file

@ -0,0 +1,39 @@
# Render the PM_K-1 ngspice simulation data to PNG plots.
# Run inside the container AFTER the sims have written their .data files:
# ./run.sh bash -lc 'cd /work/hardware/kicad; for s in ...; do ngspice -b ../eda/sim/$s.cir; done; gnuplot /work/hardware/eda/sim/plots.gp'
set terminal pngcairo size 1000,560 font "Sans,11"
set grid
set key box
P = "/work/hardware/eda/sim/plots/"
set logscale x
set xlabel "Frequency (Hz)"
set output P."input_loading.png"
set title "Input loading: line (25k) vs instrument (1M) input -- why Hi-Z preserves the pickup"
set ylabel "Level (dB)"
plot "/work/hardware/eda/sim/input_loading.csv" u 1:2 w l lw 2 t "line input (25k ohm)", \
"" u 3:4 w l lw 2 t "instrument input (1M ohm)"
set output P."stage1_cmrr.png"
set title "Stage 1 receiver: common-mode leakage vs resistor match (lower = better; perfect match ~ -inf, off scale)"
set ylabel "Output leakage (dB, Vcm = 1V)"
plot P."stage1_cmrr.data" u 1:2 w l lw 2 t "0.1% mismatch", \
"" u 3:4 w l lw 2 t "1% mismatch"
set output P."stage2_recon.png"
set title "Stage 2 DAC reconstruction filter -- flat to 20 kHz, -3 dB near 75 kHz"
set ylabel "Gain (dB)"
plot P."stage2_recon.data" u 1:2 w l lw 2 t "filter response"
set output P."stage4_driver.png"
set title "Stage 4 balanced output -- differential response (flat across the audio band)"
set ylabel "Differential gain (dB)"
plot P."stage4_driver.data" u 1:2 w l lw 2 t "hot - cold differential"
unset logscale x
set output P."stage1_phantom.png"
set title "Stage 1 protection: +48V phantom hit at the op-amp input -- clamped, then decays to ~0"
set xlabel "Time (s)"
set ylabel "Op-amp input (V)"
plot P."stage1_phantom.data" u 1:2 w l lw 2 t "V(op-amp input)"

1
hardware/eda/sim/plots/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.data

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View file

@ -0,0 +1,57 @@
* PM_K-1 Stage 1 : balanced line receiver -- why CMRR needs matched resistors
*
* A balanced receiver rejects "common-mode" noise -- hum/interference that appears
* equally on both signal wires -- by SUBTRACTING the two wires. How well it cancels
* (its Common-Mode Rejection Ratio, CMRR) depends entirely on how well its four
* resistors match. This is the circuit-level reason we chose the laser-trimmed
* THAT1240 instead of a discrete op-amp + four 1% resistors.
*
* Three identical unity-gain difference amplifiers, driven common-mode (same 1V on
* both inputs), with 0% / 0.1% / 1% mismatch on one resistor.
* CMRR(dB) = -20*log10(|Vout|) since differential gain = 1 and Vcm = 1V.
*
* Run: ngspice -b ../eda/sim/stage1_cmrr.cir
.title Stage1 line receiver CMRR vs resistor match
Vcm cm 0 AC 1 ; common-mode drive: both inputs tied to cm
* ---- A: perfectly matched ----
EA outa 0 vpa vma 1e6
R1a cm vma 10k
R2a outa vma 10k
R3a cm vpa 10k
R4a vpa 0 10k
* ---- B: 0.1% mismatch on one resistor ----
EB outb 0 vpb vmb 1e6
R1b cm vmb 10k
R2b outb vmb 10k
R3b cm vpb 10k
R4b vpb 0 10.01k
* ---- C: 1% mismatch on one resistor ----
EC outc 0 vpc vmc 1e6
R1c cm vmc 10k
R2c outc vmc 10k
R3c cm vpc 10k
R4c vpc 0 10.1k
.ac dec 10 100 10k
.control
run
meas ac acm_a find vm(outa) at=1000
meas ac acm_b find vm(outb) at=1000
meas ac acm_c find vm(outc) at=1000
let cmrr_a = -20*log10(acm_a)
let cmrr_b = -20*log10(acm_b)
let cmrr_c = -20*log10(acm_c)
echo
echo " CMRR perfect match : $&cmrr_a dB (limited only by the amplifier)"
echo " CMRR 0.1% mismatch : $&cmrr_b dB"
echo " CMRR 1% mismatch : $&cmrr_c dB"
echo " -> hand-matched 1% resistors give poor rejection; the THAT1240's"
echo " laser-trimmed internal resistors are how we get >90 dB for free."
wrdata ../eda/sim/plots/stage1_cmrr.data vdb(outb) vdb(outc)
.endc
.end

View file

@ -0,0 +1,40 @@
* PM_K-1 Stage 1 : input protection -- a +48V phantom-power step at the jack
*
* "Phantom power" is +48V DC that mixers put on their MIC inputs. If it ever reaches
* our input (miswire, wrong cable, our OUT plugged into a phantom'd input), a bare
* op-amp input would die. Our protection makes it a non-event:
* - a series DC-BLOCKING film cap passes audio (AC) but blocks the +48V (DC),
* - a series resistor + clamp diodes to the +/-15V rails survive the turn-on EDGE
* (the cap passes the fast step before it charges).
*
* We hit the input with a +48V step at t=1ms and watch the OP-AMP INPUT node.
*
* Run: ngspice -b ../eda/sim/stage1_phantom.cir
.title Stage1 phantom protection
Vin in 0 PWL(0 0 1m 0 1.001m 48 5 48) ; +48V appears at t=1ms, stays
C1 in a 1u ; DC-blocking film cap (audio passes, DC blocked)
Rs a n 1k ; series current limit
Rb n 0 1meg ; bias / input impedance (audio high-pass corner ~0.16 Hz)
.model Dclamp D(Is=1e-14 N=1 Rs=10)
Dp n vp Dclamp ; clamp to +15
Dn vn n Dclamp ; clamp to -15
Vp vp 0 15
Vn vn 0 -15
.tran 2m 5
.control
run
meas tran vn_peak max v(n) from=1m to=5
meas tran vn_final find v(n) at=4.9
echo
echo " op-amp input PEAK during the +48V step : $&vn_peak V (clamped near a +/-15 rail)"
echo " op-amp input STEADY-STATE : $&vn_final V (DC blocked by the cap)"
echo " -> +48V at the jack becomes a clamped blip that decays to ~0. Nothing is harmed;"
echo " a wrong patch only ever sounds wrong. (Audio passes -- the high-pass is ~0.16 Hz.)"
wrdata ../eda/sim/plots/stage1_phantom.data v(n)
.endc
.end

View file

@ -0,0 +1,28 @@
* PM_K-1 Stage 1b : Hi-Z instrument DI buffer -- gain + flatness check
*
* An OPA1641 (JFET input, ~1e12 ohm) as a non-inverting amp. The INPUT impedance
* the instrument sees is set by the bias resistor (1 Mohm) -- which the earlier
* input_loading.cir proved is what preserves a pickup's tone. Here we just confirm
* the voltage gain: Av = 1 + Rf/Rg. Target +12 dB (x4) with Rf=3k, Rg=1k.
*
* Run: ngspice -b ../eda/sim/stage1b_di.cir
.title Stage1b DI buffer gain
Vin in 0 AC 1
Eop out 0 in vm 1e6 ; ideal op-amp: +IN = in, -IN = vm
Rf out vm 3k ; feedback
Rg vm 0 1k ; gain set
.ac dec 20 10 100k
.control
run
meas ac g_1k find vdb(out) at=1000
meas ac g_20k find vdb(out) at=20000
let av = pow(10, g_1k/20)
echo
echo " DI buffer gain @1kHz : $&g_1k dB ( x$&av ) target +12.04 dB (x4)"
echo " DI buffer gain @20kHz : $&g_20k dB (flat across audio band)"
echo " Input impedance is set by the 1Mohm bias R (JFET input ~1e12 ohm) -- see input_loading.cir"
.endc
.end

View file

@ -0,0 +1,36 @@
* PM_K-1 Stage 2 : DAC reconstruction filter (2nd-order Sallen-Key low-pass)
*
* The PCM5102A is a delta-sigma DAC: its analog output carries the audio plus
* shaped high-frequency quantization noise WAY above the audio band. A gentle
* low-pass cleans that residue before it reaches the pro output stage. We want it
* dead flat to 20 kHz and rolling off above ~80 kHz (Butterworth, Q~0.7).
*
* Unity-gain Sallen-Key, equal R. Target fc ~75 kHz with R=1.5k, C1=2.2n, C2=1n.
* Run: ngspice -b ../eda/sim/stage2_recon.cir
.title Stage2 DAC reconstruction filter
.param R=1.5k
.param Ca=2.2n
.param Cb=1n
Vin in 0 AC 1
R1 in a {R}
R2 a vp {R}
Ca a out {Ca} ; feedback cap
Cb vp 0 {Cb} ; cap to ground
Eop out 0 vp out 1e6 ; unity-gain follower op-amp (-in tied to out)
.ac dec 100 100 2meg
.control
run
meas ac g_1k find vdb(out) at=1k
meas ac g_20k find vdb(out) at=20k
meas ac f_3db when vdb(out)=-3
echo
echo " passband @1kHz : $&g_1k dB"
echo " @20kHz (audio edge) : $&g_20k dB (want ~0 dB = flat)"
echo " -3dB corner : $&f_3db Hz (well above audio; attenuates DAC HF residue)"
wrdata ../eda/sim/plots/stage2_recon.data vdb(out)
.endc
.end

View file

@ -0,0 +1,40 @@
* PM_K-1 Stage 3 : summing node (selected input + click)
*
* An inverting summing amp mixes STAGE1_OUT (line/instrument) and CLICK_OUT (DAC).
* Each source feeds the op-amp's inverting input through its own 10k resistor; the
* feedback holds that node at a "virtual ground" (~0V). Because BOTH sources see 0V
* there, neither can load or pull on the other -- they sum with no interaction.
* Vout = -(Rf/Ri1)*V1 - (Rf/Ri2)*V2 ; with Rf=Ri=10k -> Vout = -(V1+V2).
*
* We confirm: each input alone = 0 dB (gain -1), both together = +6 dB (they add),
* and each input's gain is unchanged by the other (isolation).
* Run: ngspice -b ../eda/sim/stage3_sum.cir
.title Stage3 inverting summer
Vinp inp 0 AC 1 ; selected input (STAGE1_OUT)
Vclk clk 0 AC 1 ; filtered click (CLICK_OUT)
Ri1 inp vm 10k
Ri2 clk vm 10k
Rf vm out 10k
Eop out 0 0 vm 1e6 ; +in = gnd, -in = vm -> inverting; feedback makes vm a virtual ground
.ac dec 10 100 20k
.control
* both sources active -> they sum
run
meas ac g_both find vdb(out) at=1k
echo " both = $&g_both dB (+6 dB over each-alone = the two sum)"
* input alone (click muted)
alter @Vclk[acmag]=0
run
meas ac g_in find vdb(out) at=1k
echo " input alone = $&g_in dB (gain -1, i.e. 0 dB)"
* click alone (input muted)
alter @Vinp[acmag]=0
alter @Vclk[acmag]=1
run
meas ac g_clk find vdb(out) at=1k
echo " click alone = $&g_clk dB (gain -1; unchanged by the input = virtual-ground isolation)"
.endc
.end

View file

@ -0,0 +1,39 @@
* PM_K-1 Stage 4 : balanced output driver -- antiphase + build-out into a cable
*
* The THAT1646 turns the single-ended mix into a balanced (differential) output:
* Out+ and Out- swing equal-and-opposite, so noise picked up equally on both wires
* cancels at the far end. It has a fixed +6 dB gain (differential = 2x input).
* We model it as two ideal sources (Out+ = +Vin, Out- = -Vin) and add the 47 ohm
* per-leg build-out resistors driving a 600 ohm load plus cable capacitance.
*
* Shows: audio band is dead flat, and the build-out + cable rolloff sits far above
* audio -- i.e. the build-out tames long/capacitive cables without dulling the sound.
* Run: ngspice -b ../eda/sim/stage4_driver.cir
.title Stage4 balanced driver
Vin in 0 AC 1
Eop op 0 in 0 1 ; Out+ = +Vin
Eon on 0 0 in 1 ; Out- = -Vin -> differential (op-on) = 2*Vin = +6 dB
Rbp op hot 47 ; build-out, hot leg
Rbn on cold 47 ; build-out, cold leg
Rl hot cold 600 ; receiver / load
Cch hot 0 1n ; ~cable capacitance per leg
Ccc cold 0 1n
.ac dec 20 10 10meg
.control
run
let vddb = db(v(hot) - v(cold)) ; differential magnitude in dB
meas ac d_1k find vddb at=1k
meas ac d_20k find vddb at=20k
meas ac d_1meg find vddb at=1meg
meas ac ph_hot find vp(hot) at=1k
meas ac ph_cold find vp(cold) at=1k
echo
echo " differential @1kHz : $&d_1k dB @20kHz : $&d_20k dB (flat across audio)"
echo " hot phase: $&ph_hot rad ; cold phase: $&ph_cold rad (~pi rad = 180 deg apart = balanced/antiphase)"
echo " differential @1MHz : $&d_1meg dB (build-out+cable rolloff stays above audio)"
wrdata ../eda/sim/plots/stage4_driver.data vddb
.endc
.end

12
hardware/kicad/.gitignore vendored Normal file
View file

@ -0,0 +1,12 @@
# KiCad + SKiDL generated/derived outputs (keep source: *.kicad_sch/_pro/_pcb)
*.net
*.pdf
*.svg
*.rpt
*.erc
*.log
*_sklib.py
*-bak
*.kicad_prl
fp-info-cache
~*.lck

View file

@ -0,0 +1,4 @@
(fp_lib_table
(version 7)
(lib (name "pm_k1")(type "KiCad")(uri "${KIPRJMOD}/pm_k1.pretty")(options "")(descr "PM_K-1 custom footprints: TQ2SA relay + RV-8803-C7 RTC"))
)

View file

@ -0,0 +1,145 @@
(footprint "RV-8803-C7"
(version 20241229)
(generator "pcbnew")
(generator_version "9.0")
(layer "F.Cu")
(descr "Micro Crystal RV-8803-C7 I2C RTC, SON-8 ceramic 3.2x1.5x0.8mm. Land pattern from the datasheet (doc RV-8803-C7, Mouser 2308195) Recommended Solder Pad: pad 0.4 x 0.8 mm, pitch 0.9 mm (X), two rows 0.5mm inner gap -> 1.3mm center-to-center (rows +/-0.65mm). Top-view pin map (pin-1 index bottom-left): bottom L->R = 1,2,3,4 ; top L->R = 8,7,6,5. 1=SDA 2=CLKOUT 3=VDD 4=CLKOE 5=VSS 6=/INT 7=EVI 8=SCL. VERIFY the 0.4/0.8/0.9/0.5 numbers against the drawing.")
(tags "RTC I2C MicroCrystal RV-8803 SON-8")
(property "Reference" "REF**"
(at 0 -1.6 0)
(layer "F.SilkS")
(uuid "db94bdc4-f491-4d1a-a0f6-2af3485c7953")
(effects
(font
(size 0.8 0.8)
(thickness 0.12)
)
)
)
(property "Value" "RV-8803-C7"
(at 0 1.6 0)
(layer "F.Fab")
(uuid "8b098e33-fbac-4a8d-9196-9768322b4f49")
(effects
(font
(size 0.8 0.8)
(thickness 0.12)
)
)
)
(property "Datasheet" "https://www.microcrystal.com/fileadmin/Media/Products/RTC/Datasheet/RV-8803-C7.pdf"
(at 0 0 0)
(layer "F.Fab")
(hide yes)
(uuid "70208115-007d-4854-82ab-38a363d72138")
(effects
(font
(size 0.5 0.5)
(thickness 0.1)
)
)
)
(property "Description" "Micro Crystal RV-8803-C7 I2C RTC, SON-8"
(at 0 0 0)
(layer "F.Fab")
(hide yes)
(uuid "d186185c-5fb4-4f14-84da-c51bb56fb4be")
(effects
(font
(size 0.5 0.5)
(thickness 0.1)
)
)
)
(attr smd)
(fp_circle
(center -1.85 1)
(end -1.75 1)
(stroke
(width 0.15)
(type default)
)
(fill yes)
(layer "F.SilkS")
(uuid "72c1c634-b952-418f-af45-674e2220124d")
)
(fp_rect
(start -1.9 -1.15)
(end 1.9 1.15)
(stroke
(width 0.05)
(type default)
)
(fill no)
(layer "F.CrtYd")
(uuid "62649bd7-29b7-4b2d-9b36-cc43829314af")
)
(fp_rect
(start -1.6 -0.75)
(end 1.6 0.75)
(stroke
(width 0.1)
(type default)
)
(fill no)
(layer "F.Fab")
(uuid "cacaa345-30e2-43b2-854f-ae63f1669346")
)
(pad "1" smd roundrect
(at -1.35 0.65)
(size 0.4 0.8)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(uuid "dcf9769c-71d8-41d3-81cd-dc7b0a77da0d")
)
(pad "2" smd roundrect
(at -0.45 0.65)
(size 0.4 0.8)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(uuid "bdb930da-5abe-44cc-9a15-21573286262c")
)
(pad "3" smd roundrect
(at 0.45 0.65)
(size 0.4 0.8)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(uuid "ed65728b-1ce6-4553-b02b-60d6e2012701")
)
(pad "4" smd roundrect
(at 1.35 0.65)
(size 0.4 0.8)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(uuid "7ddbefdc-3020-4f5b-8d4e-7f35628b4b77")
)
(pad "5" smd roundrect
(at 1.35 -0.65)
(size 0.4 0.8)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(uuid "fc535879-d6bb-4506-927a-86017e48e026")
)
(pad "6" smd roundrect
(at 0.45 -0.65)
(size 0.4 0.8)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(uuid "a9cea043-d14e-48e5-b2ff-eb0152772c9d")
)
(pad "7" smd roundrect
(at -0.45 -0.65)
(size 0.4 0.8)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(uuid "8e50f635-2d1e-44a1-874a-f7b26f11fe9e")
)
(pad "8" smd roundrect
(at -1.35 -0.65)
(size 0.4 0.8)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(uuid "9142efa1-e0f4-403e-abcc-ce468d17eeab")
)
(embedded_fonts no)
)

View file

@ -0,0 +1,179 @@
(footprint "Relay_DPDT_Panasonic_TQ2-SA"
(version 20241229)
(generator "pcbnew")
(generator_version "9.0")
(layer "F.Cu")
(descr "Panasonic TQ2-SA SMD DPDT (2 Form C) signal relay, 10 SMD terminals. Land pattern derived from the Panasonic TQ-SMD datasheet 'Recommendable mounting pad (Top view), SA type': 2.54mm row pitch, pad 1.0 x 2.94 mm, two rows at +/-3.31mm (9.56mm outer span), ~14mm overall. Pin map: coil 1/10; pole1 COM=3 NC=4 NO=2; pole2 COM=8 NC=7 NO=9. VERIFY against the datasheet before fab.")
(tags "relay Panasonic TQ2 TQ2SA DPDT 2FormC SMD")
(property "Reference" "REF**"
(at 0 -6 0)
(layer "F.SilkS")
(uuid "20e13ea2-2257-4b4a-a53e-9d3c94dd6bb3")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Value" "TQ2SA-5V"
(at 0 6 0)
(layer "F.Fab")
(uuid "58c051b0-2d2f-4a32-a1f7-505512d81218")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Datasheet" "https://industrial.panasonic.com/ww/products/pt/tq"
(at 0 0 0)
(layer "F.Fab")
(hide yes)
(uuid "5c67e25e-81d3-445b-97d2-43119d6e39dc")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Description" "Panasonic TQ2-SA DPDT signal relay"
(at 0 0 0)
(layer "F.Fab")
(hide yes)
(uuid "20a952d1-ff72-4879-bd1e-80e5859d208e")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(attr smd)
(fp_line
(start -7 -4.5)
(end 7 -4.5)
(stroke
(width 0.12)
(type default)
)
(layer "F.SilkS")
(uuid "d886079d-cdaf-475c-8aa7-32035c18a226")
)
(fp_line
(start -7 4.5)
(end 7 4.5)
(stroke
(width 0.12)
(type default)
)
(layer "F.SilkS")
(uuid "2647f0ce-0b5c-4f5e-b966-d3f873b32ad4")
)
(fp_circle
(center -6.5 -4)
(end -6.3 -4)
(stroke
(width 0.2)
(type default)
)
(fill yes)
(layer "F.SilkS")
(uuid "dec3a367-3d98-4060-be90-e5a7bf5854c9")
)
(fp_rect
(start -7.7 -5.4)
(end 7.7 5.4)
(stroke
(width 0.05)
(type default)
)
(fill no)
(layer "F.CrtYd")
(uuid "d6a2930d-0aca-41af-87a6-a0e76f97e85a")
)
(fp_rect
(start -7 -4.5)
(end 7 4.5)
(stroke
(width 0.1)
(type default)
)
(fill no)
(layer "F.Fab")
(uuid "82dbbeab-fe2f-4ee0-be6c-14536513b34d")
)
(pad "1" smd roundrect
(at -5.08 -3.31)
(size 1 2.94)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(uuid "8b24d11a-e344-417e-a739-9aa09d9d668f")
)
(pad "2" smd roundrect
(at -2.54 -3.31)
(size 1 2.94)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(uuid "2e7e36aa-115d-4b44-bcd0-a8aa0e78ff00")
)
(pad "3" smd roundrect
(at 0 -3.31)
(size 1 2.94)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(uuid "2bca0a65-ef27-4bd0-872a-0ffaa70634d9")
)
(pad "4" smd roundrect
(at 2.54 -3.31)
(size 1 2.94)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(uuid "c6bde6c3-ed9a-4374-bc5d-007be9adb4d5")
)
(pad "5" smd roundrect
(at 5.08 -3.31)
(size 1 2.94)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(uuid "9e38c1e9-6197-4cc6-b7a2-8b4c5261fead")
)
(pad "6" smd roundrect
(at 5.08 3.31)
(size 1 2.94)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(uuid "af54b502-62f4-4ad4-a150-c038f527dcb7")
)
(pad "7" smd roundrect
(at 2.54 3.31)
(size 1 2.94)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(uuid "3f57f831-9d5c-4052-a9af-53c2fdee994f")
)
(pad "8" smd roundrect
(at 0 3.31)
(size 1 2.94)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(uuid "b3a87d9e-357b-4685-a78b-8b23e0bb20ad")
)
(pad "9" smd roundrect
(at -2.54 3.31)
(size 1 2.94)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(uuid "5147e9ac-2ddd-4ed1-9ba7-b0da686c5cc3")
)
(pad "10" smd roundrect
(at -5.08 3.31)
(size 1 2.94)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(uuid "1d45a9c4-d273-4376-8f72-d26b07458648")
)
(embedded_fonts no)
)

View file

@ -0,0 +1,92 @@
{
"board": {
"design_settings": {
"defaults": {},
"rules": {},
"track_widths": [],
"via_dimensions": []
},
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"rule_severities": {},
"rule_severitiesV2": {}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "pm_k1_core.kicad_pro",
"version": 1
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.25,
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 6
}
],
"meta": {
"version": 3
}
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"specctra_dsn": "",
"step": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"drawing": {},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"page_layout_descr_file": "",
"plot_directory": "",
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [
[
"743b8308-6a69-48de-a6ff-2336bc6c804f",
""
]
],
"text_variables": {}
}

View file

@ -0,0 +1,56 @@
(kicad_sch (version 20230121) (generator eeschema)
(uuid 743b8308-6a69-48de-a6ff-2336bc6c804f)
(paper "A3")
(title_block
(title "PM_K-1 Core Board (brain) - VARASYS PolyMeter")
(date "2026-05-30")
(rev "A")
(company "VARASYS")
(comment 1 "Heirloom pro-audio modular core: RP2350 + +/-15V studio audio + balanced click-injector")
(comment 2 "Design-of-record canvas - see hardware/DESIGN.md. Symbol placement + wiring = interactive TODO in Eeschema.")
)
(lib_symbols)
(text "PM_K-1 CORE BOARD (\"brain\")\nHeirloom pro-audio modular core - design-of-record canvas.\nFull spec, BOM and interconnect pinouts: hardware/DESIGN.md + hardware/BOM.csv."
(at 20 18 0)
(effects (font (size 2.2 2.2)) (justify left top))
(uuid 00109097-7760-4eb7-b299-bd3f64383aab)
)
(text "POWER TREE\nUSB-C 5V -> AP2112K 3V3 (digital IO)\nRP2350 internal SMPS -> 1.1V core (ext L)\nTPS65131 dual boost/inverter -> raw +/-18V\nTPS7A4901 / TPS7A3001 LDO -> CLEAN +/-15V (audio)\nStar ground; switcher in a guarded corner."
(at 20 45 0)
(effects (font (size 1.6 1.6)) (justify left top))
(uuid 4e184999-041e-4c11-9e86-faad487f2bad)
)
(text "MCU + DIGITAL\nRP2350A (QFN-60) - mind E9 erratum: external pulldowns on read inputs.\nW25Q128JV 16MB QSPI flash (wear-leveled).\n12MHz crystal. SWD 2x5 + labeled test points.\nI2S BCK/LRCK/DOUT + low-jitter 24.576MHz MCLK stay ON-CORE."
(at 110 45 0)
(effects (font (size 1.6 1.6)) (justify left top))
(uuid 43233868-23ee-43fa-b873-a47bffeacf5f)
)
(text "CLICK SOURCE\nPCM5102A I2S DAC (Burr-Brown).\nFed by dedicated low-jitter audio XO (ASEM1-24.576MHz),\nnot PIO-jittered MCLK."
(at 200 45 0)
(effects (font (size 1.6 1.6)) (justify left top))
(uuid a62ad51b-a1a9-4c34-b3d7-314c107afc7a)
)
(text "ANALOG INPUT (switchable line / instrument)\nProtection (non-negotiable): series DC-block film cap (blocks +48V phantom),\nclamp diodes/TVS to rails, series current-limit R.\nLINE: THAT1240 balanced receiver (laser-trimmed CMRR).\nINST: OPA1641 JFET Hi-Z buffer (>=1Mohm) + ~+10..15dB gain.\nK1 gold relay selects path (GPIO; touchscreen or optional face switch)."
(at 20 80 0)
(effects (font (size 1.6 1.6)) (justify left top))
(uuid 737ed14f-66db-4799-a337-21438143ce09)
)
(text "MIX + OUTPUT\nMix = digital/firmware (click level via DAC); analog stage at unity.\nTHAT1646 balanced driver + 47ohm build-out per leg.\nOutput level: Bourns 3296W 25-turn trim -> DAC FS = +4dBu (~+24dBu headroom).\nMUTE relay K2: fail-safe (de-energized = muted); HW supervisor, not MCU-dependent.\nGROUND-LIFT: face panel switch IN SERIES with core relay K3; soft-lift 100ohm||10nF.\nNO electrolytics in signal path - WIMA film caps, 0.1% thin-film R."
(at 110 80 0)
(effects (font (size 1.6 1.6)) (justify left top))
(uuid 99fe482f-1fde-4dd1-9579-56113dcb21bc)
)
(text "INDICATORS / MIDI / SPEAKER\nSIG/CLIP: peak detect -> LM393 -> RP2350 GPIO (UI) + LED lines on interconnect.\nMIDI: USB-MIDI default (firmware). DNP hardware option: H11L1 opto IN + 74LVC14 OUT/THRU.\nSpeaker: PAM8302 class-D, DNP per form factor.\nESD/EMI: USBLC6-2 on USB + CM choke; series R + ESD arrays on interconnect; ferrites at analog crossing."
(at 20 120 0)
(effects (font (size 1.6 1.6)) (justify left top))
(uuid 03eeefa8-7728-4e14-90c0-6d0496a0d207)
)
(text "INTERCONNECTS (see DESIGN.md s7)\nJ2 DIGITAL RIBBON 2x13 - Pico-pinout-compatible (SPI/I2C/ADC/buttons/LED + GNDLIFT_SW/LINEINST_SW/SIG_LED/CLIP_LED).\nJ3 ANALOG 2x5 - AOUT H/C, AIN H/C, AGND, CHASSIS/SHIELD, SPK+/- (DNP). Kept away from the fast digital ribbon.\nJ4 MIDI 1x6 - OUT A/B, IN A/B, +5V, GND (only if DNP MIDI populated).\nRelays + I2S + MCLK are core-only and NOT on the ribbon; a Pico test brain drives digital I/O but not the analog chain."
(at 110 120 0)
(effects (font (size 1.5 1.5)) (justify left top))
(uuid 0b1d2c3e-4f5a-6b7c-8d9e-0a1b2c3d4e5f)
)
(sheet_instances
(path "/" (page "1"))
)
)

View file

@ -0,0 +1,109 @@
# PCB Layout — what software, and how it actually works
A plain-English orientation for turning our finished schematic into a manufacturable board.
You don't need prior PCB experience to follow this. (Reminder: this is **board design**
arranging finished chips and copper wires — *not* "chip design," which is the silicon inside
the chips. Totally different, far more approachable.)
## The software: KiCad
**Use KiCad** (free, open-source, professional-grade). It's what we've been using to verify
the design, and it's already installed in the project container (`hardware/eda/run.sh`).
There are paid alternatives (Altium ~$$$, Autodesk Fusion/EAGLE), but KiCad is free, capable,
and has the best free learning material — no reason to use anything else here.
KiCad is actually several tools in one:
- **Eeschema** — draws the *schematic* (what connects to what). *We did this in code (SKiDL),
which generated the same thing.*
- **Pcbnew** — the *PCB layout* editor. **This is the part you'll learn next.**
- helpers for 3D view, Gerber export, etc.
## The core paradigm (read this part twice)
A circuit board is described at **two levels**, and keeping them separate is the whole idea:
1. **The schematic / netlist = the *contract*.** It says *what must be electrically
connected* — "pin 6 of the DAC connects to the resistor R12, which connects to the op-amp."
It says nothing about physical position. **Ours is done:** `hardware/kicad/board.net`.
2. **The PCB layout = the *physical realization*.** You decide *where* each component sits and
*how* the copper wires (called **traces**) run to satisfy every connection the netlist
demands — while obeying physical rules (wires can't be too thin, too close, etc.).
So the mental model is: **constraint-satisfying connect-the-dots.** The netlist hands you a
list of dots that *must* be joined; you place the parts on a rectangle and draw copper to join
exactly those dots, in the cleanest way, without breaking the rules. Nothing more, nothing less.
Why two levels? Because *what* connects (the design) and *where things go* (the craft) are
genuinely different problems. We nailed the first — rigorously, with simulations. The second
is a spatial puzzle.
## The workflow, step by step
1. **Import the netlist.** Open Pcbnew, import `board.net`. All 167 components appear as
**footprints** (the real-world copper pads + outline for each part), piled up, joined by a
tangle of thin lines called the **ratsnest** — each line is one connection the netlist
requires but that isn't routed yet.
2. **Place the footprints.** Drag parts into a sensible arrangement. *This is where our
`LAYOUT.md` rules apply* — switcher in one corner, quiet analog in another, USB by its
connector, etc. Good placement makes routing easy; bad placement makes it impossible.
3. **Route the traces.** Draw copper to replace each ratsnest line. You work on **layers**
(top copper, bottom copper, and inner layers on a 4-layer board); you hop between layers
through plated holes called **vias**. As you connect dots, the ratsnest lines disappear.
4. **Pour the planes.** Fill empty areas with copper connected to **GND** (and power) — the
"ground plane" that keeps everything quiet. (Big topic in `LAYOUT.md`.)
5. **Run DRC** (**Design Rule Check**) — the layout equivalent of the ERC we ran on the
schematic. It flags traces too close together, too thin, unrouted connections, etc. Fix
until clean.
6. **Export and order.** Generate **Gerber** files (the standard format every fab understands —
one file per layer), plus a drill file, a pick-and-place file, and the BOM
(`BOM_board.csv`). Upload to a fab like **JLCPCB** or **PCBWay**; they make and (optionally)
assemble it.
## The vocabulary you'll meet
| Term | Plain meaning |
|---|---|
| **Footprint** | the copper pads + outline a real part solders onto |
| **Net** | one electrical connection (a wire), e.g. `+3V3`, `MIX_OUT` |
| **Ratsnest** | the thin "still-to-route" lines showing required connections |
| **Trace** | a copper wire you draw |
| **Via** | a plated hole that carries a trace between layers |
| **Plane / pour** | a large copper fill, usually ground or power |
| **Layer** | one copper sheet; our board uses 4, stacked |
| **Clearance** | the minimum gap rules between copper |
| **DRC** | Design Rule Check — automated "is this manufacturable?" |
| **Gerber** | the file format you send to the factory |
## How our files fit together
- **`board.net`** → the contract you import into Pcbnew (the dots to connect).
- **`LAYOUT.md`** → the rulebook for *how* to place and route this specific board (grounding,
the switcher, USB pair, analog isolation).
- **`BOM_board.csv`** → the parts list for ordering/assembly.
- **`DESIGN.md`** → why the circuit is the way it is, if you want the background.
## Learning resources (do these in order)
1. **Digi-Key "Intro to KiCad" by Shawn Hymel** (YouTube) — start-to-finish, free; the layout
parts are exactly steps 16 above.
2. **Phil's Lab** (YouTube) — especially relevant: he does **audio and mixed-signal** PCBs,
grounding, and USB — our exact problem domain.
3. KiCad's official "Getting Started" docs.
## Honest expectations
The *digital* parts of this board (RP2350, flash, the ribbons) are beginner-friendly to route.
The **switching supply, the ±15 V analog section, and the USB pair are not** — they reward
experience, and mistakes there show up as noise or instability. Three realistic paths:
- **Learn on it:** route the easy parts yourself, and follow `LAYOUT.md` carefully (with me) for
the tricky three. Best for understanding your own device.
- **Hybrid:** you place + route the digital/connectors; hand the switcher/analog/USB to a
freelance layout person (or me, iterating).
- **Hire the layout:** `board.net` + `BOM_board.csv` + `LAYOUT.md` is a complete brief a
contract PCB designer can execute (~$3001500 for a board this size).
The hard intellectual work — the design — is already done and verified. Layout is craft and
care, and it's learnable.

View file

@ -2,68 +2,253 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>VARASYS PolyMeter</title>
<meta name="theme-color" content="#eef3f9" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#05070a" media="(prefers-color-scheme: dark)" />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VARASYS PolyMeter — Concepts (polymetric groove trainer &amp; metronome)</title>
<meta name="description" content="PolyMeter — a polymetric groove trainer and metronome. Design grooves in the editor and play them on any form factor; one engine, one program string." />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
(function(){ try{
var p = localStorage.getItem("metronome.theme");
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#e7edf5; --muted:#8b96a5; --link:#6cb6ff;
--card:#161b22; --card-bd:#2a313c; --cyan:#0AB3F7; }
:root[data-theme="light"]{ --bg1:#eef3f9; --bg2:#cfd9e6; --txt:#10202f; --muted:#5c6776; --link:#1769c4;
--card:#ffffff; --card-bd:#d2dae4; --cyan:#0AB3F7; }
html,body{ height:100%; }
body{ margin:0; color:var(--txt);
background:radial-gradient(circle at 50% -10%, var(--bg1), var(--bg2));
font-family:"Segoe UI", Roboto, Helvetica, Arial, sans-serif; -webkit-text-size-adjust:100%; }
[data-theme="light"] .logo-dark{ display:none; } [data-theme="dark"] .logo-light{ display:none; }
main{ min-height:100%; box-sizing:border-box; display:flex; flex-direction:column; align-items:center; justify-content:center;
gap:clamp(20px,4vmin,40px); padding:max(24px,env(safe-area-inset-top)) 22px max(24px,env(safe-area-inset-bottom)); text-align:center; }
.logo{ height:clamp(34px,7vmin,56px); width:auto; }
.tagline{ color:var(--muted); font-size:clamp(13px,2.4vmin,17px); letter-spacing:.01em; margin:-6px 0 0; max-width:30ch; line-height:1.4; }
.choices{ display:flex; flex-wrap:wrap; gap:clamp(16px,3vmin,28px); justify-content:center; width:100%; max-width:620px; }
.choice{ flex:1 1 240px; min-width:0; max-width:300px; text-decoration:none; color:var(--txt);
background:linear-gradient(180deg, rgba(127,139,154,.07), transparent), var(--card);
border:1px solid var(--card-bd); border-radius:18px; padding:clamp(22px,4vmin,34px) 20px;
display:flex; flex-direction:column; align-items:center; gap:10px;
box-shadow:0 6px 22px rgba(0,0,0,.18); transition:transform .12s ease, border-color .12s ease, box-shadow .12s ease; }
.choice:hover, .choice:focus-visible{ border-color:var(--cyan); transform:translateY(-3px); box-shadow:0 12px 30px rgba(10,179,247,.18); outline:none; }
.choice:active{ transform:translateY(0); }
.choice .ic{ font-size:clamp(34px,7vmin,52px); line-height:1; }
.choice .lbl{ font-size:clamp(19px,3.4vmin,26px); font-weight:700; letter-spacing:.01em; }
.choice .sub{ font-size:clamp(11px,2vmin,13px); color:var(--muted); line-height:1.4; }
footer{ color:var(--muted); font-size:12px; line-height:1.7; }
footer a{ color:var(--link); text-decoration:none; }
footer a:hover{ text-decoration:underline; }
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
--panel-bg:#161b22; --panel-bd:#2a313c; --field-bg:#0e1116; --field-bd:#2a313c; }
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
--panel-bg:#ffffff; --panel-bd:#d2dae4; --field-bg:#f1f4f8; --field-bd:#d2dae4; }
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt);
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); }
a{ color:var(--link); }
main{ width:100%; max-width:1040px; margin:0 auto; }
.intro{ text-align:center; padding:34px 12px 18px; }
.intro h1{ font-size:clamp(32px, 6.5vw, 54px); margin:0; letter-spacing:-.02em; line-height:1;
background:linear-gradient(90deg, var(--cyan), #6cb6ff); -webkit-background-clip:text; background-clip:text; color:transparent; }
.intro .tagline{ margin:13px auto 0; font-size:clamp(15px,2.4vw,19px); color:var(--txt); font-weight:600; }
.intro p{ margin:11px auto 0; max-width:66ch; color:var(--muted); font-size:14.5px; line-height:1.6; }
.section-label{ text-align:center; font-size:11px; text-transform:uppercase; letter-spacing:.12em; color:var(--muted); margin:26px 0 12px; }
/* summary panes — click to load that version into the viewport */
.panes{ display:grid; grid-template-columns:repeat(auto-fit, minmax(225px, 1fr)); gap:12px; }
.pane{ text-align:left; background:var(--panel-bg); border:1px solid var(--panel-bd); border-radius:12px; padding:12px 13px;
cursor:pointer; display:flex; flex-direction:column; gap:6px; transition:border-color .14s, box-shadow .14s; }
.pane:hover{ border-color:var(--cyan); }
.pane.active{ border-color:var(--cyan); box-shadow:0 0 0 1px var(--cyan) inset; }
.pane .ph{ display:flex; align-items:center; gap:8px; }
.pane h3{ margin:0; font-size:14px; }
.pane .chip{ font-size:9px; text-transform:uppercase; letter-spacing:.07em; padding:2px 7px; border-radius:999px;
border:1px solid var(--panel-bd); color:var(--muted); }
.pane .chip.hw{ color:var(--cyan); border-color:rgba(10,179,247,.45); }
.pane .chip.app{ color:#2fe07a; border-color:rgba(47,224,160,.45); }
.pane p{ margin:0; font-size:12px; color:var(--muted); line-height:1.45; flex:1; }
.pane .links{ display:flex; gap:14px; align-items:center; flex-wrap:wrap; }
.pane .open{ font-size:11px; color:var(--link); text-decoration:none; font-weight:600; }
/* viewport: the live, selected device */
.viewport{ margin-top:16px; border:1px solid var(--panel-bd); border-radius:14px; overflow:hidden; background:var(--field-bg); }
.vp-bar{ display:flex; align-items:center; justify-content:space-between; gap:10px; padding:8px 12px; border-bottom:1px solid var(--panel-bd); font-size:12px; color:var(--muted); }
.vp-bar b{ color:var(--txt); }
.vp-bar a{ font-size:12px; }
#vp{ display:block; width:100%; height:620px; border:0; background:var(--field-bg); transition:height .15s; }
/* program I/O — decoded program string in/out (plain text or base64), linted */
.prog{ display:flex; align-items:center; gap:9px; flex-wrap:wrap; margin-top:12px; padding:9px 12px;
border:1px solid var(--panel-bd); border-radius:11px; background:var(--panel-bg); }
.prog > label{ flex:0 0 auto; font-size:10px; text-transform:uppercase; letter-spacing:.09em; color:var(--muted); }
.prog input{ flex:1; min-width:180px; background:var(--field-bg); color:var(--txt); border:1px solid var(--field-bd);
border-radius:8px; padding:8px 10px; font-family:"Courier New",monospace; font-size:12.5px; }
.prog input.err{ border-color:#c0392b; }
.prog button{ flex:0 0 auto; background:var(--field-bg); color:var(--txt); border:1px solid var(--field-bd); border-radius:8px;
padding:8px 13px; font-size:13px; cursor:pointer; }
.prog button.primary{ background:linear-gradient(180deg,#34c6ff,var(--cyan)); color:#04121b; border-color:transparent; font-weight:600; }
.prog button:hover{ border-color:var(--cyan); }
.prog-msg{ flex:1 1 100%; font-size:11.5px; color:var(--muted); min-height:1.1em; }
.prog-msg.ok{ color:#5fd08a; } .prog-msg.bad{ color:#ff8a7a; }
.prog-hint{ flex:1 1 100%; font-size:11px; color:var(--muted); }
.philosophy{ margin-top:34px; }
.phil-grid{ display:grid; grid-template-columns:repeat(auto-fit, minmax(300px, 1fr)); gap:16px; }
.phil{ background:var(--panel-bg); border:1px solid var(--panel-bd); border-radius:14px; padding:18px 18px 16px; }
.phil h3{ margin:0 0 8px; font-size:15px; display:flex; align-items:center; gap:8px; }
.phil p{ margin:0; font-size:13.5px; color:var(--muted); line-height:1.62; }
.phil p b{ color:var(--txt); }
</style>
</head>
<body>
/*@BUILD:include:src/header.html@*/
<main>
<img class="logo logo-dark" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS PolyMeter" />
<img class="logo logo-light" src="data:image/png;base64,@BUILD:logo-light@" alt="VARASYS PolyMeter" />
<p class="tagline">A polymetric groove trainer &amp; metronome. Pick how you want to play.</p>
<div class="choices">
<a class="choice" href="/mobile.html">
<span class="ic" aria-hidden="true">📱</span>
<span class="lbl">Mobile</span>
<span class="sub">Touch-first phone &amp; tablet app — tap a beat, set the tempo, practice. Installable, works offline.</span>
</a>
<a class="choice" href="/pm_e-2.html">
<span class="ic" aria-hidden="true">🎼</span>
<span class="lbl">Desktop</span>
<span class="sub">Engraved-notation editor — build rhythms on a staff with full keyboard control. Best on a big screen.</span>
</a>
<section class="intro">
<h1>PolyMeter</h1>
<p class="tagline">Polymetric grooves — one engine, one program string, every form factor.</p>
<p>Stack independent meter lanes — each with its own subdivision, drum voice and perstep accents — to build
true polymeter and ratio polyrhythm. Design a groove once; it saves to a compact <b>program string</b> that
plays back identically on the web editor, the hardware concepts, or an embedded widget. The editor is open
below — or pick any form factor to load and play the same groove on it.</p>
</section>
<section class="philosophy">
<div class="section-label">Philosophy</div>
<div class="phil-grid">
<div class="phil">
<h3>🛠️ Program on the web, play on any device</h3>
<p>The website is the workbench: design in the <a href="/pm_e-2.html">editor</a>, and the same
<b>program string</b> loads into whichever form factor fits the moment. One engine, one language.</p>
</div>
<footer>
VARASYS PolyMeter · <a href="https://codeberg.org/VARASYS/metronome" target="_blank" rel="noopener">source on Codeberg</a>
</footer>
<div class="phil">
<h3>🔌 USBC power everywhere — no batteries</h3>
<p>Every device runs over a single <b>USBC</b> port (the larger ones add a passthrough to daisychain).
No internal battery to wear out; bring a power bank. One connector keeps it all <b>futureproof</b>.</p>
</div>
</div>
</section>
<div class="section-label">Pick a form factor — it loads live below</div>
<div class="panes" id="panes"></div>
<div class="viewport">
<div class="vp-bar"><span id="vpName"><b>PM_E2 Editor</b></span><span><a id="vpInfo" href="/info-pm_e-2.html" target="_blank" rel="noopener">Specs &amp; info ⓘ</a> &nbsp;·&nbsp; <a id="vpOpen" href="/pm_e-2.html" target="_blank" rel="noopener">Open full page ↗</a></span></div>
<iframe id="vp" title="PolyMeter — live viewport" allow="autoplay"></iframe>
</div>
<div class="prog">
<label for="prog">program</label>
<input id="prog" spellcheck="false" autocomplete="off" autocapitalize="off"
placeholder="v1;t120;kick:4;snare:4=.X.X;hat:4/2 — or paste a base64 set-list code">
<button id="progLoad" class="primary" title="Decode, check, and load into the viewport">Load ▸</button>
<button id="progCopy" title="Copy the program string">Copy</button>
<div class="prog-msg" id="progMsg"></div>
<div class="prog-hint">The current program, decoded (not base64). Paste a patch <i>or</i> a base64 setlist code; it's checked, then loaded.
Conventions: GM names or numbers (<code>kick</code> / <code>36</code>), <code>=X.x-</code> steps, <code>/2</code> subdivision, <code>(3,8)</code> euclidean, <code>@-3</code> dB, <code>~</code> polymeter.</div>
</div>
<section class="philosophy" id="rust">
<div class="section-label">Firmware · Rust (developer · alpha)</div>
<div class="phil-grid">
<div class="phil">
<h3>🦀 NativeRust firmware — PM_K1 (RP2350 / Pico 2)</h3>
<p>An experimental nativeRust build of the Kit firmware: one <code>no_std</code> core (track codec + scheduler,
validated against the same golden vectors as the web and CircuitPython builds) plus perboard drivers.
It now runs as a working metronome — boots, drives the ST7796 display, plays builtin grooves with audio
clicks, and has a drumnotation view. Controls: <b>A</b> = play/stop, <b>B</b> = grid/notation, joystick =
tempo &amp; groove. (Alpha; built and reviewed in a host simulator.)</p>
<p style="margin-top:12px">
<a href="/pm-kit.uf2" download
style="display:inline-block; padding:10px 18px; border-radius:9px; font-weight:600; text-decoration:none;
color:#04121b; background:linear-gradient(180deg,#34c6ff,var(--cyan))">Download pmkit.uf2 ↓</a>
</p>
<p class="prog-hint" style="margin-top:10px">Flash: hold <b>BOOTSEL</b> on the Pico 2, plug in USB, and drag the
<code>.uf2</code> onto the RP2350 drive. Recover anytime with BOOTSEL + a CircuitPython <code>.uf2</code>.
Source &amp; staged plan live in <code>rust/</code> and <code>docs/rust-port.md</code>.</p>
</div>
</div>
</section>
</main>
/*@BUILD:include:src/footer.html@*/
<script>
const APP_VERSION = "v0.0.1-dev";
const $ = (id) => document.getElementById(id);
/* engine codec (for decode + lint) + seed set lists (default program). No audio here — the viewport plays. */
const SAMPLES = {}; let state = { bpm:120, volume:0.85 }, meters = [], muteWindows = [];
/*@BUILD:include:src/engine.js@*/
/*@BUILD:include:src/setlists.js@*/
const VERSIONS = [
// PM_E-1 (editor.html) is hidden from the landing — PM_E-2 is the focus. The page still exists.
{ key:"pme2", file:"/pm_e-2.html", name:"PM_E2 Editor", chip:"app", h:640, sum:"The PolyMeter editor, built around engraved drum notation — a 5line percussion staff (Bravura/SMuFL) with Staff / TUBS / Konnakol views, editonstaff, plus flams/drags/rolls, odd meters &amp; clave." },
{ key:"mobile", file:"/mobile.html", name:"PM_M1 Mobile", chip:"app", h:600, sum:"Phone &amp; tablet app — a touchfirst, fullscreen player you can “Add to Home Screen.” Big tap targets, dragtoscrub tempo, a pulsing beat display, screenwakelock, and an iOS fix for the ring/silent switch. Installable &amp; works offline." },
{ key:"kit", file:"/kit.html", name:"PM_K1 Kit", chip:"hw", h:560, sum:"Build it today — a Raspberry Pi Pico on the 52Pi touchscreen kit; tap the 3.5″ screen, joystick tempo, RGB beat light, buzzer. MicroPython firmware included." },
{ key:"explorer", file:"/explorer.html", name:"PM_X1 Explorer", chip:"hw", h:500, sum:"Offtheshelf — the Pimoroni Explorer (RP2350, 2.8″ LCD, 6 buttons, piezo) as a buttondriven sibling to the Kit. Edit on the web with Live sync; the device mirrors play/stop/tempo/track changes both ways." },
{ key:"grid", file:"/grid.html", name:"PM_G1 Grid", chip:"hw", h:470, sum:"Offtheshelf — a Pimoroni Pico Scroll Pack (17×7 white LED matrix + 4 buttons) on a Raspberry Pi Pico. The matrix IS the editor's lane × step pad grid in miniature; edit on the web with Live sync." },
{ key:"teacher", file:"/teacher.html", name:"PM_T1 Teacher", chip:"hw", h:440, sum:"Studio / lesson desk console — colour TFT of every lane, arcade buttons, instrument passthrough." },
{ key:"stage", file:"/stage.html", name:"PM_S1 Stage", chip:"hw", h:430, sum:"Live foot pedal — two footswitches, expressionpedal tempo, a big floorreadable RGB beat light." },
{ key:"micro", file:"/micro.html", name:"PM_P1 Practice", chip:"hw", h:240, sum:"Inline practice bar — clickable thumbroller, amber 14segment, instrument in/out passthrough." },
{ key:"showcase", file:"/showcase.html",name:"PM_D1 Display", chip:"hw", h:540, sum:"Pyramid display piece — an RGBlight pendulum combining every lane's subdivisions &amp; accents." },
{ key:"initial", file:"/player.html", name:"PM_C1 Concept", chip:"", h:440, sum:"The idealized concept render — full multilane display and setlist navigation." },
];
// "Specs & info" link helper - usually swaps /<dev>.html for /info-<dev>.html, but if the
// pane already POINTS at the info page (PM_X-1 has no separate widget yet), keep it as is.
const infoOf = (f) => f.startsWith("/info-") ? f : f.replace("/", "/info-");
const DEFAULT_PROG = (typeof SEED_SETLISTS !== "undefined" && SEED_SETLISTS[0] && SEED_SETLISTS[0].items[0] && SEED_SETLISTS[0].items[0][1]) || "v1;t120;kick:4;snare:4=.X.X;hat:4/2";
let cur = "pme2", userEditing = false;
const vp = $("vp"), box = $("prog");
const verOf = (k) => VERSIONS.find((v) => v.key === k);
function renderPanes() {
$("panes").innerHTML = VERSIONS.map((v) => `
<div class="pane" data-key="${v.key}" role="button" tabindex="0">
<div class="ph"><span class="chip ${v.chip}">${v.chip === "app" ? "Web app" : v.chip === "hw" ? "Hardware" : "Concept"}</span><h3>${v.name}</h3></div>
<p>${v.sum}</p>
<div class="links">
<a class="open" href="${v.file}" target="_blank" rel="noopener" onclick="event.stopPropagation()">Open ↗</a>
<a class="open" href="${infoOf(v.file)}" target="_blank" rel="noopener" onclick="event.stopPropagation()">Specs &amp; info ⓘ</a>
</div>
</div>`).join("");
$("panes").querySelectorAll(".pane").forEach((el) => {
const k = el.dataset.key;
el.addEventListener("click", () => loadVersion(k));
el.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); loadVersion(k); } });
});
}
function loadVersion(key, prog) {
cur = key; const v = verOf(key);
$("panes").querySelectorAll(".pane").forEach((p) => p.classList.toggle("active", p.dataset.key === key));
$("vpName").innerHTML = "<b>" + v.name + "</b>";
$("vpOpen").href = v.file;
$("vpInfo").href = infoOf(v.file);
vp.style.height = (v.h || 440) + "px";
vp.src = v.file + "?embed=1" + (prog ? "#p=" + encodeURIComponent(prog) : "");
if (prog && !userEditing) box.value = prog;
}
/* program I/O: lint a patch OR a base64 set-list code, return canonical plain text */
function lintProgram(text) {
text = (text || "").trim(); if (!text) return { ok:false, msg:"empty — type or paste a program" };
const m = text.match(/[#?&](p|sl)=([^&\s]+)/); let kind = null, payload = text;
if (m) { kind = m[1]; try { payload = decodeURIComponent(m[2]); } catch (e) { payload = m[2]; } }
const b64 = /^[A-Za-z0-9_-]{12,}$/.test(payload) && !/[;:]/.test(payload);
try {
if (kind === "sl" || (kind !== "p" && b64)) {
const sl = codeToSetlist(payload);
if (!sl.items || !sl.items.length) throw new Error("set-list code has no items");
return { ok:true, plain:setupToPatch(sl.items[0]), msg:"decoded set list “" + sl.title + "” (" + sl.items.length + " item" + (sl.items.length>1?"s":"") + ") — loading item 1" };
}
const s = patchToSetup(payload);
if (!s.lanes.length) throw new Error("no lanes — try e.g. kick:4");
return { ok:true, plain:setupToPatch(s), msg:s.lanes.length + " lane" + (s.lanes.length>1?"s":"") + " · " + s.bpm + " BPM" };
} catch (e) { return { ok:false, msg:"✗ " + e.message }; }
}
function setMsg(t, ok) { const m = $("progMsg"); m.textContent = t || ""; m.classList.toggle("ok", !!ok && !!t); m.classList.toggle("bad", !ok && !!t); }
function doLoad() {
const r = lintProgram(box.value);
if (!r.ok) { box.classList.add("err"); setMsg(r.msg, false); return; }
box.classList.remove("err"); box.value = r.plain; setMsg("✓ " + r.msg, true);
loadVersion(cur, r.plain);
}
$("progLoad").addEventListener("click", doLoad);
$("prog").addEventListener("keydown", (e) => { if (e.key === "Enter") { e.preventDefault(); doLoad(); } });
$("prog").addEventListener("focus", () => userEditing = true);
$("prog").addEventListener("blur", () => userEditing = false);
$("prog").addEventListener("input", () => box.classList.remove("err"));
$("progCopy").addEventListener("click", async () => { try { await navigator.clipboard.writeText(box.value); const b = $("progCopy"); b.textContent = "Copied!"; setTimeout(() => b.textContent = "Copy", 1200); } catch (e) { box.select(); } });
/* the viewport reports its height + current program back to us */
addEventListener("message", (e) => {
if (!e.data || e.source !== vp.contentWindow) return;
if (e.data.type === "varasys-h" && typeof e.data.h === "number") vp.style.height = e.data.h + "px";
else if (e.data.type === "varasys-prog" && typeof e.data.patch === "string" && !userEditing) { box.value = e.data.patch; box.classList.remove("err"); }
});
renderPanes();
// default = each device's built-in set lists (no forced program); the box fills from what the device reports
loadVersion("pme2");
/*@BUILD:include:src/chrome.js@*/
</script>
</body>
</html>

68
info-editor.html Normal file
View file

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VARASYS PM_E1 PolyMeter Editor — what it is</title>
<meta name="description" content="PM_E1 PolyMeter Editor — the web workbench for the family: stack independent meter lanes with their own subdivision, drum voice, perstep accents/ghosts/mutes, swing, polyrhythm, set lists and perlane dB gain. Design once; it plays on any form factor." />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
--panel-bg:#161b22; --panel-bd:#2a313c; --field-bg:#0e1116; --field-bd:#2a313c; --silk:#aab2bc; }
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
--panel-bg:#ffffff; --panel-bd:#d2dae4; --field-bg:#f1f4f8; --field-bd:#d2dae4; }
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt);
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); }
a{ color:var(--link); }
main{ width:100%; max-width:980px; margin:0 auto; }
.info-hero{ text-align:center; padding:16px 8px 2px; }
.info-hero h1{ font-size:clamp(24px,5vw,36px); margin:0; letter-spacing:-.01em; }
.info-hero .sub{ margin:9px auto 0; max-width:64ch; font-size:14.5px; }
</style>
</head>
<body>
/*@BUILD:include:src/header.html@*/
<main>
<section class="info-hero">
<h1>PM_E1 PolyMeter Editor</h1>
<p class="sub">The web workbench for the whole family — design grooves here, and the same program string plays identically on every form factor.</p>
</section>
/*@BUILD:include:src/infoembed.html@*/
<section class="about">
<h2>What it is</h2>
<div class="ff-tags"><span>Web app</span><span>The workbench</span><span>Runs in any browser</span></div>
<p>The editor is where you build a groove: stack independent <b>meter lanes</b>, each with its own subdivision,
drum voice and perstep <b>accents, ghosts and mutes</b>, plus swing, ratio polyrhythm, set lists and a
perlane <b>dB gain</b>. It's zeroinstall — it runs in any modern browser and works fully offline.</p>
<p>Everything you design saves to a compact <b>program string</b> in the shared sharelanguage. That same string
loads into any of the hardware concepts or an embedded widget, so a groove built here plays back identically
everywhere. There's no bill of materials — it's the software workbench, not a buildable device; the buildable
realizations are the <a href="/info-teacher.html">Teacher</a>, <a href="/info-stage.html">Stage</a>,
<a href="/info-micro.html">Practice</a> and <a href="/info-showcase.html">Display</a>.</p>
</section>
<p class="sub" style="max-width:760px;margin:14px auto 0">Embed the editor (or any device) elsewhere with one <code>&lt;div&gt;</code> + a script —
see <a href="/embed.html">the embed docs</a>.</p>
</main>
/*@BUILD:include:src/footer.html@*/
<script>
const APP_VERSION = "v0.0.1-dev";
window.INFO_DEVICE = { file:"/editor.html", name:"PM_E1 PolyMeter Editor" };
/*@BUILD:include:src/infoembed.js@*/
/*@BUILD:include:src/chrome.js@*/
</script>
</body>
</html>

177
info-explorer.html Normal file
View file

@ -0,0 +1,177 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VARASYS PM_X-1 Explorer - wiring, parts &amp; firmware (Pimoroni Explorer / RP2350)</title>
<meta name="description" content="PM_X-1 Explorer - the Pimoroni Explorer (PIM744, RP2350) as a 6-button polymeter metronome with live-sync to the web editor. Pinout, parts list, and the precompiled CircuitPython firmware bundle." />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
/* ?embed=1 -> strip site chrome (base.css [data-embed]) + auto-size to the host iframe */
(function(){ if(!/[?&]embed=1/.test(location.search)) return;
document.documentElement.dataset.embed="1";
function ph(){ try{ parent.postMessage({type:"varasys-h",h:Math.ceil(document.documentElement.getBoundingClientRect().height)},"*"); }catch(e){} }
addEventListener("load",ph); addEventListener("resize",ph); setTimeout(ph,300); setTimeout(ph,1000);
})();
</script>
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
--panel-bg:#161b22; --panel-bd:#2a313c; --field-bg:#0e1116; --field-bd:#2a313c; --silk:#aab2bc; }
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
--panel-bg:#ffffff; --panel-bd:#d2dae4; --field-bg:#f1f4f8; --field-bd:#d2dae4; }
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt);
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); }
a{ color:var(--link); }
main{ width:100%; max-width:980px; margin:0 auto; }
.info-hero{ text-align:center; padding:16px 8px 2px; }
.info-hero h1{ font-size:clamp(24px,5vw,36px); margin:0; letter-spacing:-.01em; }
.info-hero .sub{ margin:9px auto 0; max-width:64ch; font-size:14.5px; }
.steps{ width:100%; max-width:760px; margin:8px auto 0; color:var(--muted); font-size:14px; line-height:1.6; }
.steps li{ margin:5px 0; }
.steps code, .about code, .sub code { background:var(--field-bg); border:1px solid var(--field-bd); border-radius:5px; padding:1px 5px; font-size:12.5px; }
.dl{ display:inline-flex; align-items:center; gap:7px; margin:4px 10px 4px 0; padding:9px 14px; border-radius:10px;
background:linear-gradient(180deg,#34c6ff,var(--cyan)); color:#04121b; font-weight:700; text-decoration:none; font-size:13.5px; }
.dl.alt{ background:var(--field-bg); color:var(--txt); border:1px solid var(--field-bd); font-weight:600; }
</style>
</head>
<body>
/*@BUILD:include:src/header.html@*/
<main>
<section class="info-hero">
<h1>PM_X-1 Explorer</h1>
<p class="sub">The off-the-shelf <b>Pimoroni Explorer Kit</b> (RP2350, 2.8&quot; LCD, 6 buttons, piezo) as a polymeter metronome - sibling to the PM_K-1 Kit, sharing the engine, program-string grammar, and live-sync protocol with the web editor.</p>
</section>
<section class="about">
<h2>What it is</h2>
<div class="ff-tags"><span class="hw">Buildable now</span><span>RP2350 (Pico 2 class)</span><span>Pimoroni Explorer PIM744</span><span>~$60</span></div>
<p>The <a href="https://shop.pimoroni.com/products/explorer" target="_blank" rel="noopener">Pimoroni Explorer Kit (PIM744)</a> is a finished
development board: <b>RP2350B</b> built in, <b>2.8&quot; ST7789V 320x240 IPS LCD</b>, <b>6 user buttons</b> (A/B/C
on the left of the screen, X/Y/Z on the right), a <b>piezo speaker</b>, USB-C, a JST-PH battery
connector, and a mini breadboard with 6 GPIOs / 3 ADCs broken out for sensor projects. No soldering;
you flash CircuitPython and drop the firmware on. <b>No touchscreen, no joystick, no RGB LED</b> -
everything is driven from the 6 buttons.</p>
<p>It runs the same <b>polymeter engine</b> and the same <b>program strings</b> as the web editor.
Beat editing is done in the browser; <b>Live sync</b> mirrors edits to the device in real time
(HELLO/FULL/DELTA over USB-MIDI), and the device mirrors play/stop/tempo/track changes back. The
piezo clicks; a tiny on-screen dot shows run state.</p>
</section>
<details class="spec" open>
<summary>Wiring - the Pimoroni Explorer fixed pinout (no breadboarding required)</summary>
<div class="spec-body">
<p class="sub">Everything is wired on the board; this is what the firmware reads. The display is driven by an 8-bit parallel bus initialised by CircuitPython's official board definition - we use <code>board.DISPLAY</code> directly.</p>
<table class="bom">
<thead><tr><th>Component</th><th>RP2350 pins</th></tr></thead>
<tbody>
<tr class="grp"><td colspan="2">Display - 2.8&quot; ST7789V, 320x240 (8-bit parallel 8080)</td></tr>
<tr><td class="part">BL / CS / DC / WR / RD / D0-D7</td><td>GP26 / GP27 / GP28 / GP30 / GP31 / GP32-GP39 (board.c)</td></tr>
<tr class="grp"><td colspan="2">Buttons (digital, pull-up)</td></tr>
<tr><td class="part">A (play/stop) / B (tap tempo) / C (menu)</td><td>GP16 / GP15 / GP14 (left side, top to bottom)</td></tr>
<tr><td class="part">X (prev track) / Y (-bpm) / Z (next track)</td><td>GP17 / GP18 / GP19 (right side, top to bottom)</td></tr>
<tr class="grp"><td colspan="2">Audio</td></tr>
<tr><td class="part">Piezo PWM</td><td>GP12</td></tr>
<tr><td class="part">Amp enable</td><td>GP13</td></tr>
<tr class="grp"><td colspan="2">I2C (QwSTEMMA - unused by the firmware, free for sensors)</td></tr>
<tr><td class="part">SDA / SCL</td><td>GP20 / GP21</td></tr>
</tbody>
</table>
</div>
</details>
<details class="spec" open>
<summary>Controls</summary>
<div class="spec-body">
<table class="bom">
<thead><tr><th>Button</th><th>Action</th></tr></thead>
<tbody>
<tr><td class="part">A</td><td>play / stop</td></tr>
<tr><td class="part">B</td><td>tap tempo</td></tr>
<tr><td class="part">C</td><td>menu (Settings / Practice log / Help / About)</td></tr>
<tr><td class="part">X</td><td>prev track (hold to repeat)</td></tr>
<tr><td class="part">Z</td><td>next track (hold to repeat)</td></tr>
<tr><td class="part">Y</td><td>tempo -1 (hold = -5 after 1.5 s)</td></tr>
<tr><td class="part">X + Z (chord)</td><td>tempo +1 (same hold rule as Y)</td></tr>
<tr class="grp"><td colspan="2">In a menu</td></tr>
<tr><td class="part">X / Z</td><td>move cursor up / down (Help: prev / next page)</td></tr>
<tr><td class="part">Y</td><td>decrement the focused value</td></tr>
<tr><td class="part">A</td><td>cycle / increment / select</td></tr>
<tr><td class="part">B</td><td>back (cancel)</td></tr>
<tr><td class="part">C</td><td>close the menu</td></tr>
</tbody>
</table>
</div>
</details>
<details class="spec" open>
<summary>Parts</summary>
<div class="spec-body">
<p class="sub">A finished development board, not a custom build - ballpark one-off price (USD).</p>
<table class="bom">
<thead><tr><th>Part</th><th class="q">Qty</th><th class="c">~$</th></tr></thead>
<tbody>
<tr><td class="part">Pimoroni Explorer Kit (PIM744) <span class="spec">- RP2350B, 2.8&quot; ST7789V, 6 buttons, piezo + amp, USB-C</span></td><td class="q">1</td><td class="c">60</td></tr>
<tr><td class="part">USB-C cable <span class="spec">- power + flashing</span></td><td class="q">1</td><td class="c">2</td></tr>
<tr class="total"><td>Total (one-off)</td><td class="q"></td><td class="c">&asymp; $62</td></tr>
</tbody>
</table>
<p class="sub" style="margin-top:10px">Reference: <a href="https://shop.pimoroni.com/products/explorer" target="_blank" rel="noopener">Pimoroni Explorer product page</a>
&middot; <a href="https://github.com/pimoroni/explorer" target="_blank" rel="noopener">vendor code</a>
&middot; <a href="https://circuitpython.org/board/pimoroni_explorer2350/" target="_blank" rel="noopener">CircuitPython for Pimoroni Explorer (RP2350)</a>.</p>
</div>
</details>
<details class="spec" open>
<summary>Firmware - self-contained appliance (USB drive &middot; web-driven editing via Live sync &middot; MIDI audio &middot; practice log)</summary>
<div class="spec-body">
<p class="sub">The firmware turns the Explorer into a self-contained appliance: it mounts as a
<b>USB drive</b> carrying the (precompiled) firmware, your tracks and an offline copy of this editor;
drives a lanes/pads display with <b>web-driven editing</b> via <b>Live sync</b>; <b>logs your practice</b> to
<code>history.json</code>; takes new set lists <b>pushed from the editor over USB-MIDI</b>; and plays
out your <b>computer's speakers over USB-MIDI</b>. By default the firmware owns the drive (read-only to
the computer - so it can log and can't be accidentally erased); hold <b>button A</b> at power-on for
editor mode (drive writable).</p>
<p>
<a class="dl" href="/pm_x1_circuitpy.zip" download>Download CircuitPython bundle &darr;</a>
<a class="dl alt" href="https://codeberg.org/VARASYS/metronome/src/branch/main/pico-explorer" target="_blank" rel="noopener">Source + README &nearr;</a>
</p>
<ol class="steps">
<li>Flash <b>CircuitPython for Pimoroni Explorer (RP2350)</b>
(<a href="https://circuitpython.org/board/pimoroni_explorer2350/" target="_blank" rel="noopener">download</a>)
via BOOTSEL, unzip the bundle onto <code>CIRCUITPY</code>, and power-cycle. It boots into appliance mode.</li>
<li><b>Edit on the web:</b> open the <a href="/editor-beta.html">editor (beta)</a> in Chrome / Edge / Firefox,
click <b>&#x1f517; Live sync</b>, and the Explorer mirrors your edits live (beats, tempo, track changes).</li>
<li><b>Save a set list to the device</b> for offline use: set-list <b>&middot;&middot;&middot;</b> menu &rarr;
<b>&#x1f4DF; Save to device</b>. It's pushed over USB-MIDI; the device persists it to
<code>/programs.json</code>.</li>
<li><b>Play through your computer:</b> click <b>&#x1f3b9; Device audio</b>, then press <b>A</b> on the device -
the full groove sounds through your speakers over USB-MIDI, in sync. A <b>MIDI</b> badge appears in the
header and the piezo auto-mutes.</li>
<li><b>Practice log:</b> press <b>C</b> &rarr; <b>Practice log</b>. Plays over 5 s appear (time &middot; BPM &middot; duration &middot; bars).</li>
<li><b>Firmware updates:</b> &middot;&middot;&middot; menu &rarr; <b>&#x2B06; Update firmware</b> - the editor reads
the device id (X = Explorer), fetches the matching <code>pico-explorer-app.mpy</code>, and pushes it
over USB-MIDI. The device A/B-updates with automatic rollback if a build won't boot.</li>
</ol>
</div>
</details>
<p class="sub" style="max-width:760px;margin:14px auto 0">Pairs with the touch-driven <a href="/info-kit.html">PM_K-1 Kit</a> - same engine, same programs.json, same web editor.</p>
</main>
/*@BUILD:include:src/footer.html@*/
<script>
const APP_VERSION = "v0.0.1-dev";
/*@BUILD:include:src/chrome.js@*/
</script>
</body>
</html>

168
info-grid.html Normal file
View file

@ -0,0 +1,168 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VARASYS PM_G-1 Grid - wiring, parts &amp; firmware (Pimoroni Pico Scroll Pack / RP2040)</title>
<meta name="description" content="PM_G-1 Grid - the Pimoroni Pico Scroll Pack (PIM545, 17x7 LED matrix + 4 buttons) on a Raspberry Pi Pico as a polymeter metronome. Pinout, parts list, and the native Rust firmware (flash the .uf2)." />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
/* ?embed=1 -> strip site chrome (base.css [data-embed]) + auto-size to the host iframe */
(function(){ if(!/[?&]embed=1/.test(location.search)) return;
document.documentElement.dataset.embed="1";
function ph(){ try{ parent.postMessage({type:"varasys-h",h:Math.ceil(document.documentElement.getBoundingClientRect().height)},"*"); }catch(e){} }
addEventListener("load",ph); addEventListener("resize",ph); setTimeout(ph,300); setTimeout(ph,1000);
})();
</script>
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
--panel-bg:#161b22; --panel-bd:#2a313c; --field-bg:#0e1116; --field-bd:#2a313c; --silk:#aab2bc; }
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
--panel-bg:#ffffff; --panel-bd:#d2dae4; --field-bg:#f1f4f8; --field-bd:#d2dae4; }
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt);
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); }
a{ color:var(--link); }
main{ width:100%; max-width:980px; margin:0 auto; }
.info-hero{ text-align:center; padding:16px 8px 2px; }
.info-hero h1{ font-size:clamp(24px,5vw,36px); margin:0; letter-spacing:-.01em; }
.info-hero .sub{ margin:9px auto 0; max-width:64ch; font-size:14.5px; }
.steps{ width:100%; max-width:760px; margin:8px auto 0; color:var(--muted); font-size:14px; line-height:1.6; }
.steps li{ margin:5px 0; }
.steps code, .about code, .sub code { background:var(--field-bg); border:1px solid var(--field-bd); border-radius:5px; padding:1px 5px; font-size:12.5px; }
.dl{ display:inline-flex; align-items:center; gap:7px; margin:4px 10px 4px 0; padding:9px 14px; border-radius:10px;
background:linear-gradient(180deg,#34c6ff,var(--cyan)); color:#04121b; font-weight:700; text-decoration:none; font-size:13.5px; }
.dl.alt{ background:var(--field-bg); color:var(--txt); border:1px solid var(--field-bd); font-weight:600; }
</style>
</head>
<body>
/*@BUILD:include:src/header.html@*/
<main>
<section class="info-hero">
<h1>PM_G-1 Grid</h1>
<p class="sub">The off-the-shelf <b>Pimoroni Pico Scroll Pack</b> (a 17&times;7 white LED matrix + 4 buttons) on a plain <b>Raspberry Pi Pico</b> - sibling to the PM_K-1 Kit and PM_X-1 Explorer, sharing the engine, program-string grammar, and live-sync protocol with the web editor.</p>
</section>
<section class="about">
<h2>What it is</h2>
<div class="ff-tags"><span class="hw">Buildable now</span><span>RP2040 (Raspberry Pi Pico)</span><span>Pimoroni Pico Scroll Pack PIM545</span><span>~$30</span></div>
<p>The <a href="https://shop.pimoroni.com/products/pico-scroll-pack" target="_blank" rel="noopener">Pico Scroll Pack (PIM545)</a>
plugs straight onto a Raspberry Pi Pico's headers: <b>119 white LEDs in a 17&times;7 matrix</b> driven by an
<b>IS31FL3731</b> over I&sup2;C (individually brightness-controlled), and <b>4 buttons</b> (A / B / X / Y).
No soldering, no touchscreen, no joystick, no RGB - and <b>no onboard speaker</b>. About 65 &times; 25 &times; 10 mm
on top of the Pico. Power is over VSYS (USB or battery).</p>
<p>It runs the <b>native Rust firmware</b> (<code>rust/pm-grid</code>), built on the same <b>polymeter engine</b>
(the shared <code>track-format</code> crate) and <b>program strings</b> as the web editor. The 7-row &times; 17-column
matrix maps directly onto the editor's <b>lane &times; step</b> pad grid: each lane is a row, each step a column, and
LED brightness encodes accent / normal / ghost. <b>Audio is over USB-MIDI</b> - turn on the editor's
<b>&#x1f3b9; Device audio</b> to hear the clicks through your computer (the Scroll Pack has no speaker of its own).</p>
</section>
<details class="spec" open>
<summary>Wiring - the Pico Scroll Pack fixed pinout (just press it onto the Pico)</summary>
<div class="spec-body">
<p class="sub">Everything is wired through the header; this is what the firmware reads. Pins verified against Pimoroni's <code>pico_scroll</code> library.</p>
<table class="bom">
<thead><tr><th>Component</th><th>Pico pins</th></tr></thead>
<tbody>
<tr class="grp"><td colspan="2">LED matrix - 17&times;7 white, IS31FL3731 (I&sup2;C @ 0x74)</td></tr>
<tr><td class="part">SDA / SCL</td><td>GP4 / GP5</td></tr>
<tr class="grp"><td colspan="2">Buttons (digital, pull-up)</td></tr>
<tr><td class="part">A (play/stop) / B (track)</td><td>GP12 / GP13</td></tr>
<tr><td class="part">X (+bpm) / Y (-bpm)</td><td>GP14 / GP15</td></tr>
<tr class="grp"><td colspan="2">Audio - over USB-MIDI (the Scroll Pack has no speaker)</td></tr>
</tbody>
</table>
</div>
</details>
<details class="spec" open>
<summary>Controls &amp; views</summary>
<div class="spec-body">
<table class="bom">
<thead><tr><th>Button</th><th>Tap</th><th>Hold</th></tr></thead>
<tbody>
<tr><td class="part">A</td><td>play / stop</td><td>cycle view (Ticker &rarr; Grid &rarr; Pendulum)</td></tr>
<tr><td class="part">B</td><td>next track</td><td>next set list</td></tr>
<tr><td class="part">X</td><td>tempo +1</td><td>repeat (+5 after ~1.5 s)</td></tr>
<tr><td class="part">Y</td><td>tempo -1</td><td>repeat (-5 after ~1.5 s)</td></tr>
</tbody>
</table>
<table class="bom" style="margin-top:10px">
<thead><tr><th>View</th><th>What the 17&times;7 matrix shows</th></tr></thead>
<tbody>
<tr><td class="part">Ticker</td><td>the track name infinite-scrolls along the left; a beat strip on the top row marks the beats with a bright playhead; the BPM is pinned right, rotated 90&deg; CCW (a hundreds dot-bar + the last two digits). The whole matrix flashes on the downbeat.</td></tr>
<tr><td class="part">Grid</td><td>lanes as rows, steps as columns; brightness = accent / normal / ghost; a bright playhead column tracks the beat (bars &gt; 17 steps scale to fit - no steps dropped)</td></tr>
<tr><td class="part">Pendulum</td><td>a column bounces across the bar like a metronome arm, full-height flash on each beat</td></tr>
</tbody>
</table>
<p class="sub" style="margin-top:8px">The button mapping is deliberately simple (this is a UI prototype) and easy to re-bind in <code>rust/pm-grid/src/main.rs</code>.</p>
</div>
</details>
<details class="spec" open>
<summary>Parts</summary>
<div class="spec-body">
<p class="sub">Two off-the-shelf parts, no soldering - ballpark one-off price (USD).</p>
<table class="bom">
<thead><tr><th>Part</th><th class="q">Qty</th><th class="c">~$</th></tr></thead>
<tbody>
<tr><td class="part">Pimoroni Pico Scroll Pack (PIM545) <span class="spec">- 17&times;7 white LED matrix (IS31FL3731) + 4 buttons</span></td><td class="q">1</td><td class="c">22</td></tr>
<tr><td class="part">Raspberry Pi Pico <span class="spec">- RP2040; pre-soldered headers so the pack presses on</span></td><td class="q">1</td><td class="c">5</td></tr>
<tr><td class="part">USB cable <span class="spec">- power + flashing (micro-USB)</span></td><td class="q">1</td><td class="c">2</td></tr>
<tr class="total"><td>Total (one-off)</td><td class="q"></td><td class="c">&asymp; $29</td></tr>
</tbody>
</table>
<p class="sub" style="margin-top:10px">Reference: <a href="https://shop.pimoroni.com/products/pico-scroll-pack" target="_blank" rel="noopener">Pico Scroll Pack product page</a>
&middot; <a href="https://github.com/pimoroni/pimoroni-pico/tree/main/libraries/pico_scroll" target="_blank" rel="noopener">vendor code (pico_scroll)</a>.</p>
</div>
</details>
<details class="spec" open>
<summary>Firmware - native Rust (flash the <code>.uf2</code>)</summary>
<div class="spec-body">
<p class="sub">The Grid runs <b>native Rust firmware</b> (<code>rust/pm-grid</code>), sharing the same
<code>track-format</code> engine crate as the web editor and the other devices. It's a button-driven LED
metronome: <b>three views</b> (Ticker, Grid, Pendulum), the <b>built-in set lists</b>, tempo <b>ramp</b> +
<b>gap-trainer</b>, and a whole-matrix flash on the downbeat. <b>Audio plays through your computer over
USB-MIDI</b> - the Scroll Pack has no speaker. Flashing is a one-step <code>.uf2</code> drag (no CircuitPython,
no drive bundle).</p>
<p>
<a class="dl" href="/pm-grid.uf2" download>Download firmware (pm-grid.uf2) &darr;</a>
<a class="dl alt" href="https://codeberg.org/VARASYS/metronome/src/branch/main/rust/pm-grid" target="_blank" rel="noopener">Source &nearr;</a>
</p>
<ol class="steps">
<li><b>Flash it:</b> hold <b>BOOTSEL</b> on the Pico, plug in USB (it appears as the <code>RPI-RP2</code> drive),
and drag <b>pm-grid.uf2</b> onto it. It flashes and reboots - "PM-G1 GRID" scrolls once, then the Ticker view.</li>
<li><b>Play through your computer:</b> plug the Pico into your computer, open the
<a href="/editor.html">editor</a> in Chrome / Edge / Firefox, click <b>&#x1f3b9; Device audio</b>, and press
<b>A</b> on the device - each click sounds through your speakers over USB-MIDI (GM channel 10).</li>
<li><b>Controls:</b> <b>A</b> tap = play/stop, hold = cycle view; <b>B</b> tap = next track, hold = next set list;
<b>X / Y</b> = tempo up / down (auto-repeats while held).</li>
</ol>
<p class="sub" style="margin-top:8px">Built from <code>rust/pm-grid</code> via its <code>build.sh</code> (RP2040 /
<code>thumbv6m</code>). For development, a Raspberry Pi Debug Probe flashes it and streams logs over
<code>probe-rs</code> + <code>defmt</code> (the <code>pm-grid.elf</code> is served alongside for log decoding).
Live-sync editing, on-device practice log and editor firmware-push are on the roadmap (not in the Rust build yet).</p>
</div>
</details>
<p class="sub" style="max-width:760px;margin:14px auto 0">Pairs with the touch-driven <a href="/info-kit.html">PM_K-1 Kit</a> and the button-driven <a href="/info-explorer.html">PM_X-1 Explorer</a> - same engine, same programs.json, same web editor.</p>
</main>
/*@BUILD:include:src/footer.html@*/
<script>
const APP_VERSION = "v0.0.1-dev";
/*@BUILD:include:src/chrome.js@*/
</script>
</body>
</html>

143
info-kit.html Normal file
View file

@ -0,0 +1,143 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VARASYS PM_K1 Kit — wiring, parts &amp; firmware (Raspberry Pi Pico build)</title>
<meta name="description" content="PM_K1 Kit — build a touchscreen polymeter metronome from a Raspberry Pi Pico on the 52Pi EP0172 breadboard kit (3.5in ST7796 captouch, joystick, RGB, speaker). Pinout, parts list, and the precompiled CircuitPython firmware bundle." />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
--panel-bg:#161b22; --panel-bd:#2a313c; --field-bg:#0e1116; --field-bd:#2a313c; --silk:#aab2bc; }
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
--panel-bg:#ffffff; --panel-bd:#d2dae4; --field-bg:#f1f4f8; --field-bd:#d2dae4; }
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt);
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); }
a{ color:var(--link); }
main{ width:100%; max-width:980px; margin:0 auto; }
.info-hero{ text-align:center; padding:16px 8px 2px; }
.info-hero h1{ font-size:clamp(24px,5vw,36px); margin:0; letter-spacing:-.01em; }
.info-hero .sub{ margin:9px auto 0; max-width:64ch; font-size:14.5px; }
.steps{ width:100%; max-width:760px; margin:8px auto 0; color:var(--muted); font-size:14px; line-height:1.6; }
.steps li{ margin:5px 0; }
.steps code, .about code, .sub code { background:var(--field-bg); border:1px solid var(--field-bd); border-radius:5px; padding:1px 5px; font-size:12.5px; }
.dl{ display:inline-flex; align-items:center; gap:7px; margin:4px 10px 4px 0; padding:9px 14px; border-radius:10px;
background:linear-gradient(180deg,#34c6ff,var(--cyan)); color:#04121b; font-weight:700; text-decoration:none; font-size:13.5px; }
.dl.alt{ background:var(--field-bg); color:var(--txt); border:1px solid var(--field-bd); font-weight:600; }
</style>
</head>
<body>
/*@BUILD:include:src/header.html@*/
<main>
<section class="info-hero">
<h1>PM_K1 Kit</h1>
<p class="sub">Build it yourself: a Raspberry Pi Pico on the 52Pi breadboard kit becomes a touchscreen polymeter metronome — same engine, same program strings, with a precompiled CircuitPython firmware bundle and oneclick updates over USBMIDI.</p>
</section>
/*@BUILD:include:src/infoembed.html@*/
<section class="about">
<h2>What it is</h2>
<div class="ff-tags"><span class="hw">Buildable now</span><span>Raspberry Pi Pico</span><span>52Pi EP0172 kit</span><span>~$45 incl. Pico</span></div>
<p>This is the first member of the family you can actually build today from offtheshelf parts: a
<b>Raspberry Pi Pico</b> seated on the <b>52Pi EP0172 "Pico Breadboard Kit Plus"</b>, which carries a
3.5″ <b>ST7796</b> 320×480 capacitivetouch screen (<b>GT911</b>), a PSP <b>joystick</b>, a <b>WS2812 RGB</b>
LED, a <b>speaker</b> and two buttons — all prewired, so you don't solder anything; you just seat the Pico
and drop the firmware bundle onto its USB drive.</p>
<p>It runs the same <b>polymeter engine</b> and the same <b>program strings</b> as the web editor: design a
groove on the site and <b>Save to device</b> over USBMIDI (or edit on the device's touchscreen — beats,
lanes, playlists), and it plays standalone. Tap the screen, nudge tempo with the joystick; the RGB shows
run/stop and the beat pulse, and the speaker clicks. Powered over the Pico's USB.</p>
</section>
<details class="spec" open>
<summary>Wiring — the EP0172 fixed pinout (Raspberry Pi Pico)</summary>
<div class="spec-body">
<p class="sub">Everything is wired on the board; this is just what the firmware drives. No breadboarding required.</p>
<table class="bom">
<thead><tr><th>Component</th><th>Raspberry Pi Pico pins</th></tr></thead>
<tbody>
<tr class="grp"><td colspan="2">Display — 3.5″ ST7796, 320×480 (SPI0)</td></tr>
<tr><td class="part">SCK / MOSI</td><td>GP2 / GP3</td></tr>
<tr><td class="part">CS / DC / RST</td><td>GP5 / GP6 / GP7</td></tr>
<tr class="grp"><td colspan="2">Touch — GT911 capacitive (I2C0)</td></tr>
<tr><td class="part">SDA / SCL <span class="spec">— addr 0x5D</span></td><td>GP8 / GP9</td></tr>
<tr class="grp"><td colspan="2">Controls &amp; feedback</td></tr>
<tr><td class="part">PSP joystick X / Y</td><td>ADC0 (GP26) / ADC1 (GP27)</td></tr>
<tr><td class="part">Button A (play/stop) / Button B (tap)</td><td>GP15 / GP14</td></tr>
<tr><td class="part">WS2812 RGB LED</td><td>GP12</td></tr>
<tr><td class="part">Speaker</td><td>GP13</td></tr>
</tbody>
</table>
</div>
</details>
<details class="spec" open>
<summary>Parts</summary>
<div class="spec-body">
<p class="sub">An offtheshelf kit, not a custom board — ballpark oneoff prices (USD).</p>
<table class="bom">
<thead><tr><th>Part</th><th class="q">Qty</th><th class="c">~$</th></tr></thead>
<tbody>
<tr><td class="part">Raspberry Pi Pico (or Pico W / Pico 2) <span class="spec">— the brain</span></td><td class="q">1</td><td class="c">5</td></tr>
<tr><td class="part">52Pi EP0172 "Pico Breadboard Kit Plus" <span class="spec">— 3.5″ ST7796 captouch, GT911, PSP joystick, WS2812 RGB, speaker, 2 buttons, breadboard, acrylic panel</span></td><td class="q">1</td><td class="c">38</td></tr>
<tr><td class="part">USB cable <span class="spec">— power + flashing</span></td><td class="q">1</td><td class="c">2</td></tr>
<tr class="total"><td>Total (oneoff)</td><td class="q"></td><td class="c">≈ $45</td></tr>
</tbody>
</table>
<p class="sub" style="margin-top:10px">Reference: <a href="https://wiki.52pi.com/index.php?title=EP-0172" target="_blank" rel="noopener">52Pi EP0172 wiki</a>
· <a href="https://github.com/geeekpi/pico_breakboard_kit" target="_blank" rel="noopener">vendor code</a>. Lots may ship the screen as ST7796 (320×480) — this build targets that.</p>
</div>
</details>
<details class="spec" open>
<summary>Firmware — selfcontained appliance (USB drive · ondevice editing · push programming · MIDI audio · practice log)</summary>
<div class="spec-body">
<p class="sub">The firmware turns the Pico into a selfcontained appliance: it mounts as a
<b>USB drive</b> carrying the (precompiled) firmware, your tracks and an offline copy of this editor; drives
a full lanes/pads touchscreen with <b>ondevice editing</b> (tap beats, tap an instrument for the lane
editor, add/remove lanes, save/revert); <b>logs your practice</b> to <code>history.json</code>; takes new
set lists <b>pushed from the editor over USBMIDI</b>; and plays out your <b>computer's speakers over
USBMIDI</b>. By default the firmware owns the drive (readonly to the computer — so it can log and
can't be accidentally erased); hold <b>button A</b> at poweron for editor mode (drive writable).</p>
<p>
<a class="dl" href="/pm_k1_circuitpy.zip" download>Download CircuitPython bundle ↓</a>
<a class="dl alt" href="https://codeberg.org/VARASYS/metronome/src/branch/main/pico-cp" target="_blank" rel="noopener">Source + README ↗</a>
</p>
<ol class="steps">
<li>Flash <b>CircuitPython</b> (<a href="https://circuitpython.org/board/raspberry_pi_pico/" target="_blank" rel="noopener">raspberry_pi_pico</a>)
via BOOTSEL, unzip the bundle onto <code>CIRCUITPY</code>, and powercycle. It boots into appliance mode.</li>
<li><b>Program it from the web:</b> build a set list in the <a href="/editor.html">editor</a> (Chrome/Edge/Firefox),
then the setlist <b></b> menu → <b>📟 Save to device</b>. It's pushed over USBMIDI and the device shows
<b>Saved ✓</b>. (Fallback for any browser: it downloads <code>programs.json</code> — boot holding A and drag it on.)</li>
<li><b>Play through your computer:</b> click <b>🎹 Device audio</b>, then press play on the device — the full
groove sounds through your speakers over USBMIDI, in sync; the screen shows a <b>MIDI</b> badge and the speaker mutes.</li>
<li><b>Practice log:</b> plays over 5 s appear at the bottom of the screen (time · BPM · duration · track); tap a row twice to delete.</li>
<li><b>Firmware updates:</b> ⋯ menu → <b>⬆ Update firmware</b> — it checks your version, pushes the latest over USBMIDI, and the device A/Bupdates with automatic rollback if a build won't boot.</li>
</ol>
</div>
</details>
<p class="sub" style="max-width:760px;margin:14px auto 0">Embed this widget elsewhere with one <code>&lt;div&gt;</code> + a script —
see <a href="/embed.html">the embed docs</a>.</p>
</main>
/*@BUILD:include:src/footer.html@*/
<script>
const APP_VERSION = "v0.0.1-dev";
window.INFO_DEVICE = { file:"/kit.html", name:"PM_K1 Kit" };
/*@BUILD:include:src/infoembed.js@*/
/*@BUILD:include:src/chrome.js@*/
</script>
</body>
</html>

129
info-micro.html Normal file
View file

@ -0,0 +1,129 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VARASYS PM_P1 Practice — purpose, dimensions &amp; bill of materials</title>
<meta name="description" content="PM_P1 Practice — a long, narrow inline practice bar: one clickable thumbroller, amber 14segment display, analog instrument passthrough with the click mixed in. Dimensions and a priced BOM." />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
--panel-bg:#161b22; --panel-bd:#2a313c; --field-bg:#0e1116; --field-bd:#2a313c; --silk:#aab2bc; }
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
--panel-bg:#ffffff; --panel-bd:#d2dae4; --field-bg:#f1f4f8; --field-bd:#d2dae4; }
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt);
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); }
a{ color:var(--link); }
main{ width:100%; max-width:980px; margin:0 auto; }
.info-hero{ text-align:center; padding:16px 8px 2px; }
.info-hero h1{ font-size:clamp(24px,5vw,36px); margin:0; letter-spacing:-.01em; }
.info-hero .sub{ margin:9px auto 0; max-width:64ch; font-size:14.5px; }
</style>
</head>
<body>
/*@BUILD:include:src/header.html@*/
<main>
<section class="info-hero">
<h1>PM_P1 Practice</h1>
<p class="sub">A long, narrow inline practice bar — patch it into your signal, drive everything from one clickable thumbroller, and read tempo and track names off an amber 14segment display.</p>
</section>
/*@BUILD:include:src/infoembed.html@*/
<section class="about">
<h2>What it is</h2>
<div class="ff-tags"><span class="hw">Hardware</span><span>Inline practice bar</span><span>~$35 oneoff</span></div>
<p>A long, narrow practice bar you patch <i>into</i> your signal: instrument in one end, amp or headphones out
the other, the click mixed in. One clickable thumbroller does everything (roll = tempo, press = start/stop,
hold + roll = switch track), and an amber 14segment display shows tempo and track names.</p>
<p>The click is summed into your signal in the <b>analog domain</b> (plus a small monitor speaker). Powered over
USBC — a wall adapter for a permanent practicespace install, or a pocket power bank when you're mobile (no
internal battery to wear out); ships with the editor's grooves built in.</p>
</section>
<div class="dview">
<p class="cap">Dimensions &amp; layout — ≈ 6.3 × 1.4 × 1.0 in (160 × 36 × 26 mm), an extruded bar</p>
<div class="drow">
<div class="dvy">↕ 1.4 in<br>(36 mm)</div>
<div class="dschem" style="height:70px">
<span class="scap">Front (top face)</span>
<div class="scr" style="left:9%; top:24px; width:42%; height:30px"></div>
<div class="ctl" style="left:60%; top:21px; width:36px; height:36px; border-radius:8px"></div>
<div class="jl" style="left:9%; bottom:5px">14seg display</div>
<div class="jl" style="left:58%; bottom:5px">thumbroller</div>
</div>
</div>
<div class="dvx">↔ 6.3 in (160 mm) long</div>
<div class="drow" style="margin-top:12px; gap:14px">
<div style="flex:1">
<div class="dschem" style="height:64px">
<span class="scap">Left end</span>
<div class="jk" style="left:calc(50% - 6px); top:26px"></div>
<div class="jl" style="left:0; right:0; bottom:5px">TRS In (instrument)</div>
</div>
</div>
<div style="flex:1">
<div class="dschem" style="height:64px">
<span class="scap">Right end</span>
<div class="jk u" style="left:30%; top:29px"></div>
<div class="jk" style="left:58%; top:26px"></div>
<div class="jl" style="left:0; right:0; bottom:5px">USBC · TRS Out</div>
</div>
</div>
</div>
<div class="dvx" style="margin-left:0">↕ ends ≈ 1.0 in (26 mm) deep</div>
</div>
<details class="spec" open>
<summary>Spec &amp; bill of materials</summary>
<div class="spec-body">
<p class="sub">Rough parts list — a USBCpowered RP2040 inline bar with analog click injection.
Ballpark oneoff prices (USD); cheaper at volume.</p>
<table class="bom">
<thead><tr><th>Part</th><th class="q">Qty</th><th class="c">~$</th></tr></thead>
<tbody>
<tr class="grp"><td colspan="3">Brain &amp; display</td></tr>
<tr><td class="part">RP2040 board, USBC <span class="spec">— e.g. Waveshare RP2040Zero</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr><td class="part">4char 14segment alphanumeric LED + I²C driver <span class="spec">— amber; HT16K33. Shows BPM &amp; track names</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr class="grp"><td colspan="3">Control</td></tr>
<tr><td class="part">Clickable thumbroller <span class="spec">— EC11 encoder + roller wheel · roll / press / holdroll</span></td><td class="q">1</td><td class="c">2</td></tr>
<tr class="grp"><td colspan="3">Audio — analog click injection</td></tr>
<tr><td class="part">PCM5102A I²S DAC <span class="spec">— linelevel click</span></td><td class="q">1</td><td class="c">3</td></tr>
<tr><td class="part">Dual opamp, NE5532 / OPA2134 <span class="spec">— hiZ instrument buffer + summing mixer</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr><td class="part">PAM8302A mono ClassD + 8 Ω speaker <span class="spec">— monitor</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr class="grp"><td colspan="3">Connectors &amp; power</td></tr>
<tr><td class="part">1/4″ jack <span class="spec">— Inst In (TS) · Out (TRS)</span></td><td class="q">2</td><td class="c">2</td></tr>
<tr><td class="part">USBC bus power (5 V) + PWR LED <span class="spec">— wall adapter or power bank; also carries config</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr class="grp"><td colspan="3">Build</td></tr>
<tr><td class="part">Custom PCB (or perfboard)</td><td class="q">1</td><td class="c">4</td></tr>
<tr><td class="part">Passives, headers, wire <span class="spec">— R/C for the analog stage + decoupling</span></td><td class="q"></td><td class="c">2</td></tr>
<tr><td class="part">Extruded aluminium bar enclosure + end caps <span class="spec">— beadblasted, matteblack anodised</span></td><td class="q">1</td><td class="c">8</td></tr>
<tr class="total"><td>Total (oneoff)</td><td class="q"></td><td class="c">≈ $35</td></tr>
</tbody>
</table>
</div>
</details>
<p class="sub" style="max-width:760px;margin:14px auto 0">Embed this widget elsewhere with one <code>&lt;div&gt;</code> + a script —
see <a href="/embed.html">the embed docs</a>.</p>
</main>
/*@BUILD:include:src/footer.html@*/
<script>
const APP_VERSION = "v0.0.1-dev";
window.INFO_DEVICE = { file:"/micro.html", name:"PM_P1 Practice" };
/*@BUILD:include:src/infoembed.js@*/
/*@BUILD:include:src/chrome.js@*/
</script>
</body>
</html>

68
info-player.html Normal file
View file

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VARASYS PM_C1 Concept — the idealized player</title>
<meta name="description" content="PM_C1 Concept — the idealized, screenfirst player render: full setlist navigation, a colour beat display of every lane, theming and a fullscreen stage view. The buildable realizations are the Teacher and Practice units." />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
--panel-bg:#161b22; --panel-bd:#2a313c; --field-bg:#0e1116; --field-bd:#2a313c; --silk:#aab2bc; }
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
--panel-bg:#ffffff; --panel-bd:#d2dae4; --field-bg:#f1f4f8; --field-bd:#d2dae4; }
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt);
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); }
a{ color:var(--link); }
main{ width:100%; max-width:980px; margin:0 auto; }
.info-hero{ text-align:center; padding:16px 8px 2px; }
.info-hero h1{ font-size:clamp(24px,5vw,36px); margin:0; letter-spacing:-.01em; }
.info-hero .sub{ margin:9px auto 0; max-width:64ch; font-size:14.5px; }
</style>
</head>
<body>
/*@BUILD:include:src/header.html@*/
<main>
<section class="info-hero">
<h1>PM_C1 Concept</h1>
<p class="sub">The idealized concept render — a clean, screenfirst player with a colour beat display, setlist navigation, theming and a fullscreen landscape view.</p>
</section>
/*@BUILD:include:src/infoembed.html@*/
<section class="about">
<h2>What it is</h2>
<div class="ff-tags"><span>Concept</span><span>Idealized device</span><span>Not buildable as drawn</span></div>
<p>The idealized concept (PM_C1): the player as a clean, screenfirst device with no concession to mechanical parts yet.
It's the look we design <i>toward</i> — full setlist navigation, a colour beat display showing every lane,
light/dark theming, and a fullscreen landscape "stage" view. It runs the same engine and program strings as
everything else in the family, but as an <i>idealized</i> object, before deciding which buttons, encoders,
jacks and enclosure actually make it real.</p>
<p>Because it's a concept, there's <b>no bill of materials</b> — there's nothing to source for a render. The
buildable realization of this idea is the <a href="/info-teacher.html">PM_T1 Teacher</a> (full priced BOM there);
for the smallest practical unit, see the <a href="/info-micro.html">PM_P1 Practice</a>.</p>
</section>
<p class="sub" style="max-width:760px;margin:14px auto 0">Embed this widget elsewhere with one <code>&lt;div&gt;</code> + a script —
see <a href="/embed.html">the embed docs</a>.</p>
</main>
/*@BUILD:include:src/footer.html@*/
<script>
const APP_VERSION = "v0.0.1-dev";
window.INFO_DEVICE = { file:"/player.html", name:"PM_C1 Concept" };
/*@BUILD:include:src/infoembed.js@*/
/*@BUILD:include:src/chrome.js@*/
</script>
</body>
</html>

74
info-pm_e-2.html Normal file
View file

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VARASYS PM_E2 PolyMeter Editor (Notation) — what it is</title>
<meta name="description" content="PM_E2 PolyMeter Editor — the second-generation web workbench built around proper engraved drum notation: a 5-line percussion staff in the Bravura (SMuFL) music font, with Staff / TUBS / Konnakol views, edit-on-staff, and the full vocabulary — accents, ghosts, flams, drags, rolls, odd meters, polyrhythm and clave. Same share-language program string as every form factor." />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
--panel-bg:#161b22; --panel-bd:#2a313c; --field-bg:#0e1116; --field-bd:#2a313c; --silk:#aab2bc; }
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
--panel-bg:#ffffff; --panel-bd:#d2dae4; --field-bg:#f1f4f8; --field-bd:#d2dae4; }
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt);
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); }
a{ color:var(--link); }
main{ width:100%; max-width:980px; margin:0 auto; }
.info-hero{ text-align:center; padding:16px 8px 2px; }
.info-hero h1{ font-size:clamp(24px,5vw,36px); margin:0; letter-spacing:-.01em; }
.info-hero .sub{ margin:9px auto 0; max-width:64ch; font-size:14.5px; }
</style>
</head>
<body>
/*@BUILD:include:src/header.html@*/
<main>
<section class="info-hero">
<h1>PM_E2 PolyMeter Editor — Notation</h1>
<p class="sub">The second-generation editor, built around <b>proper engraved drum notation</b>. Design a groove and read it as a real percussion staff — the same program string still plays identically on every form factor.</p>
</section>
/*@BUILD:include:src/infoembed.html@*/
<section class="about">
<h2>What it is</h2>
<div class="ff-tags"><span>Web app</span><span>Engraved notation</span><span>Runs in any browser</span></div>
<p>Where the first-generation <a href="/info-editor.html">PM_E1 editor</a> is built around a step-pad grid,
<b>PM_E2 is built around notation</b>. Grooves render on a 5-line percussion staff engraved with the
<b>Bravura</b> music font (the open <a href="https://www.smufl.org/" target="_blank" rel="noopener">SMuFL</a>
standard used by professional scoring apps): real noteheads (X for cymbals, filled for drums), stems up for
hands and down for feet, group-aware beaming, <b>accents</b>, <b>ghost notes</b> in parentheses, and the full
ornament vocabulary — <b>flams, drags and rolls</b>. You can <b>edit directly on the staff</b>: click a step to
cycle its dynamic, Shift-click to cycle its ornament.</p>
<p>Three views share one engine: the <b>Staff</b>, a <b>TUBS</b> grid (Time Unit Box System — the clearest way to
read odd meters, polyrhythms and West-African bell patterns at a glance), and a <b>Konnakol</b> syllable view
(South-Indian solkattu — “ta&nbsp;ka&nbsp;di&nbsp;mi”) for spoken rhythm. Clave patterns are detected and labelled
(2-3 / 3-2). It's zero-install, runs in any modern browser, and works fully offline.</p>
<p>Everything you design saves to the same compact <b>program string</b> in the shared share-language, so a groove
built here loads into the original editor, the hardware concepts, or an embedded widget and plays back identically
everywhere.</p>
</section>
<p class="sub" style="max-width:760px;margin:14px auto 0">Embed the editor (or any device) elsewhere with one <code>&lt;div&gt;</code> + a script —
see <a href="/embed.html">the embed docs</a>.</p>
</main>
/*@BUILD:include:src/footer.html@*/
<script>
const APP_VERSION = "v0.0.1-dev";
window.INFO_DEVICE = { file:"/pm_e-2.html", name:"PM_E2 PolyMeter Editor" };
/*@BUILD:include:src/infoembed.js@*/
/*@BUILD:include:src/chrome.js@*/
</script>
</body>
</html>

123
info-showcase.html Normal file
View file

@ -0,0 +1,123 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VARASYS PM_D1 Display — purpose, dimensions &amp; bill of materials</title>
<meta name="description" content="PM_D1 Display — a pyramid displaypiece metronome whose pendulum is an RGB light bar combining every lane's subdivisions and accents, with a printed tempo scale and sliding weight. Dimensions and a priced BOM." />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
--panel-bg:#161b22; --panel-bd:#2a313c; --field-bg:#0e1116; --field-bd:#2a313c; --silk:#aab2bc; }
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
--panel-bg:#ffffff; --panel-bd:#d2dae4; --field-bg:#f1f4f8; --field-bd:#d2dae4; }
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt);
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); }
a{ color:var(--link); }
main{ width:100%; max-width:980px; margin:0 auto; }
.info-hero{ text-align:center; padding:16px 8px 2px; }
.info-hero h1{ font-size:clamp(24px,5vw,36px); margin:0; letter-spacing:-.01em; }
.info-hero .sub{ margin:9px auto 0; max-width:64ch; font-size:14.5px; }
</style>
</head>
<body>
/*@BUILD:include:src/header.html@*/
<main>
<section class="info-hero">
<h1>PM_D1 Display</h1>
<p class="sub">A displaypiece metronome — the pendulum is an RGB light bar that combines every lane's subdivisions &amp; accents; a printed tempo scale with a sliding weight sets the tempo.</p>
</section>
/*@BUILD:include:src/infoembed.html@*/
<section class="about">
<h2>What it is</h2>
<div class="ff-tags"><span class="hw">Hardware</span><span>Display piece</span><span>~$41 oneoff</span></div>
<p>A metronome as an object: the silhouette of a classic pyramid windup unit, but the swinging pendulum is
pure <b>RGB light</b>. The whole bar is the display — every lane's subdivisions &amp; accents ride along its
length as moving points of light (all meters combined), a printed tempo scale runs up the vertical axis,
and a sliding <b>weight</b> sets the tempo just like the mechanical original.</p>
<p>It's a beautiful, glanceable tempo reference for the shelf, the studio, or a shop window: accents glow
amber, normal steps cyan, ghosts soft violet, and the pendulum eases to each beat exactly as a weighted rod
would. It runs the same grooves as everything else (load any program string), plays the click through a
small speaker, and is powered over USBC with a second "thru" port to daisychain. There's no power switch —
the real unit starts when you lift it from its holder / set it swinging. No instrument I/O; it's a showpiece.</p>
</section>
<div class="dview">
<p class="cap">Dimensions &amp; profile — ≈ 4.7 × 7.1 × 3.1 in (120 × 180 × 80 mm), a truncatedpyramid plinth</p>
<div class="drow">
<div class="dvy">↕ 7.1 in (180 mm)</div>
<div class="dschem" style="height:184px">
<span class="scap">Front</span>
<div style="position:absolute; inset:6px; clip-path:polygon(34% 2%, 66% 2%, 93% 98%, 7% 98%);
background:linear-gradient(180deg,#2c2e34,#15171b); border:1px solid #33363c;"></div>
<div style="position:absolute; left:calc(50% - 4px); top:24px; width:8px; height:120px; border-radius:4px;
background:linear-gradient(180deg,#33d0ff,#178fb0); box-shadow:0 0 10px #33d0ff;"></div>
<div class="jl" style="left:0; right:0; bottom:5px">RGBlight pendulum bar + tempo scale</div>
</div>
</div>
<div class="dvx">↔ 4.7 in (120 mm) base</div>
<div class="drow" style="margin-top:12px">
<div class="dvy">↕ 7.1 in (180 mm)</div>
<div class="dschem" style="height:184px; max-width:200px">
<span class="scap">Side</span>
<div style="position:absolute; inset:6px; clip-path:polygon(40% 2%, 60% 2%, 86% 98%, 14% 98%);
background:linear-gradient(180deg,#26282d,#141519); border:1px solid #33363c;"></div>
<div class="jk u" style="left:calc(50% - 7px); bottom:14px; top:auto"></div>
<div class="jl" style="left:0; right:0; bottom:1px">USBC in base</div>
</div>
</div>
<div class="dvx">↔ 3.1 in (80 mm) deep</div>
</div>
<details class="spec" open>
<summary>Spec &amp; bill of materials</summary>
<div class="spec-body">
<p class="sub">Rough parts list — a USBCpowered RP2040 display piece driving addressable RGB light.
Ballpark oneoff prices (USD); cheaper at volume.</p>
<table class="bom">
<thead><tr><th>Part</th><th class="q">Qty</th><th class="c">~$</th></tr></thead>
<tbody>
<tr class="grp"><td colspan="3">Brain</td></tr>
<tr><td class="part">RP2040 board, USBC <span class="spec">— e.g. Waveshare RP2040Zero</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr class="grp"><td colspan="3">RGB light</td></tr>
<tr><td class="part">Addressable RGB LEDs (WS2812B) <span class="spec">— a strip down the pendulum bar, ~40 px</span></td><td class="q">1</td><td class="c">5</td></tr>
<tr><td class="part">Frosted acrylic diffuser / lightguide <span class="spec">— the glowing pendulum bar</span></td><td class="q">1</td><td class="c">3</td></tr>
<tr class="grp"><td colspan="3">Audio</td></tr>
<tr><td class="part">MAX98357A I²S amp + small speaker <span class="spec">— the click</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr class="grp"><td colspan="3">Power &amp; build</td></tr>
<tr><td class="part">2× USBC (data+power &amp; powerthru) + PWR LED <span class="spec">— daisychain</span></td><td class="q">1</td><td class="c">3</td></tr>
<tr><td class="part">Tilt / lift sensor (accelerometer) <span class="spec">— starts when lifted from its holder</span></td><td class="q">1</td><td class="c">2</td></tr>
<tr><td class="part">Custom PCB (or perfboard)</td><td class="q">1</td><td class="c">4</td></tr>
<tr><td class="part">Passives, wire</td><td class="q"></td><td class="c">2</td></tr>
<tr><td class="part">Pyramid enclosure <span class="spec">— cast/CNC aluminium or hardwood, frosted front panel</span></td><td class="q">1</td><td class="c">14</td></tr>
<tr class="total"><td>Total (oneoff)</td><td class="q"></td><td class="c">≈ $41</td></tr>
</tbody>
</table>
</div>
</details>
<p class="sub" style="max-width:760px;margin:14px auto 0">Embed this widget elsewhere with one <code>&lt;div&gt;</code> + a script —
see <a href="/embed.html">the embed docs</a>.</p>
</main>
/*@BUILD:include:src/footer.html@*/
<script>
const APP_VERSION = "v0.0.1-dev";
window.INFO_DEVICE = { file:"/showcase.html", name:"PM_D1 Display" };
/*@BUILD:include:src/infoembed.js@*/
/*@BUILD:include:src/chrome.js@*/
</script>
</body>
</html>

130
info-stage.html Normal file
View file

@ -0,0 +1,130 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VARASYS PM_S1 Stage — purpose, dimensions &amp; bill of materials</title>
<meta name="description" content="PM_S1 Stage — a footpedal polymeter stompbox: handsfree footswitches, expressionpedal tempo, a floorreadable RGB beat light, analog instrument passthrough. Dimensions and a priced BOM." />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
--panel-bg:#161b22; --panel-bd:#2a313c; --field-bg:#0e1116; --field-bd:#2a313c; --silk:#aab2bc; }
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
--panel-bg:#ffffff; --panel-bd:#d2dae4; --field-bg:#f1f4f8; --field-bd:#d2dae4; }
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt);
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); }
a{ color:var(--link); }
main{ width:100%; max-width:980px; margin:0 auto; }
.info-hero{ text-align:center; padding:16px 8px 2px; }
.info-hero h1{ font-size:clamp(24px,5vw,36px); margin:0; letter-spacing:-.01em; }
.info-hero .sub{ margin:9px auto 0; max-width:64ch; font-size:14.5px; }
</style>
</head>
<body>
/*@BUILD:include:src/header.html@*/
<main>
<section class="info-hero">
<h1>PM_S1 Stage</h1>
<p class="sub">A footpedal polymeter stompbox — handsfree footswitches, expressionpedal tempo, a floorreadable RGB beat light, and analog instrument passthrough with the click mixed in.</p>
</section>
/*@BUILD:include:src/infoembed.html@*/
<section class="about">
<h2>What it is</h2>
<div class="ff-tags"><span class="hw">Hardware</span><span>Footpedal stompbox</span><span>~$52 oneoff</span></div>
<p>A footoperated polymeter stompbox for the stage: drive it handsfree with two heavy footswitches and an
expression pedal, read it off the floor from the big RGB beat light, and run your instrument through it with
the click mixed in. (For a desk/lesson unit with a full screen, see the <a href="/info-teacher.html">Teacher</a>.)</p>
<p>The controls are built for feet: the <b>left footswitch</b> taps tempo (hold to start/stop), the <b>right</b>
steps through your set list (hold for previous), and a <b>1/4″ expressionpedal input</b> sweeps tempo on the
fly. Your instrument passes through (1/4″ in) with the click summed in the <b>analog domain</b> and sent to a
balanced 1/4″ TRS out. Powered over USBC — with a second USBC <b>"thru"</b> port so several pedals
daisychain off one charger or power bank.</p>
</section>
<div class="dview">
<p class="cap">Dimensions &amp; layout — ≈ 4.7 × 3.7 × 1.5 in (120 × 93 × 38 mm), a 1590BBstyle stompbox</p>
<div class="drow">
<div class="dvy">↕ 3.7 in (93 mm)</div>
<div class="dschem" style="height:150px">
<span class="scap">Front</span>
<div class="scr" style="left:18%; right:18%; top:22px; height:34px"></div>
<div class="ctl" style="left:calc(50% - 15px); top:64px; width:30px; height:30px"></div>
<div class="ctl" style="left:20%; top:100px; width:34px; height:34px"></div>
<div class="ctl" style="left:calc(80% - 34px); top:100px; width:34px; height:34px"></div>
<div class="jl" style="left:0; right:0; bottom:5px">angled TFT · RGB beat light · Tap + Next footswitches</div>
</div>
</div>
<div class="dvx">↔ 4.7 in (120 mm) wide</div>
<div class="drow" style="margin-top:12px">
<div class="dvy">↕ 1.5 in (38 mm)</div>
<div class="dschem" style="height:56px">
<span class="scap">Top edge — I/O</span>
<div class="jk" style="left:7%; top:18px"></div><div class="jk" style="left:22%; top:18px"></div>
<div class="jk" style="left:37%; top:18px"></div><div class="jk" style="left:52%; top:18px"></div>
<div class="jk u" style="left:68%; top:21px"></div><div class="jk u" style="left:83%; top:21px"></div>
<div class="jl" style="left:0; right:0; bottom:4px">Trig · Inst In · Out TRS · Exp · USBC · USBC thru</div>
</div>
</div>
<div class="dvx">↔ 4.7 in (120 mm)</div>
</div>
<details class="spec" open>
<summary>Spec &amp; bill of materials</summary>
<div class="spec-body">
<p class="sub">Rough parts list — a footoperated RP2040 stompbox (USBC, dualport) with analog click injection.
Ballpark oneoff prices (USD); cheaper at volume.</p>
<table class="bom">
<thead><tr><th>Part</th><th class="q">Qty</th><th class="c">~$</th></tr></thead>
<tbody>
<tr class="grp"><td colspan="3">Brain &amp; display</td></tr>
<tr><td class="part">RP2040 board, USBC <span class="spec">— e.g. Waveshare RP2040Zero</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr><td class="part">1.3″ IPS TFT, ST7789 <span class="spec">— SPI; angled BPM / item readout</span></td><td class="q">1</td><td class="c">6</td></tr>
<tr><td class="part">Highbright diffused RGB beat indicator <span class="spec">— floorreadable</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr class="grp"><td colspan="3">Controls</td></tr>
<tr><td class="part">Heavyduty momentary footswitch (softtouch) <span class="spec">— Tap · Next</span></td><td class="q">2</td><td class="c">6</td></tr>
<tr><td class="part">1/4″ expressionpedal input jack (TRS) <span class="spec">— tempo sweep</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr class="grp"><td colspan="3">Audio — analog click injection</td></tr>
<tr><td class="part">PCM5102A I²S DAC <span class="spec">— linelevel click</span></td><td class="q">1</td><td class="c">3</td></tr>
<tr><td class="part">Dual opamp, NE5532 / OPA2134 <span class="spec">— hiZ instrument buffer + summing mixer</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr><td class="part">Balanced line driver, DRV134 <span class="spec">— → 1/4″ TRS out</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr class="grp"><td colspan="3">Connectors &amp; power</td></tr>
<tr><td class="part">1/4″ jack <span class="spec">— Inst In (TS) · Out (TRS) · Trig In (TS)</span></td><td class="q">3</td><td class="c">3</td></tr>
<tr><td class="part">2× USBC (data+power &amp; powerthru) + powerpath/protection + PWR LED <span class="spec">— daisychain pedals</span></td><td class="q">1</td><td class="c">3</td></tr>
<tr class="grp"><td colspan="3">Build</td></tr>
<tr><td class="part">Custom PCB (or perfboard)</td><td class="q">1</td><td class="c">5</td></tr>
<tr><td class="part">Passives, headers, wire <span class="spec">— R/C for the analog stage + decoupling</span></td><td class="q"></td><td class="c">3</td></tr>
<tr><td class="part">Diecast aluminium stompbox (Hammond 1590BBstyle) <span class="spec">— beadblasted, matteblack Type II anodise, laseretched</span></td><td class="q">1</td><td class="c">12</td></tr>
<tr class="total"><td>Total (oneoff)</td><td class="q"></td><td class="c">≈ $52</td></tr>
</tbody>
</table>
<p class="sub" style="margin-top:12px">No builtin speaker — the Stage feeds your amp / PA. The click is summed in
the <b>analog domain</b> (hiZ instrument buffer + DAC → balanced line driver), so your instrument is never
redigitised (no added latency).</p>
</div>
</details>
<p class="sub" style="max-width:760px;margin:14px auto 0">Embed this widget elsewhere with one <code>&lt;div&gt;</code> + a script —
see <a href="/embed.html">the embed docs</a>.</p>
</main>
/*@BUILD:include:src/footer.html@*/
<script>
const APP_VERSION = "v0.0.1-dev";
window.INFO_DEVICE = { file:"/stage.html", name:"PM_S1 Stage" };
/*@BUILD:include:src/infoembed.js@*/
/*@BUILD:include:src/chrome.js@*/
</script>
</body>
</html>

105
info-teacher.html Normal file
View file

@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VARASYS PM_T1 Teacher — purpose, spec &amp; bill of materials</title>
<meta name="description" content="PM_T1 Teacher — a fullfeature studio / lesson desk console: a colour TFT of every lane, arcade buttons, a thumbroller, and analog instrument passthrough with the click mixed in. Spec and a priced BOM." />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
--panel-bg:#161b22; --panel-bd:#2a313c; --field-bg:#0e1116; --field-bd:#2a313c; --silk:#aab2bc; }
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
--panel-bg:#ffffff; --panel-bd:#d2dae4; --field-bg:#f1f4f8; --field-bd:#d2dae4; }
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt);
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); }
a{ color:var(--link); }
main{ width:100%; max-width:980px; margin:0 auto; }
.info-hero{ text-align:center; padding:16px 8px 2px; }
.info-hero h1{ font-size:clamp(24px,5vw,36px); margin:0; letter-spacing:-.01em; }
.info-hero .sub{ margin:9px auto 0; max-width:64ch; font-size:14.5px; }
</style>
</head>
<body>
/*@BUILD:include:src/header.html@*/
<main>
<section class="info-hero">
<h1>PM_T1 Teacher</h1>
<p class="sub">The fullfeature studio / lesson desk console — a colour TFT showing every lane, arcade buttons and a thumbroller, with your instrument running through and the click mixed in.</p>
</section>
/*@BUILD:include:src/infoembed.html@*/
<section class="about">
<h2>What it is</h2>
<div class="ff-tags"><span class="hw">Hardware</span><span>Studio / lesson console</span><span>~$59 oneoff</span></div>
<p>The fullfeature desktop console: a colour readout of every lane, fast setlist navigation, and your
instrument running straight through with the click mixed in — the handson unit for a studio desk or a
teaching room, on a nonreflective matteblack case. (For handsfree live use, see the footoperated
<a href="/info-stage.html">Stage</a> stompbox.)</p>
<p>Topmounted 1/4″ jacks keep cabling tidy; the metronome click is summed into the signal in the
<b>analog domain</b> (no redigitising, no added latency) and sent to a balanced 1/4″ TRS output for the
desk or interface, plus a small monitor speaker. Powered over USBC — a wall adapter or a power bank. The
colour TFT shows tempo, the item name and all lane patterns; arcade buttons + a recessed thumbroller make
it quick to drive while you teach or track.</p>
</section>
<details class="spec" open>
<summary>Spec &amp; bill of materials</summary>
<div class="spec-body">
<p class="sub">Rough parts list — a desk/studio RP2040 build (USBC powered) with analog click injection.
Ballpark oneoff prices (USD); cheaper at volume.</p>
<table class="bom">
<thead><tr><th>Part</th><th class="q">Qty</th><th class="c">~$</th></tr></thead>
<tbody>
<tr class="grp"><td colspan="3">Brain &amp; display</td></tr>
<tr><td class="part">RP2040 board, USBC <span class="spec">— e.g. Waveshare RP2040Zero / Picoclone</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr><td class="part">2.0″ 320×240 IPS TFT, ST7789 <span class="spec">— SPI</span></td><td class="q">1</td><td class="c">8</td></tr>
<tr class="grp"><td colspan="3">Controls</td></tr>
<tr><td class="part">Arcade pushbutton, 24 mm <span class="spec">— Prev · Next · Tap</span></td><td class="q">3</td><td class="c">4</td></tr>
<tr><td class="part">Arcade pushbutton, 30 mm <span class="spec">— Play</span></td><td class="q">1</td><td class="c">2</td></tr>
<tr><td class="part">Detented encoder (EC11 / PEC12) + sidemount thumbroller <span class="spec">— recessed; nothing to snap off</span></td><td class="q">1</td><td class="c">2</td></tr>
<tr class="grp"><td colspan="3">Audio — analog click injection</td></tr>
<tr><td class="part">PCM5102A I²S DAC <span class="spec">— linelevel click</span></td><td class="q">1</td><td class="c">3</td></tr>
<tr><td class="part">Dual opamp, NE5532 / OPA2134 <span class="spec">— hiZ instrument buffer + summing mixer</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr><td class="part">Balanced line driver, DRV134 <span class="spec">— (or crosscoupled opamp) → 1/4″ TRS out</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr><td class="part">PAM8302A mono ClassD + 8 Ω 2 W speaker <span class="spec">— monitor</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr class="grp"><td colspan="3">Connectors &amp; power</td></tr>
<tr><td class="part">1/4″ jack <span class="spec">— Inst In (TS) · Out (TRS) · Trig In (TS)</span></td><td class="q">3</td><td class="c">3</td></tr>
<tr><td class="part">USBC bus power (5 V) + PWR LED <span class="spec">— wall adapter or power bank; same port carries config; no battery</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr class="grp"><td colspan="3">Build</td></tr>
<tr><td class="part">Custom PCB (or perfboard)</td><td class="q">1</td><td class="c">5</td></tr>
<tr><td class="part">Passives, headers, wire <span class="spec">— R/C for the analog stage + decoupling</span></td><td class="q"></td><td class="c">3</td></tr>
<tr><td class="part">Diecast aluminium enclosure (Hammond 1590style) <span class="spec">— beadblasted, matteblack Type II anodise, laseretched legends</span></td><td class="q">1</td><td class="c">12</td></tr>
<tr class="total"><td>Total (oneoff)</td><td class="q"></td><td class="c">≈ $56</td></tr>
</tbody>
</table>
<p class="sub" style="margin-top:12px">Audio is summed in the <b>analog domain</b>: the DAC's click is mixed with a
highimpedance buffer of the 1/4″ instrument input, then fed to the balanced line driver (1/4″ TRS out) and the
monitor amp — so your instrument is never redigitised (no added latency).</p>
</div>
</details>
<p class="sub" style="max-width:760px;margin:14px auto 0">Embed this widget elsewhere with one <code>&lt;div&gt;</code> + a script —
see <a href="/embed.html">the embed docs</a>.</p>
</main>
/*@BUILD:include:src/footer.html@*/
<script>
const APP_VERSION = "v0.0.1-dev";
window.INFO_DEVICE = { file:"/teacher.html", name:"PM_T1 Teacher" };
/*@BUILD:include:src/infoembed.js@*/
/*@BUILD:include:src/chrome.js@*/
</script>
</body>
</html>

307
kit.html Normal file
View file

@ -0,0 +1,307 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>VARASYS PM_K1 — Kit (Raspberry Pi Pico touchscreen build)</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
/* ?embed=1 → strip site chrome + auto-size to the host */
(function(){ if(!/[?&]embed=1/.test(location.search)) return;
document.documentElement.dataset.embed="1";
function ph(){ try{ parent.postMessage({type:"varasys-h",h:Math.ceil(document.documentElement.getBoundingClientRect().height)},"*"); }catch(e){} }
addEventListener("load",ph); addEventListener("resize",ph); setTimeout(ph,300); setTimeout(ph,1000);
})();
</script>
<!--
PM_K-1 "Kit" — the buildable touchscreen unit: a Raspberry Pi Pico on the 52Pi EP-0172
breadboard kit (3.5" ST7796 320x480 cap-touch, GT911 touch, PSP joystick on ADC0/1,
WS2812 RGB on GP12, buzzer GP13, buttons GP14/15). This page mirrors the MicroPython
firmware's on-screen UI so the web simulator looks like the real device. Shares src/engine.js.
-->
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
--panel-bd:#2a313c; --device-bd:#33363c; --silk:#aab2bc; --cyan:#0AB3F7;
--panel-bg:#161b22; --field-bg:#0e1116; --field-bd:#2a313c; }
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
--panel-bd:#d2dae4; --panel-bg:#ffffff; --field-bg:#f1f4f8; --field-bd:#d2dae4 }
body{ margin:0; min-height:100vh; padding:26px 14px 46px;
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); color:var(--txt);
display:flex; flex-direction:column; align-items:center; gap:16px }
a{ color:var(--link) }
/* the kit: a Pico carrier board with the screen + joystick + RGB + buzzer + buttons */
.device{ width:100%; max-width:330px; position:relative; border-radius:16px; padding:14px 14px 18px;
background:
radial-gradient(rgba(255,255,255,.022) .5px, transparent .6px) 0 0/3px 3px,
linear-gradient(180deg, #1f3a2e, #0c2a20); /* 52Pi green PCB vibe */
border:1px solid #2d5c47;
box-shadow:0 26px 52px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.05) }
.pcbrow{ display:flex; align-items:center; justify-content:space-between; margin:0 2px 10px }
.dev-logo{ height:18px }
.silk{ display:flex; align-items:center; gap:7px; color:#bfe6d4 }
.silk .model{ font-size:8.5px; text-transform:uppercase; letter-spacing:.16em; opacity:.9 }
.pin{ font-size:7px; color:#9fd2bd; letter-spacing:.1em; text-transform:uppercase; opacity:.8 }
.screen-wrap{ padding:8px; border-radius:10px; background:linear-gradient(180deg,#05070a,#020406);
border:1px solid #04060a; box-shadow:inset 0 2px 10px rgba(0,0,0,.8) }
#screen{ display:block; width:100%; height:auto; border-radius:5px; background:#06080c; touch-action:manipulation; cursor:pointer }
.hw{ display:flex; align-items:center; justify-content:space-between; gap:10px; margin:14px 4px 0 }
/* PSP joystick */
.joy{ width:74px; height:74px; border-radius:50%; position:relative; flex:0 0 auto; touch-action:none; cursor:grab;
background:radial-gradient(circle at 40% 34%, #2a2f37, #0c0f13 72%); border:2px solid #0a3a2a;
box-shadow:inset 0 2px 6px rgba(0,0,0,.6) }
.joy .nub{ position:absolute; left:50%; top:50%; width:34px; height:34px; margin:-17px 0 0 -17px; border-radius:50%;
background:radial-gradient(circle at 38% 32%, #e9eef3, #aab2bc 46%, #6c7480 72%, #3b424c);
box-shadow:0 3px 6px rgba(0,0,0,.5); transition:transform .04s }
.joy .cap{ position:absolute; left:0; right:0; bottom:-15px; text-align:center; font-size:7px; color:#9fd2bd; letter-spacing:.08em; text-transform:uppercase; opacity:.85 }
.mids{ display:flex; flex-direction:column; align-items:center; gap:8px; flex:1 }
.led{ width:30px; height:30px; border-radius:50%; background:#0c0f14;
box-shadow:0 0 4px #000 inset; transition:none }
.led-cap, .buz-cap{ font-size:7px; color:#9fd2bd; letter-spacing:.1em; text-transform:uppercase; opacity:.85 }
.buz{ width:26px; height:26px; border-radius:50%; background:radial-gradient(circle at 50% 40%, #2a2f37, #0c0f13);
border:2px solid #0a3a2a; position:relative }
.buz::after{ content:""; position:absolute; left:50%; top:50%; width:6px; height:6px; margin:-3px 0 0 -3px; border-radius:50%; background:#05070a }
.btns{ display:flex; flex-direction:column; gap:9px; flex:0 0 auto }
.pbtn{ width:60px; padding:9px 0; border-radius:9px; border:1px solid #0a3a2a; cursor:pointer;
background:radial-gradient(circle at 40% 30%, #d7dde3, #8b939e 70%, #5a626c); color:#0c1116;
font-size:9px; font-weight:800; letter-spacing:.06em; text-transform:uppercase;
box-shadow:0 3px 5px rgba(0,0,0,.4) }
.pbtn:active{ transform:translateY(2px) }
.pbtn small{ display:block; font-size:6.5px; font-weight:600; opacity:.7 }
.hint{ max-width:330px; text-align:center; font-size:11px; color:var(--muted); line-height:1.5 }
[data-embed] .hint{ display:none !important }
</style>
</head>
<body>
/*@BUILD:include:src/header.html@*/
<h1 class="ff-title">PM_K1 Kit</h1>
<p class="ff-sum">The buildityourself touchscreen unit — a Raspberry Pi Pico on the 52Pi breadboard kit (3.5″ captouch, joystick, RGB, buzzer). Tap the screen, nudge tempo with the stick; runs the same program strings, with MicroPython firmware you flash yourself.</p>
<div class="device">
<div class="pcbrow">
<div class="silk"><span class="dev-lock"><img class="dev-logo" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS — Simplifying Complexity" /></span><span class="model">PM_K1 Kit</span></div>
<span class="pin">Pico · USBC</span>
</div>
<div class="screen-wrap"><canvas id="screen" width="320" height="480" aria-label="touchscreen metronome"></canvas></div>
<div class="hw">
<div class="joy" id="joy" title="Joystick — up/down tempo · left/right groove"><div class="nub" id="nub"></div><span class="cap">Joystick</span></div>
<div class="mids">
<div class="led" id="led"></div><span class="led-cap">RGB</span>
<div class="buz"></div><span class="buz-cap">Buzzer</span>
</div>
<div class="btns">
<button class="pbtn" id="btnA">A<small>play</small></button>
<button class="pbtn" id="btnB">B<small>tap</small></button>
</div>
</div>
</div>
<div class="hint">Tap the onscreen buttons (the real unit is capacitive touch). The <b>joystick</b> sets tempo (up/down)
and switches grooves (left/right); <b>A</b> = play/stop, <b>B</b> = tap. The RGB LED flashes each beat and the buzzer clicks.</div>
/*@BUILD:include:src/progbox.html@*/
<p class="ff-link pageonly"><a href="/info-kit.html">Wiring, parts &amp; firmware to flash →</a></p>
<script>
const APP_VERSION = "v0.0.1-dev";
const $ = (id) => document.getElementById(id);
/* ========================= ENGINE (shared; synth voices only) ================= */
const SAMPLES = {};
/*@BUILD:include:src/engine.js@*/
/*@BUILD:include:src/setlists.js@*/
const state = { bpm:120, volume:0.85, running:false };
let meters = [], muteWindows = [];
function setBpm(v){ state.bpm = Math.max(30, Math.min(300, Math.round(v))); if(window.progRefresh) progRefresh(); }
function scheduler(){
const ahead = audioCtx.currentTime + SCHEDULE_AHEAD;
for(const m of meters){ while(m.nextTime < ahead){ scheduleMeterTick(m, m.nextTime); m.nextTime += laneStepDur(m, m.tick); m.tick++; } }
}
function buildMeters(lanes){
return (lanes||[]).map(c=>{ const p=parseGroups(c.groupsStr);
return {groupsStr:c.groupsStr,groups:p.groups,beatsPerBar:p.beatsPerBar,groupStarts:p.groupStarts,
stepsPerBeat:c.stepsPerBeat||1,sound:c.sound,beatsOn:(c.beatsOn||[]).slice(),poly:!!c.poly,swing:!!c.swing,enabled:c.enabled!==false,gainDb:c.gainDb||0,
tick:0,nextTime:0,vq:[],vqPtr:0,currentStep:-1,currentBar:0}; });
}
function startAudio(){
ensureAudio(); audioCtx.resume(); state.running=true;
const t0=audioCtx.currentTime+0.08;
for(const m of meters){ m.tick=0; m.nextTime=t0; m.vq=[]; m.vqPtr=0; m.currentStep=-1; }
muteWindows=[];
schedulerTimer=setInterval(scheduler,LOOKAHEAD_MS); scheduler();
}
function stopAudio(){ state.running=false; clearInterval(schedulerTimer); schedulerTimer=null; for(const m of meters) m.currentStep=-1; }
function toggle(){ state.running ? stopAudio() : startAudio(); }
/* ========================= TRACKS (seed grooves, flattened) =================== */
let tracks = SEED_SETLISTS.flatMap(sl => sl.items.map(([n,p]) => ({ name:n, ...patchToSetup(p) })));
let trackIdx = 0;
function tracksFromHash(){
const m=(location.hash||"").match(/[#&](p|sl)=([^&]+)/); if(!m) return null;
let payload=m[2]; try{ payload=decodeURIComponent(payload); }catch(e){}
try{
if(m[1]==="sl"){ const sl=codeToSetlist(payload); return sl.items.length ? sl.items.map(it=>({...it})) : null; }
const s=patchToSetup(payload); return s.lanes.length ? [{name:"Patch",...s}] : null;
}catch(e){ return null; }
}
function loadTrack(i){
const n=tracks.length; if(!n) return; trackIdx=((i%n)+n)%n;
const t=tracks[trackIdx]; setBpm(t.bpm||120); meters=buildMeters(t.lanes);
const was=state.running; if(was){ clearInterval(schedulerTimer); schedulerTimer=null; state.running=false; }
if(was) startAudio();
}
/* ========================= SCREEN (canvas mirrors the firmware UI) ============ */
const cv=$("screen"), g=cv.getContext("2d"), SW=320, SH=480;
(function(){ const dpr=Math.max(1,Math.min(3,window.devicePixelRatio||1)); cv.width=SW*dpr; cv.height=SH*dpr; g.scale(dpr,dpr); })();
const COL={ bg:"#06090e", txt:"#c7d0db", mute:"#6e7a8a", cyan:"#0AB3F7", amber:"#ff9b2e",
violet:"#967bff", green:"#2fe07a", dim:"#243240", btn:"#1c222c", panel:"#12161e" };
const PRIO={2:3,1:2,3:1};
const LEDCOL={2:[255,110,0],1:[0,150,255],3:[130,70,255]};
let flash=0, flashLevel=1, beatIdx=-1, btnRects=[];
function rrect(x,y,w,h,r){ g.beginPath(); g.moveTo(x+r,y); g.arcTo(x+w,y,x+w,y+h,r); g.arcTo(x+w,y+h,x,y+h,r); g.arcTo(x,y+h,x,y,r); g.arcTo(x,y,x+w,y,r); g.closePath(); }
function layoutButtons(){
btnRects=[]; const bw=96, bh=54, gap=(SW-3*bw)/4, xs=[gap, gap*2+bw, gap*3+bw*2];
[["prev",300],["play",300],["next",300]].forEach((b,i)=>btnRects.push({x:xs[i],y:300,w:bw,h:bh,key:["prev","play","next"][i]}));
["minus","tap","plus"].forEach((k,i)=>btnRects.push({x:xs[i],y:370,w:bw,h:bh,key:k}));
}
layoutButtons();
function drawScreen(){
g.fillStyle=COL.bg; g.fillRect(0,0,SW,SH);
// header (the VARASYS logo lives on the case, not the screen)
g.textBaseline="alphabetic"; g.textAlign="left";
g.fillStyle=COL.cyan; g.font="700 18px 'Segoe UI',Roboto,Arial,sans-serif"; g.fillText("PM_K1 KIT",12,26);
g.fillStyle=COL.panel; g.fillRect(0,34,SW,2);
// BPM
g.textAlign="left"; g.fillStyle=COL.mute; g.font="700 20px 'Segoe UI',Roboto,Arial,sans-serif"; g.fillText("BPM",12,150);
g.textAlign="right"; g.fillStyle=COL.txt; g.font="800 92px 'Segoe UI',Roboto,Arial,sans-serif"; g.fillText(String(state.bpm),SW-12,176);
// beat dots
const m=meters[0]; if(m){ const bpb=m.beatsPerBar, sz=18, sp=26, x0=Math.max(12,SW-12-bpb*sp), y=196;
for(let i=0;i<bpb;i++){ const accent=m.groupStarts.has(i)||(m.beatsOn[i*m.stepsPerBeat]|0)>=2;
const on=state.running && i===beatIdx; g.fillStyle = on ? (accent?COL.amber:COL.cyan) : COL.dim;
g.fillRect(x0+i*sp,y,sz,sz); } }
// status
g.textAlign="left"; g.fillStyle=state.running?COL.green:COL.mute; g.font="700 16px 'Segoe UI',Roboto,Arial,sans-serif";
g.fillText(state.running?"▶ RUN":"■ STOP",12,256);
const nm=(tracks[trackIdx]&&tracks[trackIdx].name||"—").slice(0,18);
g.textAlign="right"; g.fillStyle=COL.txt; g.fillText(nm,SW-12,256);
g.textAlign="left"; g.fillStyle=COL.mute; g.font="600 11px 'Segoe UI',Roboto,Arial,sans-serif"; g.fillText((trackIdx+1)+"/"+tracks.length,12,276);
// touch buttons
const lbl={prev:"",play:state.running?"▮▮":"▶",next:"",minus:"",tap:"TAP",plus:"+"};
for(const b of btnRects){ g.fillStyle=COL.btn; rrect(b.x,b.y,b.w,b.h,9); g.fill();
g.fillStyle = b.key==="play" ? COL.green : COL.txt;
g.font = (b.key==="minus"||b.key==="plus") ? "800 30px 'Segoe UI',Roboto,Arial,sans-serif" : "800 22px 'Segoe UI',Roboto,Arial,sans-serif";
g.textAlign="center"; g.textBaseline="middle"; g.fillText(lbl[b.key], b.x+b.w/2, b.y+b.h/2+2); g.textBaseline="alphabetic"; }
g.textAlign="left"; g.fillStyle=COL.mute; g.font="600 10px 'Segoe UI',Roboto,Arial,sans-serif";
g.fillText("joystick: tempo / groove · A play B tap", 12, SH-14);
}
function audioLatency(){ return audioCtx ? (audioCtx.outputLatency || audioCtx.baseLatency || 0) : 0; }
function frame(){
const now = audioCtx ? audioCtx.currentTime - audioLatency() : 0;
if(audioCtx && state.running){
let fired=[];
for(const m of meters){
while(m.vqPtr<m.vq.length && m.vq[m.vqPtr].time<=now){
const e=m.vq[m.vqPtr]; m.currentStep=e.step; m.currentBar=e.bar;
const lvl=m.beatsOn[e.step]|0; if(lvl>0) fired.push(lvl);
if(m===meters[0] && e.step % m.stepsPerBeat===0) beatIdx = e.step/m.stepsPerBeat;
m.vqPtr++;
}
if(m.vqPtr>512){ m.vq=m.vq.slice(m.vqPtr); m.vqPtr=0; }
}
if(fired.length){ flashLevel = fired.sort((a,b)=>PRIO[b]-PRIO[a])[0]; flash=1; }
}
flash=Math.max(0,flash-0.08);
// RGB LED element
const c=LEDCOL[flashLevel]||LEDCOL[1], lit=flash;
const led=$("led");
if(lit>0.02){ const rgb="rgb("+c.map(v=>Math.round(v*lit)).join(",")+")";
led.style.background=rgb; led.style.boxShadow="0 0 "+(8+lit*22)+"px rgb("+c.join(",")+")"; }
else { led.style.background="#0c0f14"; led.style.boxShadow="0 0 4px #000 inset"; }
drawScreen();
requestAnimationFrame(frame);
}
/* ========================= INPUTS ============================================ */
function dispatch(key){
if(key==="play") toggle();
else if(key==="prev") loadTrack(trackIdx-1);
else if(key==="next") loadTrack(trackIdx+1);
else if(key==="minus") setBpm(state.bpm-1);
else if(key==="plus") setBpm(state.bpm+1);
else if(key==="tap") tapTempo();
}
cv.addEventListener("pointerdown",(e)=>{
const r=cv.getBoundingClientRect(); const x=(e.clientX-r.left)*SW/r.width, y=(e.clientY-r.top)*SH/r.height;
for(const b of btnRects){ if(x>=b.x&&x<=b.x+b.w&&y>=b.y&&y<=b.y+b.h){ dispatch(b.key); return; } }
});
let taps=[];
function tapTempo(){ const now=performance.now(); taps.push(now); taps=taps.filter(x=>now-x<2400);
if(taps.length>=2){ let s=0; for(let i=1;i<taps.length;i++) s+=taps[i]-taps[i-1];
const bpm=Math.round(60000/(s/(taps.length-1))); if(bpm>=30&&bpm<=300) setBpm(bpm); } }
$("btnA").addEventListener("click",()=>toggle());
$("btnB").addEventListener("click",()=>tapTempo());
/* joystick: drag up/down = tempo, left/right = groove */
(function(){
const joy=$("joy"), nub=$("nub"); let dragging=false, R=26, last=0, itemLatch=0;
function at(e){ const r=joy.getBoundingClientRect(); return {x:e.clientX-r.left-r.width/2, y:e.clientY-r.top-r.height/2}; }
function move(e){ if(!dragging) return; e.preventDefault();
let {x,y}=at(e); const d=Math.hypot(x,y)||1, k=Math.min(1,R/d); let nx=x*k, ny=y*k;
nub.style.transform="translate("+nx+"px,"+ny+"px)";
const fy=-ny/R, fx=nx/R, now=performance.now();
if(Math.abs(fy)>0.45 && now-last>80){ setBpm(state.bpm+(fy>0?1:-1)*(Math.abs(fy)>0.8?5:1)); last=now; }
if(Math.abs(fx)>0.6){ if(now-itemLatch>320){ loadTrack(trackIdx+(fx>0?1:-1)); itemLatch=now; } }
}
function up(){ dragging=false; nub.style.transform="translate(0,0)"; }
joy.addEventListener("pointerdown",(e)=>{ dragging=true; try{joy.setPointerCapture(e.pointerId);}catch(_){ } move(e); });
joy.addEventListener("pointermove",move);
joy.addEventListener("pointerup",up); joy.addEventListener("pointercancel",up);
})();
/* theme toggle + version */
/*@BUILD:include:src/chrome.js@*/
addEventListener("keydown",(e)=>{
const tag=e.target?e.target.tagName:""; if(tag==="INPUT"||tag==="TEXTAREA"||tag==="SELECT") return;
if(e.key===" "){ e.preventDefault(); toggle(); }
else if(e.key==="t"||e.key==="T"){ tapTempo(); }
else if(e.key==="ArrowUp"){ e.preventDefault(); setBpm(state.bpm+1); }
else if(e.key==="ArrowDown"){ e.preventDefault(); setBpm(state.bpm-1); }
else if(e.key==="ArrowRight"){ loadTrack(trackIdx+1); }
else if(e.key==="ArrowLeft"){ loadTrack(trackIdx-1); }
});
/* ========================= INIT ============================================== */
{ const ht=tracksFromHash(); if(ht) tracks=ht; }
loadTrack(0);
requestAnimationFrame(frame);
window.currentProgramString = function(){ var t=tracks[trackIdx]||{}; return setupToPatch({bpm:state.bpm, volume:state.volume, lanes:t.lanes||[]}); };
window.loadProgramString = function(plain){ var s=patchToSetup(plain); tracks=[{name:"Program", ...s}]; trackIdx=0; loadTrack(0); };
/*@BUILD:include:src/progbox.js@*/
</script>
/*@BUILD:include:src/footer.html@*/
</body>
</html>

353
micro.html Normal file
View file

@ -0,0 +1,353 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>VARASYS PM_P1 — Practice (inline bar)</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
/* ?embed=1 → strip site chrome (base.css [data-embed]) + auto-size to the host */
(function(){ if(!/[?&]embed=1/.test(location.search)) return;
document.documentElement.dataset.embed="1";
function ph(){ try{ parent.postMessage({type:"varasys-h",h:Math.ceil(document.documentElement.getBoundingClientRect().height)},"*"); }catch(e){} }
addEventListener("load",ph); addEventListener("resize",ph); setTimeout(ph,300); setTimeout(ph,1000);
})();
</script>
<!--
"Micro" — a long, narrow INLINE practice bar on the same RP2040 firmware.
Patch your instrument through it: 1/4" TRS in on one end; USB-C + 1/4" TRS out
on the other; powered over USB-C (wall adapter or power bank — no internal
battery). The click is summed into your signal in the ANALOG domain (and a
small speaker). Display is a 4-char amber 14-segment
(shows BPM *and* short track names). One control — a clickable thumb-ROLLER:
• roll → tempo
• press (click) → start / stop
• hold + roll → switch track (the display shows the track name)
Built-in tracks are the editor's seed grooves, flattened. Shares src/engine.js.
-->
<script>
// Set theme before first paint (shared "metronome.theme" with the other pages).
(function(){ try{
var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{
--bg1:#12151c; --bg2:#05070a;
--txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
--panel-bd:#2a313c;
--device-bd:#33363c; --silk:#aab2bc; --dmuted:#5a626c;
--cyan:#0AB3F7;
}
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4; --panel-bd:#d2dae4 }
body{ margin:0; min-height:100vh; padding:30px 14px 44px;
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); color:var(--txt);
display:flex; flex-direction:column; align-items:center; gap:16px }
a{ color:var(--link) }
.topbar{ width:100%; max-width:330px; display:flex; align-items:center; justify-content:space-between; gap:8px; font-size:12px; color:var(--muted); flex-wrap:wrap }
.topbar b{ color:var(--txt) }
.topbar-right{ display:flex; align-items:center; gap:10px }
.tbtn{ background:transparent; color:var(--muted); border:1px solid var(--panel-bd); border-radius:8px; padding:3px 9px; font-size:14px; line-height:1; cursor:pointer }
.tbtn:hover{ color:var(--txt) }
/* ---- the micro device: a long, narrow brushed-aluminium bar ----
The main body and the two end caps are SEPARATE pieces with a gap between
them, so the jacks read as being on the ENDS of the bar (not the front). */
.device{ width:100%; max-width:660px; display:flex; align-items:center; gap:15px; position:relative }
/* end caps — the extrusion's end faces, stood apart from the body; jacks exit here.
A touch shorter than the body (align-self:stretch + margin) so they look set-back. */
.endcap{ flex:0 0 auto; width:66px; align-self:stretch; margin:9px 0;
display:flex; flex-direction:column; align-items:center; justify-content:center; gap:11px;
padding:12px 7px; border-radius:10px; border:1px solid #292b30;
background:linear-gradient(180deg,#1b1d21,#0a0b0e); /* darker end-grain */
box-shadow:0 12px 24px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.04), inset 0 -2px 6px rgba(0,0,0,.55) }
.endcap.left{ border-right-color:#3b3e45 } /* chamfer highlight on the edge facing the body */
.endcap.right{ border-left-color:#3b3e45 }
.endlbl{ font-size:7px; color:var(--silk); text-transform:uppercase; letter-spacing:.1em; text-align:center; line-height:1.35; opacity:.8 }
.jk{ display:flex; flex-direction:column; align-items:center; gap:4px }
.jk i{ width:23px; height:23px; border-radius:50%; background:radial-gradient(circle at 40% 34%, #333a44, #07090c 72%);
border:2px solid #5b6470; box-shadow:inset 0 0 5px #000 }
.jk.usb i{ width:25px; height:10px; border-radius:4px; border:2px solid #5b6470; background:#07090c }
.jk b{ font-size:7px; font-weight:700; color:var(--silk); letter-spacing:.05em; text-transform:uppercase; opacity:.9; text-align:center; line-height:1.25 }
/* the main body / top face (between the end caps) */
.face{ flex:1; min-width:0; display:flex; flex-direction:column; padding:11px 16px; gap:8px;
border-radius:16px; border:1px solid var(--device-bd);
background:
radial-gradient(rgba(255,255,255,.022) .5px, transparent .6px) 0 0/3px 3px, /* bead-blast micro-texture */
linear-gradient(180deg, #2b2d33, #161719); /* matte anodised graphite */
box-shadow:0 24px 50px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.05), inset 0 -2px 8px rgba(0,0,0,.5) }
.brandrow{ display:flex; align-items:center; justify-content:space-between; margin:0 }
.dev-logo{ height:22px }
.silk{ display:flex; align-items:center; gap:7px; color:var(--silk) }
.silk .model{ font-size:8.5px; text-transform:uppercase; letter-spacing:.16em; opacity:.85 }
.meta{ display:flex; align-items:center; gap:12px }
.pwr{ display:flex; align-items:center; gap:5px; font-size:7.5px; color:var(--silk); text-transform:uppercase; letter-spacing:.12em; opacity:.85 }
.pwr .dot{ width:6px; height:6px; border-radius:50%; background:#2fe07a; box-shadow:0 0 6px #2fe07a }
.facemain{ display:flex; align-items:center; gap:14px }
/* ---- amber 14-segment alphanumeric display ---- */
.led-win{ flex:1; min-width:0; background:#140a02; border:2px solid #050100; border-radius:8px; padding:6px 12px;
box-shadow:inset 0 0 16px rgba(0,0,0,.85), inset 0 0 8px rgba(255,150,30,.10), 0 1px 0 rgba(255,255,255,.25) }
#led{ display:block; width:100%; max-width:236px; height:58px; margin:0 auto }
/* ---- recessed clickable thumb-roller (tempo) ---- */
.rollwrap{ display:flex; flex-direction:column; align-items:center; gap:3px }
.roller{ width:92px; height:46px; border-radius:9px; position:relative; cursor:ew-resize; overflow:hidden; touch-action:none;
background:#0a0d12; border:1px solid #04060a; box-shadow:inset 0 0 0 1px rgba(255,255,255,.03), 0 2px 5px rgba(0,0,0,.5) }
.roller::before{ content:""; position:absolute; inset:4px 3px; border-radius:5px; /* ribbed cylinder, scrolls via --rib */
background:repeating-linear-gradient(90deg, rgba(255,255,255,.12) 0 1px, rgba(0,0,0,.5) 1px 5px); background-position:var(--rib,0px) 0 }
.roller::after{ content:""; position:absolute; inset:0; border-radius:9px; pointer-events:none; /* cylinder sheen: bright centre, dark edges */
background:linear-gradient(90deg, rgba(0,0,0,.72) 0%, rgba(255,255,255,.13) 49%, rgba(255,255,255,.13) 51%, rgba(0,0,0,.72) 100%) }
.roller.press{ filter:brightness(.9); box-shadow:inset 0 0 0 1px rgba(255,255,255,.03), 0 1px 2px rgba(0,0,0,.6) }
.roll-cap{ font-size:7px; color:var(--silk); letter-spacing:.16em; text-transform:uppercase; opacity:.8 }
/* speaker grille + status indicators along the bottom of the face */
.facebot{ display:flex; align-items:center; gap:12px }
.grille{ flex:1; height:9px; border-radius:5px; background:radial-gradient(circle, #000 1px, transparent 1.3px) 0 0/7px 7px; opacity:.45 }
.inds{ display:flex; gap:11px }
.ind{ display:flex; align-items:center; gap:4px; font-size:7.5px; color:var(--silk); letter-spacing:.1em; text-transform:uppercase; opacity:.85 }
.ind .d{ width:6px; height:6px; border-radius:50%; background:#3a2306; box-shadow:inset 0 0 2px #000; transition:background .08s, box-shadow .08s }
.ind.on .d{ background:#ff8a1e; box-shadow:0 0 6px #ff8a1e }
.ind.play.on .d{ background:#2fe07a; box-shadow:0 0 6px #2fe07a }
.hint{ max-width:560px; text-align:center; font-size:11px; color:var(--muted); line-height:1.5 }
/* embed mode: just the device */
[data-embed] .hint { display:none !important; }
/* stack the bar's end caps under the face on very narrow screens */
@media (max-width:460px){ .device{ flex-wrap:wrap; gap:10px }
.endcap{ width:calc(50% - 5px); align-self:auto; margin:0; flex-direction:row; gap:18px; justify-content:center }
.face{ flex-basis:100%; order:-1 } }
</style>
</head>
<body>
/*@BUILD:include:src/header.html@*/
<h1 class="ff-title">PM_P1 Practice</h1>
<p class="ff-sum">Inline practice bar — patch your instrument through it (in one end, amp/headphones out the other) with the click mixed in. One clickable thumbroller, an amber 14segment display.</p>
<div class="device">
<!-- LEFT END: instrument / aux in -->
<div class="endcap left">
<div class="jk" title="1/4&quot; TRS input — plug your instrument (or an aux source) in; the click is mixed into it"><i></i><b>TRS&nbsp;In</b></div>
<div class="endlbl">Inst /<br>aux in</div>
</div>
<!-- TOP FACE: display + roller + speaker -->
<div class="face">
<div class="brandrow">
<div class="silk"><span class="dev-lock"><img class="dev-logo" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS — Simplifying Complexity" /></span><span class="model">PM_P1 Practice</span></div>
<div class="meta">
<div class="pwr" title="Powered over USBC — wall adapter or power bank"><span class="dot"></span>USBC&nbsp;PWR</div>
</div>
</div>
<div class="facemain">
<div class="led-win"><canvas id="led" width="236" height="58" aria-label="14-segment tempo / track display"></canvas></div>
<div class="rollwrap">
<div class="roller" id="enc" title="Roll = tempo · press = start/stop · hold + roll = switch track"></div>
<div class="roll-cap">Tempo</div>
</div>
</div>
<div class="facebot">
<div class="grille" title="monitor speaker"></div>
<div class="inds">
<div class="ind on" id="indBpm"><span class="d"></span>BPM</div>
<div class="ind" id="indTrk"><span class="d"></span>Trk</div>
<div class="ind play" id="indPlay"><span class="d"></span></div>
</div>
</div>
</div>
<!-- RIGHT END: power + output -->
<div class="endcap right">
<div class="jk usb" title="USBC — power (5 V) &amp; set-list transfer"><i></i><b>USBC</b></div>
<div class="jk" title="1/4&quot; TRS output — instrument + click, to your amp, headphones or the desk"><i></i><b>TRS&nbsp;Out</b></div>
</div>
</div>
<div class="hint">Roll = <b>tempo</b> · press = <b>start / stop</b> · hold &amp; roll = <b>switch track</b>.</div>
/*@BUILD:include:src/progbox.html@*/
<script>
const APP_VERSION = "v0.0.1-dev";
const $ = (id) => document.getElementById(id);
/* ========================= ENGINE (shared; synth voices only) ================= */
const SAMPLES = {}; // synth-only
/*@BUILD:include:src/engine.js@*/
/*@BUILD:include:src/setlists.js@*/
const state = { bpm:120, volume:0.85, running:false };
let meters = [], muteWindows = [];
function setBpm(v){ state.bpm = Math.max(30, Math.min(300, Math.round(v))); }
function scheduler(){
const ahead = audioCtx.currentTime + SCHEDULE_AHEAD;
for(const m of meters){ while(m.nextTime < ahead){ scheduleMeterTick(m, m.nextTime); m.nextTime += laneStepDur(m, m.tick); m.tick++; } }
}
function buildMeters(lanes){
return (lanes||[]).map(c=>{ const p=parseGroups(c.groupsStr);
return {groupsStr:c.groupsStr,groups:p.groups,beatsPerBar:p.beatsPerBar,groupStarts:p.groupStarts,
stepsPerBeat:c.stepsPerBeat||1,sound:c.sound,beatsOn:(c.beatsOn||[]).slice(),poly:!!c.poly,swing:!!c.swing,enabled:c.enabled!==false,gainDb:c.gainDb||0,
tick:0,nextTime:0,vq:[],vqPtr:0,currentStep:-1,currentBar:0}; });
}
function startAudio(){
ensureAudio(); audioCtx.resume(); state.running=true;
const t0=audioCtx.currentTime+0.08;
for(const m of meters){ m.tick=0; m.nextTime=t0; m.vq=[]; m.vqPtr=0; }
muteWindows=[]; beatFlash=0; schedulerTimer=setInterval(scheduler,LOOKAHEAD_MS); scheduler(); render();
}
function stopAudio(){ state.running=false; clearInterval(schedulerTimer); schedulerTimer=null; render(); }
function toggle(){ state.running ? stopAudio() : startAudio(); }
/* ========================= TRACKS (built-in seed grooves, flattened) ========= */
let tracks = SEED_SETLISTS.flatMap(sl => sl.items.map(([n,p]) => ({ name:n, ...patchToSetup(p) })));
let trackIdx=0, previewIdx=0;
// preload from a share link / embed config string (#p=<patch> or #sl=<set-list code>)
function tracksFromHash(){
const m=(location.hash||"").match(/[#&](p|sl)=([^&]+)/); if(!m) return null;
let payload=m[2]; try{ payload=decodeURIComponent(payload); }catch(e){}
try{
if(m[1]==="sl"){ const sl=codeToSetlist(payload); return sl.items.length ? sl.items.map(it=>({...it})) : null; }
const s=patchToSetup(payload); return s.lanes.length ? [{name:"Patch",...s}] : null;
}catch(e){ return null; }
}
function loadTrack(i){
const n=tracks.length; if(!n) return; trackIdx=((i%n)+n)%n; previewIdx=trackIdx;
const t=tracks[trackIdx]; setBpm(t.bpm||120); meters=buildMeters(t.lanes); // ramps/bars ignored — a steady practice loop
const was=state.running; if(was){ clearInterval(schedulerTimer); schedulerTimer=null; state.running=false; }
if(was) startAudio(); else render();
}
/* ========================= 14-SEGMENT DISPLAY ================================ */
const led=$("led"), lc=led.getContext("2d"), NCH=4, LW=236, LH=58;
(function(){ const dpr=Math.max(1,Math.min(3,window.devicePixelRatio||1)); led.width=LW*dpr; led.height=LH*dpr; lc.scale(dpr,dpr); })();
const LED_ON="#ff8a1e", LED_OFF="#1d1004", LED_BG="#120802"; // OFF kept very dim so the lit digits read clearly
// 14-seg font (Adafruit bit order): 0=A 1=B 2=C 3=D 4=E 5=F 6=G1 7=G2 8=H 9=I 10=J 11=K 12=L 13=M
const SEG14={ " ":0x0000,"-":0x00C0,
"0":0x0C3F,"1":0x0006,"2":0x00DB,"3":0x008F,"4":0x00E6,"5":0x2069,"6":0x00FD,"7":0x0007,"8":0x00FF,"9":0x00EF,
"A":0x00F7,"B":0x128F,"C":0x0039,"D":0x120F,"E":0x00F9,"F":0x0071,"G":0x00BD,"H":0x00F6,"I":0x1209,"J":0x001E,
"K":0x2470,"L":0x0038,"M":0x0536,"N":0x2136,"O":0x003F,"P":0x00F3,"Q":0x203F,"R":0x20F3,"S":0x00ED,"T":0x1201,
"U":0x003E,"V":0x0C30,"W":0x2836,"X":0x2D00,"Y":0x1500,"Z":0x0C09 };
let displayMode="bpm";
function trackName(i){ const raw=(tracks[i]&&tracks[i].name)||("TR"+(i+1));
return (raw.replace(/[^A-Za-z0-9]/g,"").toUpperCase().slice(0,NCH)) || ("T"+(i+1)); }
function ledText(){ return (displayMode==="track" ? trackName(previewIdx) : String(state.bpm)).padStart(NCH," "); }
function drawChar(dx,dy,w,h,ch){
const m=SEG14[ch]!=null?SEG14[ch]:0, t=Math.max(2.5,w*0.13), g=Math.max(1.5,t*0.5),
cx=dx+w/2, midY=dy+h/2, vH=h/2-t-g;
const bar=(b,x,y,ww,hh)=>{ if((m>>b)&1){ lc.fillStyle=LED_ON; lc.shadowColor=LED_ON; lc.shadowBlur=6; } else { lc.fillStyle=LED_OFF; lc.shadowBlur=0; }
lc.fillRect(x,y,ww,hh); lc.shadowBlur=0; };
const diag=(b,x1,y1,x2,y2)=>{ lc.lineCap="round"; lc.lineWidth=t*0.82;
if((m>>b)&1){ lc.strokeStyle=LED_ON; lc.shadowColor=LED_ON; lc.shadowBlur=6; } else { lc.strokeStyle=LED_OFF; lc.shadowBlur=0; }
lc.beginPath(); lc.moveTo(x1,y1); lc.lineTo(x2,y2); lc.stroke(); lc.shadowBlur=0; };
bar(0, dx+t, dy, w-2*t, t); // A top
bar(5, dx, dy+t, t, vH); // F upper-left
bar(1, dx+w-t, dy+t, t, vH); // B upper-right
bar(9, cx-t/2, dy+t, t, vH); // I centre-upper
bar(6, dx+t, midY-t/2, w/2-t-g, t); // G1 mid-left
bar(7, cx+g, midY-t/2, w/2-t-g, t); // G2 mid-right
bar(4, dx, midY+g, t, vH); // E lower-left
bar(2, dx+w-t, midY+g, t, vH); // C lower-right
bar(12, cx-t/2, midY+g, t, vH); // L centre-lower
bar(3, dx+t, dy+h-t, w-2*t, t); // D bottom
diag(8, dx+t+1, dy+t+1, cx-t*0.6, midY-t*0.6); // H top-left
diag(10, dx+w-t-1, dy+t+1, cx+t*0.6, midY-t*0.6); // J top-right
diag(11, dx+t+1, dy+h-t-1, cx-t*0.6, midY+t*0.6); // K bottom-left
diag(13, dx+w-t-1, dy+h-t-1, cx+t*0.6, midY+t*0.6); // M bottom-right
}
function drawLED(flash, level){
flash = flash || 0; level = level || 0;
lc.fillStyle=LED_BG; lc.fillRect(0,0,LW,LH);
// whole-display beat flash: a wash over the window — subtle on sub-beats, bright on the "1"
if(flash>0){ const a = flash * (level>=3 ? 0.55 : level>=2 ? 0.30 : 0.13);
lc.fillStyle = (level>=3 ? "rgba(255,184,74," : "rgba(255,138,30,") + a.toFixed(3) + ")"; lc.fillRect(0,0,LW,LH); }
const txt=ledText(), pad=10, gap=9, n=NCH, dw=(LW-2*pad-(n-1)*gap)/n, dh=LH-2*pad;
for(let i=0;i<n;i++) drawChar(pad+i*(dw+gap), pad, dw, dh, txt[i]||" ");
}
// beat / sub-beat flash, driven off the master lane (latency-compensated, like the other devices)
let beatFlash=0, beatLevel=0;
function audioLatency(){ return audioCtx ? (audioCtx.outputLatency || audioCtx.baseLatency || 0) : 0; }
function frame(){
if(audioCtx && state.running){
const now = audioCtx.currentTime - audioLatency();
for(const m of meters){
while(m.vqPtr<m.vq.length && m.vq[m.vqPtr].time<=now){
const e=m.vq[m.vqPtr]; m.currentStep=e.step;
if(m===meters[0]){ const spb=m.stepsPerBeat;
beatLevel = (e.step===0) ? 3 : (e.step % spb === 0) ? 2 : 1; // downbeat ("1") · beat · sub-beat
if((m.beatsOn[e.step]|0)!==0 || e.step % spb === 0) beatFlash=1; }
m.vqPtr++;
}
if(m.vqPtr>512){ m.vq=m.vq.slice(m.vqPtr); m.vqPtr=0; }
}
beatFlash = Math.max(0, beatFlash - 0.10);
drawLED(beatFlash, beatLevel);
}
requestAnimationFrame(frame);
}
function render(){
drawLED();
$("indBpm").classList.toggle("on", displayMode==="bpm");
$("indTrk").classList.toggle("on", displayMode==="track");
$("indPlay").classList.toggle("on", state.running);
if(window.progRefresh) progRefresh();
}
/* ========================= ROLLER (the only control) ========================= */
let rollPos=0;
function nudge(d){ setBpm(state.bpm+d); displayMode="bpm"; rollPos+=d*5; $("enc").style.setProperty("--rib", rollPos+"px"); render(); }
let revertT=null;
function previewTrack(d){ previewIdx=((previewIdx+d)%tracks.length+tracks.length)%tracks.length; displayMode="track"; render(); }
function commitTrack(){ loadTrack(previewIdx); displayMode="track"; render(); clearTimeout(revertT); revertT=setTimeout(()=>{ displayMode="bpm"; render(); }, 1100); }
/* roll = tempo · quick press = start/stop · hold (~350ms) then roll = switch track */
(function(){
const k=$("enc"); let down=false, moved=false, held=false, lastX=0, acc=0, holdT=null;
k.addEventListener("wheel",(e)=>{ e.preventDefault(); nudge(e.deltaY<0 ? (e.shiftKey?5:1) : (e.shiftKey?-5:-1)); }, {passive:false});
k.addEventListener("pointerdown",(e)=>{ down=true; moved=false; held=false; lastX=e.clientX; acc=0; previewIdx=trackIdx;
k.classList.add("press"); k.setPointerCapture(e.pointerId);
holdT=setTimeout(()=>{ if(down && !moved){ held=true; displayMode="track"; render(); } }, 350); });
k.addEventListener("pointermove",(e)=>{ if(!down) return; acc += e.clientX - lastX; lastX=e.clientX;
if(held){ while(Math.abs(acc)>=12){ const d=acc>0?1:-1; acc-=d*12; moved=true; previewTrack(d); } } // hold + roll → track
else { while(Math.abs(acc)>=6){ const d=acc>0?1:-1; acc-=d*6; moved=true; clearTimeout(holdT); nudge(d); } } }); // roll → tempo
k.addEventListener("pointerup",()=>{ if(!down) return; down=false; clearTimeout(holdT); k.classList.remove("press");
if(held){ if(moved) commitTrack(); else { displayMode="bpm"; render(); } }
else if(!moved){ toggle(); } }); // quick press → start/stop
k.addEventListener("pointercancel",()=>{ down=false; clearTimeout(holdT); k.classList.remove("press"); });
})();
/* theme toggle — cycles system → light → dark; shares the "metronome.theme" key */
/*@BUILD:include:src/chrome.js@*/
addEventListener("keydown",(e)=>{
const tag=e.target?e.target.tagName:""; if(tag==="INPUT"||tag==="TEXTAREA"||tag==="SELECT") return;
const k=e.key;
if(k===" "||e.code==="Space"){ e.preventDefault(); toggle(); }
else if(k==="ArrowUp"){ e.preventDefault(); nudge(e.shiftKey?10:1); }
else if(k==="ArrowDown"){ e.preventDefault(); nudge(e.shiftKey?-10:-1); }
else if(k==="ArrowRight"){ e.preventDefault(); previewIdx=trackIdx+1; commitTrack(); }
else if(k==="ArrowLeft"){ e.preventDefault(); previewIdx=trackIdx-1; commitTrack(); }
});
/* ========================= INIT ============================================== */
{ const ht=tracksFromHash(); if(ht) tracks=ht; } // a #p=/#sl= link (or embed config) overrides the built-ins
loadTrack(0);
render();
requestAnimationFrame(frame);
window.currentProgramString = function(){ var t=tracks[trackIdx]||{}; return setupToPatch({bpm:state.bpm, volume:state.volume, lanes:t.lanes||[]}); };
window.loadProgramString = function(plain){ var s=patchToSetup(plain); tracks=[{name:"Program", ...s}]; trackIdx=0; loadTrack(0); };
/*@BUILD:include:src/progbox.js@*/
</script>
<p class="ff-link pageonly"><a href="/info-micro.html">Purpose, dimensions &amp; bill of materials →</a></p>
/*@BUILD:include:src/footer.html@*/
</body>
</html>

159
pico-cp/README.md Normal file
View file

@ -0,0 +1,159 @@
# PM_K1 "Kit" — CircuitPython edition (USB drive · push programming · MIDI audio · practice log)
The **CircuitPython** firmware for the 52Pi EP0172 Pico kit, set up as a selfcontained appliance.
It runs the same programstring language as <https://metronome.varasys.io>. The simpler
**MicroPython** firmware (`../pico/main.py`) stays as a rocksolid fallback — and the Pico can't be
bricked (BOOTSEL → drag a MicroPython `.uf2` back).
What it does: drives the 3.5″ touchscreen with a lanes/pads display + antialiased text, plays the
speaker + RGB beat light, **logs your practice to `/history.json`**, accepts new set lists **pushed
from the web editor over USBMIDI**, and plays through your **computer's speakers** over USBMIDI.
## Two poweron modes (set by `boot.py`)
- **Appliance mode — default (just plug in / power up).** The *firmware* owns the filesystem, so it
saves your practice log and writes set lists the editor pushes over USBMIDI. The drive is then
**readonly to the computer** — which also **protects the firmware from accidental deletion**.
- **Editor mode — hold BUTTON A *alone* while plugging in.** The drive is **writable by the computer**, so you
can drag `programs.json` / `code.py` / fonts on from any OS or browser (the universal fallback).
Reset afterwards to return to appliance mode.
- **Stepper jog/test mode — hold BUTTON A + B *together* while plugging in.** A hidden screen where the
joystick spins the stepper CW/CCW for bring-up (see *Pendulum* below). This chord stays in appliance mode
(the drive is **not** flipped writable). Power-cycle with no buttons to return to normal.
## Install
1. **Flash CircuitPython:** hold **BOOTSEL**, plug in, drop the CircuitPython `.uf2` onto `RPIRP2`
(<https://circuitpython.org/board/raspberry_pi_pico/> — Pico 2 / W builds also fine). A `CIRCUITPY`
drive appears.
2. **Copy the whole bundle** onto `CIRCUITPY`: `boot.py`, `code.py` (loader) + **`app.mpy`** (the
application, **precompiled**), `programs.json`, `font_s.bin` / `font_m.bin` / `font_l.bin`,
`logo.bin` / `midi.bin` / `usb.bin` (logo + MIDI/USB status icons), `editor.html` (offline editor),
and the helper scripts. **If an old `app.py` is on the drive, delete it** — the firmware ships as
precompiled `app.mpy`. (Why: CircuitPython compiling the ~57 KB source at boot fragments the heap and
runs the RP2040 out of memory; a `.mpy` loads without compiling. `code.py` is a tiny stable loader; the
one-click updater pushes a new `app.mpy`. The `.bin` assets ride in the bundle — if one is missing the
firmware just falls back to text and never fails to boot.)
3. **Powercycle** (so `boot.py` takes effect). It boots into appliance mode and runs.
## Program it from the web (push over USBMIDI)
In the editor (Chrome / Edge / **Firefox**), build a set list → setlist **⋯** menu → **📟 Save to device**.
The editor sends it to the Pico over USBMIDI (SysEx); the firmware writes `/programs.json`, reloads, and
acknowledges — the editor shows **Saved ✓**. **📥 Load from device** reads it back.
*Universal fallback (any browser / OS, even Safari):* Save to device **downloads** `programs.json` when no
device answers — boot the Pico in **editor mode** (hold A) and drag the file onto the `CIRCUITPY` drive.
## Firmware updates (oneclick, A/B with autorollback)
`code.py` is a small stable **loader**; the application is the precompiled **`app.mpy`** (it carries
`APP_VERSION`). To update: the editor's ⋯ menu → **⬆ Update firmware…** queries the device's version, fetches
the latest `app.mpy` from the site, shows *device vs latest*, and on confirm **pushes it over USBMIDI**
(base64, in flowcontrolled chunks; the device decodes each chunk to disk and verifies the `.mpy` header
before installing). It goes to a **trial slot** (old build kept as `app.bak`) and reboots; if the new build
**doesn't boot, the loader automatically rolls back** to `app.bak`. A build that runs cleanly for ~5 s is
confirmed. No BOOTSEL, no dragging. (Updating CircuitPython *itself* still uses BOOTSEL + a `.uf2`, but that's
rare. And the Pico is unbrickable as the ultimate backstop.)
## Play through the computer's speakers
The Pico is a USBMIDI device and sends a note per click (GM drum note per lane, velocity by accent).
In the editor click **🎹 Device audio**, grant MIDI access, and press play *on the device* — the editor
voices the groove through its full synth, out your speakers, locked to the device's clock. While a host is
listening the screen shows a green **MIDI** badge and the **speaker automutes** (the computer plays instead).
The editor also syncs the device clock, so the practice log gets real wallclock timestamps.
## Playlists, editing & Continue
- **Built-in playlists** (Styles / Practice / Song) are baked into the firmware — read-only, updated with
firmware. **Your own** playlists live in `programs.json` (synced from the editor's *Save to device*).
- **Switch playlist:** tap the **set-list tab** (above the title; grey = built-in, cyan = yours). **Item:**
joystick left/right.
- **Edit on the device:** **tap a beat** to cycle it (off → normal → accent → ghost); **tap the instrument
name** for the **lane editor** (sound · beats · subdivision · swing · mute, plus **+ Lane / Remove**).
The title turns **red** (unsaved); **tap the title** to **Save** or **Revert**. Editing a built-in saves a
**copy** into a *My edits* playlist (built-ins never change). Editing your own updates it in place.
- **Continue (auto-advance):** tap **CONT** (top-right of the tab line) — when on, a playlist auto-advances
to the next item at the end of each item's `b<n>` segment (turn it on for the Song playlist).
## Controls & the practice log
- **Joystick:** up/down = tempo, left/right = previous/next groove.
- **Button A (GP15):** play / stop. **Button B (GP14):** tap tempo.
- **Screen:** VARASYS logo + firmware version + MIDI/USB status icons up top. Running time and bar count
show **of the segment total** when the track has a bar length (`b<n>`), e.g. `1:23 of 2:00` and `bar 3
of 16`. A track with a tempo ramp (`rmp`) shows a **ramp arrow + amount/every-bars** (e.g. `+4/2b`); a
gap-trainer track (`tr`) shows a **play|rest symbol + bars** (e.g. `2/2b`). Main beats are **squares**,
subdivisions are **circles**, with vertical gridlines lining the beats up across lanes.
- **RGB LED = run state:** dim **green** when stopped ("on"), dim **red** while playing, with the beat
pulsing brighter on top. (The screen background stays black — recoloring it forces a full-screen repaint.)
- The firmware **performs** ramps (tempo steps every N bars) and gap-trainer cycles (silent rest bars).
- **Touchscreen:** the bottom shows the **practice log for the current track** (time · BPM · duration · bars)
— newest first. Plays under 5 s aren't logged. **Tap a row to arm it (turns amber), tap again to delete.**
- **RGB LED** flashes the beat (amber accent / cyan normal / violet ghost); the **speaker** clicks to match.
- The log is saved to `/history.json` (next to `programs.json`) in appliance mode and survives powercycles.
## Pendulum (stepper motion) — optional
The Kit can drive a **physical metronome pendulum**: a 4-input unipolar stepper (e.g. a ULN2003 board +
28BYJ-48) swung in time with the beat, plus a **matching pendulum drawn on the screen**.
- **Wiring:** controller IN1..IN4 → **GP18, GP19, GP20, GP21**; controller GND → a Pico GND (shared
ground). Power the motor from the controller's own supply, **not** the Pico. *(These four pins are free
on the EP0172 kit. On the custom PM_K1 board GP19/20/21 are already taken by SIG/CLIP LED + groundlift,
so the production pendulum will need a pin reassignment.)*
- **Motion:** the arm reaches an extreme **exactly on each beat**, then reverses; it reads the live beat
clock, so it follows tempo ramps. The step pulses run on **PIO + DMA** (hardwaretimed), so the arm stays
smooth even while the screen redraws the pendulum graphic. Coils **deenergize when stopped**. The onscreen
pendulum (shown over the practice log while playing) mirrors the arm exactly — and animates even with **no
motor wired**.
- **Config (top of `code.py`):**
- `STEPPER_ENABLED` — off leaves the four pins free.
- `PEND_SWING_DEG` — total swing arc endtoend, in degrees (default **120**). Single source of truth:
drives the screen graphic exactly and the motor.
- `STEPPER_STEPS_PER_REV` — your motor's halfsteps per full turn (28BYJ48 halfstep ≈ 4096); maps
degrees → steps.
- `STEPPER_MAX_RATE` — top halfsteps/sec the motor sustains smoothly. **Jog mode spins at this rate**, and
the pendulum **autoshrinks** its arc (rather than desync) when a beat is too short to sweep the full angle.
- `STEPPER_ACCEL` — ramp (halfsteps/sec²) used to reach top speed without stalling; lower it if the motor
stalls/buzzes when starting.
- `STEPPER_JOG_START` — jog kickoff rate from rest (keep at or below the motor's pullin rate).
- *Tune without recompiling:* these five are also read from **`/settings.json`** (keys `stepper_max_rate`,
`stepper_accel`, `stepper_jog_start`, `pend_swing_deg`, `stepper_steps_per_rev`) — edit in editor mode,
powercycle.
- **Jog / test mode** (hold **A + B** at boot): the joystick sets **direction only****L = CCW, R = CW** — and
the motor **accelerates to `STEPPER_MAX_RATE`** (gentle ramp on start so it doesn't stall; releasing stops it
promptly, no coasting), with an onscreen needle + RGB LED and a **live step count + rate readout**. The step pulses are generated by **PIO + DMA**
(hardwaretimed on a state machine), so the motor stays smooth even while the screen redraws — there's no CPU
step loop to stall. *Tuning:* hold to spin; raise `STEPPER_MAX_RATE` until the motor skips, then back off; if
it stalls *starting*, lower `STEPPER_ACCEL` / `STEPPER_JOG_START`. Powercycle (no buttons) to exit.
## programs.json
```json
{ "title": "PolyMeter",
"programs": [ { "name": "Four on the floor", "prog": "t120;kick:4;snare:4=.x.x;hatClosed:4/2" } ] }
```
Each `prog` is a program string from the editor (tempo, lanes, patterns, `/2` subdivision, `/2s` swing,
`(3,8)` Euclid, `~` polymeter, `@-3` dB). The push above is the easy way to update it.
## Calibration (flags at the top of `code.py`)
- **Red/blue swapped:** flip `MADCTL` between `0x48` (default) and `0x40`.
- **Colours look negative:** toggle `INVERT_COLORS`.
- **Taps land wrong:** set `TOUCH_DEBUG = True`, read the raw coords over USB serial, then set
`TOUCH_SWAP_XY` / `TOUCH_INVERT_X` / `TOUCH_INVERT_Y`.
- **Joystick reversed:** toggle `JOY_INVERT_X` / `JOY_INVERT_Y`.
- **Computer audio:** `MIDI_ENABLED` (default on); `MUTE_SPEAKER` forces the speaker off even standalone.
- **LED too bright/dim:** `LED_BRIGHTNESS` (0..1, default 0.15).
- **Screen tearing:** SPI panels have no tearingeffect sync; `SPI_BAUD` (default 62.5 MHz) is pushed fast
to minimise it — lower only if unstable.
- **Blank / garbled:** the panel lot may differ; drop `SPI_BAUD`, and if it's a 240×320 ILI9341 rather than
the 320×480 ST7796, the init/size need changing (this targets the 320×480 you have).
- **RGB LED** uses the core `neopixel_write` (no library to install).
If `code.py` ever errors, CircuitPython prints the traceback **on the screen and over USB serial** — send
me that. The fonts are the baked antialiased blobs from `../pico/gen_font.py`. `protect-firmware.sh` (hide
the firmware files) is mainly for editor mode — appliance mode already keeps the drive readonly.

2053
pico-cp/app.py Normal file

File diff suppressed because it is too large Load diff

25
pico-cp/boot.py Normal file
View file

@ -0,0 +1,25 @@
# boot.py — runs once at power-on (before USB connects); decides who owns the filesystem.
#
# DEFAULT = appliance mode: the FIRMWARE owns the drive, so it can save your practice log to
# /history.json and write /programs.json that the editor pushes over USB-MIDI. The drive is then
# READ-ONLY to the computer — which also protects the firmware from accidental deletion.
#
# HOLD BUTTON A (GP15) ALONE WHILE PLUGGING IN = editor mode: the drive is writable by the computer,
# so you can drag programs.json / code.py on from any OS or browser (the universal fallback). Reset
# afterwards to return to appliance mode.
#
# HOLD BUTTON A + B TOGETHER = the firmware's hidden stepper jog/test mode (app.py). That chord
# stays in appliance mode here (drive NOT flipped writable) so testing doesn't disturb the drive.
#
# Also frees a USB endpoint (disables unused HID) and makes sure USB-MIDI is available.
import board, digitalio, storage, usb_hid, usb_midi
try: usb_hid.disable()
except Exception: pass
usb_midi.enable()
a = digitalio.DigitalInOut(board.GP15); a.switch_to_input(pull=digitalio.Pull.UP)
b = digitalio.DigitalInOut(board.GP14); b.switch_to_input(pull=digitalio.Pull.UP)
editor = (not a.value) and b.value # editor mode = A pressed, B NOT pressed (A+B is the jog chord)
a.deinit(); b.deinit()
if not editor:
try: storage.remount("/", readonly=False) # appliance: writable by code, read-only to the computer
except Exception: pass

24
pico-cp/code.py Normal file
View file

@ -0,0 +1,24 @@
# code.py - PM_K-1 A/B firmware loader (stable; rarely changes).
#
# The real application is the PRECOMPILED app.mpy (CircuitPython compiles a big .py at boot, which
# fragments the heap and OOMs; a .mpy loads without compiling). app.bak holds the previous known-good
# build. The web editor pushes a new app.mpy to a "trial" slot over USB-MIDI; this loader runs it, and
# if it fails to boot it AUTOMATICALLY ROLLS BACK to app.bak. (Unbrickable: BOOTSEL -> drag a .uf2.)
# app.mpy clears the /trial marker once it has run healthily for ~5s.
import supervisor, os
supervisor.runtime.autoreload = False # updates reboot explicitly; never auto-restart on our own writes
def _trial():
try: os.stat("/trial"); return True
except OSError: return False
try:
import app # runs the application (app.mpy; ends with App().run())
except Exception:
if _trial(): # a freshly-pushed build crashed on startup -> roll back
try:
os.remove("/app.mpy"); os.rename("/app.bak", "/app.mpy"); os.remove("/trial")
except Exception: pass
supervisor.reload() # reboot into the restored known-good build
else:
raise # the active build failed unexpectedly (rare) -> on-screen traceback

BIN
pico-cp/font_l.bin Normal file

Binary file not shown.

BIN
pico-cp/font_m.bin Normal file

Binary file not shown.

BIN
pico-cp/font_s.bin Normal file

Binary file not shown.

139
pico-cp/gen_assets.py Normal file
View file

@ -0,0 +1,139 @@
#!/usr/bin/env python3
# Generate the on-screen bitmap assets the CircuitPython firmware (app.py) blits:
# logo.bin - the VARASYS wordmark (no tagline), tinted brand cyan at the top
# midi.bin - a 5-pin DIN icon (lights green when a MIDI host is listening)
# usb.bin - the USB "trident" icon (lights when the device is USB-connected)
#
# Each file is a single 4-bit-alpha image with a 2-byte header (matches the font packing
# in gen_font.py, just without the glyph metrics table):
# byte 0 = width, byte 1 = height, then ((w*h+1)//2) bytes of 4-bit alpha,
# row-major, two pixels per byte (first pixel = high nibble).
# app.py's load_alpha()/make_glyph() decode it and blend bg->fg per pixel (smooth).
#
# Re-run after changing the logo/icons: python3 pico-cp/gen_assets.py
# Writes pico-cp/{logo,midi,usb}.bin and /tmp/assets_verify.png (eyeball it).
import math, pathlib
from PIL import Image, ImageDraw
HERE = pathlib.Path(__file__).parent
LOGO_PNG = pathlib.Path.home() / "src/varasys_logo/For using with dark colored background/VARASYS Limited.png"
SS = 6 # supersample factor for the drawn icons (downscaled -> anti-aliased)
def pack(coverage_img):
"""coverage_img: 'L' image (0..255 alpha). -> bytes(w, h, packed 4-bit alpha)."""
w, h = coverage_img.size
assert w <= 255 and h <= 255, "asset too large for the 1-byte dims (%dx%d)" % (w, h)
px = coverage_img.load()
nib = []
for y in range(h):
for x in range(w):
nib.append(px[x, y] >> 4) # 8-bit -> 4-bit alpha
if len(nib) % 2:
nib.append(0)
out = bytearray([w, h])
for i in range(0, len(nib), 2):
out.append((nib[i] << 4) | nib[i + 1])
return bytes(out)
def make_logo(target_w=156):
img = Image.open(LOGO_PNG).convert("RGBA")
r, g, b, a = img.split()
# Coverage = alpha if the PNG is genuinely transparent, else brightness (cyan on a flat bg).
if a.getextrema()[0] < 250:
cov = a
else:
cov = img.convert("L")
bbox = cov.getbbox()
if bbox:
cov = cov.crop(bbox)
w, h = cov.size
th = max(1, round(target_w * h / w))
cov = cov.resize((target_w, th), Image.LANCZOS)
return pack(cov)
def make_midi(size=22):
cw = size * SS
img = Image.new("L", (cw, cw), 0)
d = ImageDraw.Draw(img)
cx = cy = cw / 2
R = cw * 0.44
lw = max(2, int(cw * 0.07))
d.ellipse([cx - R, cy - R, cx + R, cy + R], outline=255, width=lw) # connector shell
# 5 pins in the DIN fan (one top-centre, a flanking pair above, a wider pair below)
ring = R * 0.60
pr = cw * 0.075
pins = [(0.0, -1.0), (-0.72, -0.55), (0.72, -0.55), (-0.95, 0.18), (0.95, 0.18)]
for fx, fy in pins:
x = cx + fx * ring
y = cy + fy * ring
d.ellipse([x - pr, y - pr, x + pr, y + pr], fill=255)
return pack(img.resize((size, size), Image.LANCZOS))
def make_usb(size=22):
cw = size * SS
img = Image.new("L", (cw, cw), 0)
d = ImageDraw.Draw(img)
cx = cw / 2
lw = max(2, int(cw * 0.075))
top, bot = cw * 0.10, cw * 0.90
d.line([(cx, top + cw * 0.10), (cx, bot)], fill=255, width=lw) # shaft
# arrowhead at the top
ah = cw * 0.13
d.polygon([(cx, top), (cx - ah, top + ah * 1.4), (cx + ah, top + ah * 1.4)], fill=255)
# base plug (filled circle at the bottom)
br = cw * 0.10
d.ellipse([cx - br, bot - br, cx + br, bot + br], fill=255)
# left branch -> small filled circle
ly = cw * 0.46
lx = cx - cw * 0.26
d.line([(cx, ly + cw * 0.10), (lx, ly)], fill=255, width=lw)
cr = cw * 0.085
d.ellipse([lx - cr, ly - cr, lx + cr, ly + cr], fill=255)
# right branch -> small filled square
ry = cw * 0.34
rx = cx + cw * 0.26
d.line([(cx, ry + cw * 0.10), (rx, ry)], fill=255, width=lw)
sq = cw * 0.08
d.rectangle([rx - sq, ry - sq, rx + sq, ry + sq], fill=255)
return pack(img.resize((size, size), Image.LANCZOS))
def unpack_to_img(blob, fg=(10, 179, 247), bg=(6, 9, 14)):
"""Decode like app.py would, for the verify sheet."""
w, h = blob[0], blob[1]
im = Image.new("RGB", (w, h), bg)
for k in range(w * h):
byte = blob[2 + (k >> 1)]
nib = (byte >> 4) if (k & 1) == 0 else (byte & 0xF)
t = nib * 17
col = tuple((bg[i] * (255 - t) + fg[i] * t) // 255 for i in range(3))
im.putpixel((k % w, k // w), col)
return im
def main():
assets = {"logo": make_logo(), "midi": make_midi(), "usb": make_usb()}
for name, blob in assets.items():
(HERE / (name + ".bin")).write_bytes(blob)
print("wrote %s.bin %dx%d %d bytes" % (name, blob[0], blob[1], len(blob)))
# verify sheet on a dark panel-like background
pad = 12
imgs = [unpack_to_img(b) for b in assets.values()]
W = max(i.width for i in imgs) + pad * 2
H = sum(i.height for i in imgs) + pad * (len(imgs) + 1)
sheet = Image.new("RGB", (W, H), (6, 9, 14))
y = pad
for i in imgs:
sheet.paste(i, (pad, y))
y += i.height + pad
sheet.save("/tmp/assets_verify.png")
print("verify -> /tmp/assets_verify.png")
if __name__ == "__main__":
main()

BIN
pico-cp/logo.bin Normal file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more