Compare commits
No commits in common. "main" and "concepts" have entirely different histories.
86
CLAUDE.md
|
|
@ -1,48 +1,70 @@
|
||||||
# CLAUDE.md
|
# 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 /
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./build.sh # assemble the self-contained pages into dist/ (git-ignored)
|
./build.sh # assemble self-contained pages into dist/ (also precompiles firmware .mpy + zips bundles)
|
||||||
./deploy.sh # build, stamp version, mirror dist/ to the Caddy web root, smoke-test
|
./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
|
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:
|
||||||
framework, no CDN, no audio samples (all voices are synthesized in Web Audio). Pages share code
|
|
||||||
through build markers that `build.sh` resolves:
|
|
||||||
|
|
||||||
- `/*@BUILD:include:src/<file>@*/` inlines a shared partial (`engine.js`, `setlists.js`,
|
- **Spec (source of truth):** `docs/track-format.md`
|
||||||
`base.css`, `chrome.js`, `header.html`/`footer.html`, `notation.js`, `midiout.js`).
|
- **Web:** `src/engine.js` — `patchToSetup` / `laneStrToCfg` / `setupToPatch` / `laneCfgToStr`
|
||||||
- `@BUILD:favicon@` / `@BUILD:logo-*@` / `@BUILD:bravura@` inline base64 blobs from `assets/`.
|
- **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
|
**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.
|
||||||
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).
|
|
||||||
|
|
||||||
State (set lists, practice log, theme) lives in `localStorage`; nothing is uploaded — share
|
## Web build system
|
||||||
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`).
|
|
||||||
|
|
||||||
## 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
|
|
@ -1,43 +1,298 @@
|
||||||
# VARASYS PolyMeter
|
# VARASYS PolyMeter
|
||||||
|
|
||||||
A **polymetric groove trainer & metronome**. Stack as many "meter lanes" as you like —
|
A small **website** built around one **polymetric groove trainer / metronome** engine.
|
||||||
each its own little metronome with a grouping, subdivision, drum voice and a per‑step
|
A landing page is the front door; the main app is the **PM_E‑1 PolyMeter Editor** — a full
|
||||||
pattern with accents. Layering lanes produces polymeter and true ratio polyrhythm.
|
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 per‑step pattern with accents. Layering lanes
|
||||||
|
produces polymeter and true ratio polyrhythm. The same engine drives an ever‑expanding library
|
||||||
|
of **form‑factor 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_K‑1 Kit**).
|
||||||
|
|
||||||
**Live:** https://metronome.varasys.io · **Source:** https://codeberg.org/VARASYS/metronome
|
**Live:** https://metronome.varasys.io · **Source:** https://codeberg.org/VARASYS/metronome
|
||||||
|
|
||||||
## What ships here
|
Every **deployed page is a single, self‑contained `.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 touch‑first phone/tablet app (tap a beat, set the tempo,
|
The site is **one editor + a gallery of form factors**, and each form factor is split into a
|
||||||
practice). It's an installable **PWA** that works fully offline, with a practice journal
|
**lean widget page** and a **separate info page**:
|
||||||
(`mobile-sessions.html`).
|
|
||||||
- **Desktop** → `pm_e-2.html` — the engraved‑notation editor: build rhythms on a staff with
|
|
||||||
full keyboard control. Best on a large screen.
|
|
||||||
|
|
||||||
Every **deployed page is a single, self‑contained `.html` file** with **zero runtime
|
- **`<device>.html`** — just the live widget (front view, controls, program box). This is what
|
||||||
dependencies** — no framework, no CDN, nothing fetched at runtime. `build.sh` inlines a shared
|
`?embed=1` serves and what the landing embeds; it never ships the BOM/narrative.
|
||||||
engine, the seed set lists, base styling and the brand assets (`assets/`) into each page. Every
|
- **`info-<device>.html`** — the spec page: it embeds the live widget at the top, then the
|
||||||
voice is **synthesized** in Web Audio (no audio samples). State (set lists, the practice log,
|
description, dimensioned drawings and a priced **Bill of Materials** (for the buildable hardware).
|
||||||
theme) lives in `localStorage`; share links encode everything in the URL hash — nothing is
|
|
||||||
uploaded.
|
|
||||||
|
|
||||||
## Build & deploy
|
| URL | What |
|
||||||
|
|-----|------|
|
||||||
|
| [`/`](https://metronome.varasys.io/) `index.html` | **Concepts** — the landing / form‑factor gallery; each box embeds the live widget (Open ↗ / Specs & info ⓘ) |
|
||||||
|
| `/editor.html` · `/info-editor.html` | **PM_E‑1 — PolyMeter Editor** (the main app) + its overview |
|
||||||
|
| `/pm_e-2.html` · `/info-pm_e-2.html` | **PM_E‑2 — PolyMeter Editor (Notation)** — second-gen, engraved drum notation (Bravura/SMuFL): Staff / TUBS / Konnakol views, edit-on-staff |
|
||||||
|
| `/kit.html` · `/info-kit.html` | **PM_K‑1 Kit** — buildable Raspberry Pi Pico touchscreen unit (52Pi EP‑0172); info page has the wiring, parts and firmware |
|
||||||
|
| `/player.html` · `/info-player.html` | **PM_C‑1 Concept** — idealized concept device (full display + set‑list nav, theme, fullscreen "stage" view) |
|
||||||
|
| `/teacher.html` · `/info-teacher.html` | **PM_T‑1 Teacher** — studio / lesson console (colour TFT, arcade buttons, 1/4″ instrument pass‑through with analog click injection) |
|
||||||
|
| `/stage.html` · `/info-stage.html` | **PM_S‑1 Stage** — foot‑pedal stompbox (two footswitches, expression‑pedal in, RGB beat light, instrument pass‑through) |
|
||||||
|
| `/micro.html` · `/info-micro.html` | **PM_P‑1 Practice** — inline practice bar (instrument in / out pass‑through, clickable thumb‑roller, 14‑segment display) |
|
||||||
|
| `/showcase.html` · `/info-showcase.html` | **PM_D‑1 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 drop‑in loader |
|
||||||
|
| `/pico-main.py` | the PM_K‑1 MicroPython firmware (download) |
|
||||||
|
|
||||||
```sh
|
The buildable units (Teacher, Stage, Practice, Display, Kit) carry a priced BOM on their info
|
||||||
./build.sh # assemble the self-contained pages into dist/ (git-ignored)
|
page; the Editor (web app) and Concept have none. Every page shares the same VARASYS header
|
||||||
./deploy.sh # build, stamp version, mirror dist/ to the Caddy web root, smoke-test
|
(official logo with baked‑in 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** (set‑list **⋯** 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.
|
||||||
|
- **Per‑lane gain** — a dB trim knob per lane (`@<db>` in the share language), applied at
|
||||||
|
schedule time so changing it never stutters playback.
|
||||||
|
- **Polyrhythm** — a per‑lane *poly* toggle fits a lane's beats evenly into lane 1's
|
||||||
|
bar (e.g. 5‑over‑4, 3‑over‑2).
|
||||||
|
- **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, human‑readable 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
|
||||||
|
hand‑write 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 0–100 | `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_E‑1 editor, the
|
Tokens are joined with `;`. `tr` and `rmp` are omitted when off.
|
||||||
embeddable widget, the whole gallery of hardware **form‑factor concepts**, the Raspberry Pi
|
|
||||||
Pico **firmware** editions, the **Rust** port, and the **KiCad/SPICE hardware** design — lives
|
### Lane grammar
|
||||||
on the [`concepts`](https://codeberg.org/VARASYS/metronome/src/branch/concepts) branch. It can
|
|
||||||
be promoted back to the front page at any time.
|
```
|
||||||
|
<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 drum‑machine voices
|
||||||
|
by name — `kick808 snare808 clap808 hat808 openHat808 cowbell808 tom808` and
|
||||||
|
`kick909 snare909 clap909 hat909 ride909 crash909`; or a **General‑MIDI 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 per‑step (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 off‑beats 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>`** — per‑lane 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` | eighth‑note hi‑hats (downbeat of each beat accented) |
|
||||||
|
| `ride:4/2s` | **swung** eighth‑note ride |
|
||||||
|
| `kick:4(3,8)` | a 3‑over‑8 Euclidean kick |
|
||||||
|
| `claves:5~` | 5 evenly across lane 1's bar (5‑over‑4 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 re‑import.
|
||||||
|
|
||||||
|
## Sharing
|
||||||
|
|
||||||
|
In the set‑list panel's **⋯** menu:
|
||||||
|
- **Share settings link** / **Share set‑list 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 self‑sizing widget. Drop in a
|
||||||
|
container and the loader script — it builds an `<iframe>` to the chrome‑stripped
|
||||||
|
(`?embed=1`) page, preloads your config string, and auto‑resizes 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 set‑list 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_K‑1 "Kit"
|
||||||
|
|
||||||
|
The **PM_K‑1 Kit** runs the same engine and program strings on a real device you can build today:
|
||||||
|
a **Raspberry Pi Pico** on the **52Pi EP‑0172 "Pico Breadboard Kit Plus"** — a 3.5″ ST7796
|
||||||
|
320×480 capacitive‑touch screen (GT911), a PSP joystick, a WS2812 RGB LED, a buzzer and two
|
||||||
|
buttons, all pre‑wired. See **`/info-kit.html`** for the pinout, parts (~$45 incl. Pico) and
|
||||||
|
flashing steps. Firmware lives in **`pico/`**:
|
||||||
|
|
||||||
|
- **`pico/main.py`** — single‑file **MicroPython** firmware: an ST7796 driver, GT911 touch,
|
||||||
|
WS2812 RGB, PWM buzzer, ADC joystick, baked anti‑aliased 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 anti‑aliased fonts (used by both firmwares).
|
||||||
|
- **`pico-cp/`** — a **CircuitPython** edition (download `/pm_k1_circuitpy.zip`): a self‑contained
|
||||||
|
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 USB‑MIDI** (with a universal download‑and‑drag fallback), and
|
||||||
|
plays **out your computer's speakers over USB‑MIDI** (the editor's **🎹 Device audio**). By default the
|
||||||
|
firmware owns the drive (read‑only to the computer, so it's protected); hold **button A** at power‑on for
|
||||||
|
editor mode (drive writable). **Firmware updates are one click** from the editor (⋯ → Update firmware) —
|
||||||
|
pushed over USB‑MIDI as an A/B update with automatic rollback. The MicroPython build stays the simple,
|
||||||
|
no‑computer 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 quick‑step) |
|
||||||
|
| `Alt`+`↑` / `Alt`+`↓` | reorder the cued item |
|
||||||
|
| `1`–`9` | enable / silence lane 1–9 |
|
||||||
|
| `?` | 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 quick‑steps. `Esc` cancels.
|
||||||
|
- **Bar‑length segments.** Give an item a **bar** count (Timers box, or `b<n>`) and a bar
|
||||||
|
countdown (▦) shows bars remaining. With **Continue** on, it auto‑advances 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 per‑device **program box**
|
||||||
|
(`src/progbox.{html,js}`) and the info‑page **live‑widget 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 self‑contained page in `dist/` (the Concepts landing,
|
||||||
|
the editor, the device/form‑factor pages and their `info-*.html`), copies `embed.js` through
|
||||||
|
as‑is, and copies the Pico firmware to `dist/pico-main.py`. `dist/` is generated, git‑ignored —
|
||||||
|
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_E‑1 editor** app (source, with `@BUILD:*` markers) |
|
||||||
|
| `pm_e-2.html` · `src/notation.js` | the **PM_E‑2 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_K‑1 Kit, PM_C‑1 Concept, Teacher, Stage, PM_P‑1 Practice, PM_D‑1 Display) |
|
||||||
|
| `info-*.html` | per‑form‑factor spec pages (embed the live widget + description + dimensions + BOM) |
|
||||||
|
| `embed.html` · `embed.js` | embed docs and the drop‑in 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_K‑1 MicroPython firmware: `main.py`, `gen_font.py` (font generator), `README.md` |
|
||||||
|
| `pico-cp/` | PM_K‑1 CircuitPython edition: `code.py`, `programs.json`, `font_*.bin`, `README.md` (bundled + served as `/pm_k1_circuitpy.zip`) |
|
||||||
|
| `build.sh` | resolve markers → self‑contained `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
|
## 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 in‑app **?** help).
|
||||||
|
|
||||||
|
### Credits
|
||||||
|
|
||||||
|
All drum and percussion voices are **synthesized in Web Audio** (808/909‑style and electronic) —
|
||||||
|
there are no audio samples. The on‑device fonts (PM_K‑1) are rendered from **DejaVu Sans**.
|
||||||
|
|
|
||||||
73
build.sh
|
|
@ -1,23 +1,34 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Assemble the deployed single-file pages from source + shared partials + assets/.
|
# 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: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
|
# 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 —
|
# (zero deps, works fully offline). deploy.sh runs this first. dist/ is generated —
|
||||||
# don't edit or commit it.
|
# 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
|
set -euo pipefail
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
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'
|
python3 - <<'PY'
|
||||||
import pathlib, re
|
import os, pathlib, re
|
||||||
A = pathlib.Path("assets")
|
A = pathlib.Path("assets")
|
||||||
|
|
||||||
def build(name):
|
def build(name):
|
||||||
|
|
@ -25,7 +36,7 @@ def build(name):
|
||||||
# 1) inline shared partials (function-replacement: no backslash/group interpretation)
|
# 1) inline shared partials (function-replacement: no backslash/group interpretation)
|
||||||
src = re.sub(r"/\*@BUILD:include:([^@]+)@\*/",
|
src = re.sub(r"/\*@BUILD:include:([^@]+)@\*/",
|
||||||
lambda m: pathlib.Path(m.group(1)).read_text().rstrip("\n"), src)
|
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: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-dark@", (A / "logo-dark.b64").read_text().strip())
|
||||||
src = src.replace("@BUILD:logo-light@", (A / "logo-light.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)
|
out.write_text(src)
|
||||||
return out.stat().st_size
|
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))
|
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.
|
# PWA support files for mobile.html (the phone/tablet app): manifest, service worker, icons.
|
||||||
for f in ("manifest.webmanifest", "mobile-sw.js"):
|
for f in ("manifest.webmanifest", "mobile-sw.js"):
|
||||||
pathlib.Path("dist/" + f).write_text(pathlib.Path(f).read_text())
|
pathlib.Path("dist/" + f).write_text(pathlib.Path(f).read_text())
|
||||||
for f in ("icon-192.png", "icon-512.png", "icon-180.png"):
|
for f in ("icon-192.png", "icon-512.png", "icon-180.png"):
|
||||||
pathlib.Path("dist/" + f).write_bytes((A / f).read_bytes())
|
pathlib.Path("dist/" + f).write_bytes((A / f).read_bytes())
|
||||||
print("copied PWA files (manifest.webmanifest, mobile-sw.js, icon-{192,512,180}.png)")
|
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
|
PY
|
||||||
|
|
|
||||||
72
deploy.sh
|
|
@ -5,10 +5,9 @@
|
||||||
# Caddy config: /var/lib/caddy/Caddyfile (metronome.varasys.io:8443 block)
|
# Caddy config: /var/lib/caddy/Caddyfile (metronome.varasys.io:8443 block)
|
||||||
# Bind-mount: /etc/containers/systemd/caddy.container
|
# Bind-mount: /etc/containers/systemd/caddy.container
|
||||||
#
|
#
|
||||||
# The web root is bind-mounted read-only into the Caddy container and served by
|
# The web root is bind-mounted read-only into the Caddy container and
|
||||||
# file_server, which picks up changes immediately — so a plain file copy is all
|
# served by file_server, which picks up changes immediately — so a plain
|
||||||
# that's needed (no container restart). The web root is mirrored from dist/ with
|
# file copy is all that's needed (no container restart).
|
||||||
# `rsync --delete`, so anything no longer built is removed from the live site.
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SRC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
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
|
BUILD="$VER-dev.$(date -u +%Y%m%dT%H%M%SZ)" # not a git checkout
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# stamp the version into the built copies only (source keeps the APP_VERSION placeholder)
|
# stamp the version into the built copy only (source stays clean)
|
||||||
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/"
|
|
||||||
echo "deployed v$BUILD -> $DEST_DIR"
|
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)"
|
echo " $f ($(stat -c '%s' "$DEST_DIR/$f") bytes)"
|
||||||
done
|
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
|
# Smoke test: Caddy serves on :8443 with tls internal; resolve the host
|
||||||
# to localhost so SNI matches the site block.
|
# to localhost so SNI matches the site block.
|
||||||
|
|
|
||||||
132
docs/daisy-spike.md
Normal 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
|
|
@ -0,0 +1,369 @@
|
||||||
|
# PM Live-Sync protocol (beta)
|
||||||
|
|
||||||
|
Bidirectional live mirror between the **PM_E‑1 editor** (web) and a **PM_K‑1 device**
|
||||||
|
(firmware). When armed, either side can edit a groove, change tempo/volume,
|
||||||
|
start/stop, or select a set‑list item, and the other side reflects it in real
|
||||||
|
time.
|
||||||
|
|
||||||
|
It rides the **existing USB‑MIDI 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 0x00–0x7F> 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 7‑bit ASCII** — never emit a byte > `0x7F` (it corrupts the SysEx
|
||||||
|
stream and, per `build.sh`, would also break the firmware‑update path). All
|
||||||
|
share‑language patch strings are already ASCII.
|
||||||
|
- `<origin>` — a short per‑session id (the editor uses e.g. `e1a2b3c`). Used to
|
||||||
|
drop your own echoes (see §4).
|
||||||
|
- `<seq>` — a monotonically increasing integer per sender. Informational /
|
||||||
|
duplicate‑drop; ordering is guaranteed by USB‑MIDI so no reordering logic is
|
||||||
|
required.
|
||||||
|
- `<running>` — `0` or `1`.
|
||||||
|
- `<sl>` / `<item>` — set‑list and item index of the loaded program, or `-1`.
|
||||||
|
- `<patch>` — a share‑language 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 share‑language 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, 0–100 |
|
||||||
|
| `sel=<sl>/<item>` | cue/load a set‑list item |
|
||||||
|
| `beat=<lane>/<step>/<level>` | per‑step dynamics; level `0/1/2/3` = mute/normal/accent/ghost |
|
||||||
|
| `lane=<lane>/<field>/<value>`| lane field edit (see below) |
|
||||||
|
|
||||||
|
`<lane>` and `<step>` are **0‑based** 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 re‑shape the lane list (add lane, remove lane,
|
||||||
|
> reorder) are **not** sent as deltas. Send a fresh **`0x41` FULL** instead — it
|
||||||
|
> is simpler and self‑healing. The editor does exactly this (a coalesced
|
||||||
|
> full‑state 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 lane‑field / 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 set‑list **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 on‑device 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 set‑list navigation
|
||||||
|
- `beat=<lane>/<step>/<level>` on a touch beat edit (`app.py` ~573–625)
|
||||||
|
- a `0x41` FULL after any lane add/remove/reorder or multi‑field lane edit
|
||||||
|
- a periodic `0x41` FULL **heartbeat** (~every 3–5 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 round‑trip 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 re‑broadcasts.** Wrap every apply in an
|
||||||
|
"applying remote" flag (the editor uses `_applyingRemote`) and have all of
|
||||||
|
your broadcast hooks early‑out while it is set. This is the primary guard.
|
||||||
|
2. **Drop your own origin.** On receive, if `origin == myOrigin`, ignore the
|
||||||
|
frame. (Belt‑and‑suspenders; also lets the editor's `?loopback=1` self‑test
|
||||||
|
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 single‑user‑friendly (last‑writer‑wins per field). True simultaneous
|
||||||
|
multi‑editor 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:1361‑1415`,
|
||||||
|
alongside `0x01/0x02/0x10/0x21‑23`). 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 remote‑apply flag.
|
||||||
|
- [ ] **DELTA (`0x42`)** → apply `play/stop/bpm/vol/sel/beat/lane` to `App`
|
||||||
|
state, wrapped in the remote‑apply flag so the on‑device handlers don't
|
||||||
|
re‑broadcast.
|
||||||
|
- [ ] **Broadcast** a `0x42` from each on‑device input handler (button A,
|
||||||
|
joystick tempo, touch beat edit, set‑list nav, lane editor), guarded by
|
||||||
|
the remote‑apply flag. Structural lane changes → `0x41` FULL.
|
||||||
|
- [ ] **Heartbeat:** emit `0x41` FULL every ~3–5 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 (replace‑per‑list, 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 + re‑sort. Emit a one‑entry 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** high‑rate sources (joystick tempo) and keep frames small —
|
||||||
|
the RP2040 USB‑MIDI RX buffer is tiny (the firmware updater already chunks
|
||||||
|
at 64 bytes), and live traffic shares the bus with MIDI clock, note‑out,
|
||||||
|
and the editor's Active‑Sensing heartbeat. Don't let a flood stall a
|
||||||
|
concurrent firmware push.
|
||||||
|
|
||||||
|
### Built‑in vs. user set lists (must match the editor)
|
||||||
|
|
||||||
|
The PM_K‑1's built‑in playlists (Styles / Practice / Song) are **baked into
|
||||||
|
firmware and read‑only**; on‑device edits **copy‑on‑write** into the user
|
||||||
|
"My edits" list. The editor follows the same rule (`userSetlists()` excludes the
|
||||||
|
seeded titles). So a **remote edit that targets a built‑in must follow the same
|
||||||
|
copy‑on‑write 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.).
|
||||||
|
- Multi‑peer / multi‑editor arbitration beyond last‑writer‑wins.
|
||||||
|
|
||||||
|
> **No longer out of scope** (now specced in §8 / §9): live set‑list **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, full‑overwrite path; `0x44` is the *incremental, merge‑by‑title* live
|
||||||
|
> mirror that runs automatically while sync is armed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Per‑device emit/apply matrix
|
||||||
|
|
||||||
|
Both targets implement the **full apply path** for every verb. They differ in what
|
||||||
|
they **emit**, because on‑device editing differs:
|
||||||
|
|
||||||
|
| Device | Emits | Applies |
|
||||||
|
|-------------|----------------------------------------------------|---------------------------------------------|
|
||||||
|
| **PM_K‑1** Kit (touchscreen + joystick) | `play` / `stop` / `bpm` / `sel` / `beat` / `lane` (FULL on structural lane edits) | all of the above |
|
||||||
|
| **PM_X‑1** Explorer (6 buttons, read‑only beats) | `play` / `stop` / `bpm` / `sel` only (no on‑device beat/lane editing) | all of the above |
|
||||||
|
| **PM_G‑1** Grid (17×7 LED matrix, 4 buttons, read‑only beats) | `play` / `stop` / `bpm` / `sel` only (no on‑device beat/lane editing) | all of the above |
|
||||||
|
|
||||||
|
Editors don't need to special‑case 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>`; pre‑0.0.23 firmware sends bare version → assume
|
||||||
|
`K`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Set‑list content sync (`0x44` SLSYNC)
|
||||||
|
|
||||||
|
The `0x41` FULL only carries the *one loaded program* (`<patch>`) plus the
|
||||||
|
*selection* indices. `0x44` carries **set‑list 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 (echo‑drop + duplicate info).
|
||||||
|
- `<json>` — a **7‑bit‑safe 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;..."}]}]}
|
||||||
|
```
|
||||||
|
|
||||||
|
Non‑ASCII in titles/names is escaped `\uXXXX` (the editor's existing
|
||||||
|
`programsJSON()` 7‑bit‑safe 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.** Built‑in / seeded lists (firmware `BUILTIN_SETLISTS`;
|
||||||
|
editor `SEED_SETLISTS` titles) are read‑only 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: lower‑case, alphanumerics only — the
|
||||||
|
firmware's `_slkey()`), independent of index. Indices diverge freely between
|
||||||
|
halves (the device prepends built‑ins; the web orders differently), so a
|
||||||
|
positional match is wrong — **title is the key.**
|
||||||
|
- **Items match by name** within a list (case‑sensitive, as both UIs key
|
||||||
|
practice history by exact name).
|
||||||
|
- Merge is a **per‑list 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 **last‑writer‑wins per list** (consistent with the rest of the
|
||||||
|
protocol). The receiver applies under its remote‑apply guard and does **not**
|
||||||
|
re‑broadcast a `0x44` in response (no echo storm); the next heartbeat / FULL
|
||||||
|
still reconciles the loaded program.
|
||||||
|
|
||||||
|
### Copy‑on‑write for built‑ins
|
||||||
|
A `0x44` never targets a built‑in: it only carries user lists, and the receiver
|
||||||
|
only ever writes user lists. If a user **edits a built‑in item** on either half,
|
||||||
|
that edit must first be **forked into a user list** (the firmware's `_save_edit`
|
||||||
|
already forks built‑in 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
|
||||||
|
copy‑on‑write happens **before** the sync, and the wire only ever sees user
|
||||||
|
content — the built‑ins on both sides stay pristine and identical.
|
||||||
|
|
||||||
|
### When it's emitted
|
||||||
|
- **Editor:** coalesced ~150 ms after any set‑list 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 remote‑apply flag, and once in reply to a
|
||||||
|
`0x40` HELLO. The device's per‑item *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
|
||||||
|
> set‑list content is last‑writer‑wins 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. Practice‑log 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 vice‑versa.
|
||||||
|
|
||||||
|
**Frame:** `F0 7D 45 <origin>;<seq>;<json> F7`
|
||||||
|
|
||||||
|
`<json>` is a 7‑bit‑safe 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 | set‑list 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
|
||||||
|
last‑writer‑wins philosophy and avoids a delete echoing into a re‑add.)
|
||||||
|
- The receiver **caps** its merged log (web keeps all; device keeps newest 200,
|
||||||
|
its existing cap) and re‑sorts newest‑first by `at`.
|
||||||
|
|
||||||
|
### When it's emitted
|
||||||
|
- **Editor:** after `logFinalize()` writes a new session (one‑entry 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
|
||||||
|
re‑broadcast.
|
||||||
|
- **Device:** after `_log_play()` appends a session (one‑entry batch), guarded by
|
||||||
|
the remote‑apply flag, and a full batch once in reply to a `0x40` HELLO.
|
||||||
|
|
||||||
|
> **Batch size caution (firmware):** a full‑history batch (up to 200 entries) is
|
||||||
|
> small JSON but still allocates; the device sends its on connect/HELLO only, and
|
||||||
|
> the editor's on‑connect 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. Per‑session emits are a single entry — negligible.
|
||||||
46
docs/playback-flow-test.md
Normal 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 4–6): 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 2–3): 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
|
|
@ -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 0–10) — faint ticks at each beat + a
|
||||||
|
bright playhead at the master lane's current step; the track name infinite-scrolls below it (cols
|
||||||
|
0–10, rows 2–6); 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 12–16 (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, 5–300 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 3–4 until Stage 1–2 are green and you've decided the live-push
|
||||||
|
tradeoff is acceptable.
|
||||||
235
docs/track-format.md
Normal 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
1754
editor.html
Normal file
138
embed.html
Normal 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_E‑1 Editor</option>
|
||||||
|
<option value="pme2">PM_E‑2 Editor (Notation)</option>
|
||||||
|
<option value="teacher">PM_T‑1 Teacher</option>
|
||||||
|
<option value="stage">PM_S‑1 Stage</option>
|
||||||
|
<option value="micro" selected>PM_P‑1 Practice</option>
|
||||||
|
<option value="showcase">PM_D‑1 Display</option>
|
||||||
|
<option value="initial">PM_C‑1 Concept</option>
|
||||||
|
</select>
|
||||||
|
<span class="ff-name"></span></p>
|
||||||
|
|
||||||
|
<h2>Drop-in (recommended)</h2>
|
||||||
|
<pre id="snipDrop"></pre>
|
||||||
|
<p>The script replaces the <code><div></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_E‑1 PolyMeter Editor (full web app)</td></tr>
|
||||||
|
<tr><td class="k">teacher</td><td>PM_T‑1 Teacher (studio / lesson console)</td></tr>
|
||||||
|
<tr><td class="k">stage</td><td>PM_S‑1 Stage (foot‑pedal stompbox)</td></tr>
|
||||||
|
<tr><td class="k">micro</td><td>PM_P‑1 Practice (inline practice bar)</td></tr>
|
||||||
|
<tr><td class="k">showcase</td><td>PM_D‑1 Display (RGB pendulum showpiece)</td></tr>
|
||||||
|
<tr><td class="k">initial</td><td>PM_C‑1 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 set‑list 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 auto‑grows to the widget).</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>Under the hood the loader builds <code><page>?embed=1#p=<patch></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 & 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_E‑1 Editor", file:"editor.html", h:560 },
|
||||||
|
{ k:"pme2", name:"PM_E‑2 Editor", file:"pm_e-2.html", h:640 },
|
||||||
|
{ k:"kit", name:"PM_K‑1 Kit", file:"kit.html", h:560 },
|
||||||
|
{ k:"teacher", name:"PM_T‑1 Teacher", file:"teacher.html", h:440 },
|
||||||
|
{ k:"stage", name:"PM_S‑1 Stage", file:"stage.html", h:430 },
|
||||||
|
{ k:"micro", name:"PM_P‑1 Practice", file:"micro.html", h:240 },
|
||||||
|
{ k:"showcase", name:"PM_D‑1 Display", file:"showcase.html",h:540 },
|
||||||
|
{ k:"grid", name:"PM_G‑1 Grid", file:"grid.html", h:470 },
|
||||||
|
{ k:"initial", name:"PM_C‑1 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
|
|
@ -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
|
|
@ -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_X‑1 Explorer</h1>
|
||||||
|
<p class="ff-sum">Off‑the‑shelf — the Pimoroni Explorer Kit (RP2350, 2.8″ LCD, 6 buttons, piezo) as a button‑driven sibling to the PM_K‑1 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_X‑1 Explorer</span></div>
|
||||||
|
<span class="pin">RP2350 · USB‑C</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 & 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
|
|
@ -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_G‑1 Grid</h1>
|
||||||
|
<p class="ff-sum">Off‑the‑shelf — 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_G‑1 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 & 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
|
|
@ -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
|
||||||
|
71
hardware/BOM_board.csv
Normal 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
|
||||||
|
221
hardware/DESIGN.md
Normal 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Ω) + ~+10–15 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; ~$80–200 first run. Core parts cost
|
||||||
|
~$25–40/board one-off (pro op-amps + relays dominate), trending toward ~$15–20 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
|
|
@ -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 (Vin−Vout)×I ≈ (18−15)×~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.
|
||||||
112
hardware/LAYOUT_REFERENCES.md
Normal 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" (≈ p65–66):
|
||||||
|
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 ~100–200 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
|
|
@ -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 (~$40–80)
|
||||||
|
| 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 | 10–50 |
|
||||||
|
| **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 | 30–40 |
|
||||||
|
| 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 M1–M4 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
|
|
@ -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+; 4–18V; 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.25–18V |
|
||||||
|
| **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 1B–7B=1–7, **GND=8, COM=9**, out 7C–1C=10–16 |
|
||||||
|
| **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
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Datasheets are copyrighted -- kept locally, never committed/pushed.
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
45
hardware/eda/Containerfile
Normal 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
|
|
@ -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)
|
||||||
|
```
|
||||||
195
hardware/eda/circuits/audio_chain.py
Normal 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)
|
||||||
319
hardware/eda/circuits/board.py
Normal 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)
|
||||||
62
hardware/eda/circuits/indicator.py
Normal 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)
|
||||||
82
hardware/eda/circuits/interconnect.py
Normal 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)
|
||||||
122
hardware/eda/circuits/mcu_core.py
Normal 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)
|
||||||
79
hardware/eda/circuits/midi.py
Normal 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)
|
||||||
141
hardware/eda/circuits/power_tree.py
Normal 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)
|
||||||
66
hardware/eda/circuits/rtc.py
Normal 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)
|
||||||
51
hardware/eda/circuits/speaker.py
Normal 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)
|
||||||
82
hardware/eda/circuits/stage1_input.py
Normal 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)
|
||||||
123
hardware/eda/circuits/stage1b_select.py
Normal 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)
|
||||||
137
hardware/eda/circuits/stage2_dac.py
Normal 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)
|
||||||
69
hardware/eda/circuits/stage3_sum.py
Normal 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)
|
||||||
113
hardware/eda/circuits/stage4_driver.py
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
||||||
|
*.json
|
||||||
|
*_skin.svg
|
||||||
34
hardware/eda/schematics/README.md
Normal 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`.
|
||||||
3449
hardware/eda/schematics/audio_chain.svg
Normal file
|
After Width: | Height: | Size: 357 KiB |
407
hardware/eda/schematics/indicator.svg
Normal 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 |
1620
hardware/eda/schematics/mcu_core.svg
Normal file
|
After Width: | Height: | Size: 173 KiB |
373
hardware/eda/schematics/midi.svg
Normal 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 |
2307
hardware/eda/schematics/power_tree.svg
Normal file
|
After Width: | Height: | Size: 216 KiB |
269
hardware/eda/schematics/rtc.svg
Normal 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 |
215
hardware/eda/schematics/speaker.svg
Normal 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 |
379
hardware/eda/schematics/stage1_input.svg
Normal 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 |
301
hardware/eda/schematics/stage1b_select.svg
Normal 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 |
821
hardware/eda/schematics/stage2_dac.svg
Normal 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 |
208
hardware/eda/schematics/stage3_sum.svg
Normal 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 |
406
hardware/eda/schematics/stage4_driver.svg
Normal 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
|
|
@ -0,0 +1 @@
|
||||||
|
*.csv
|
||||||
47
hardware/eda/sim/input_loading.cir
Normal 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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
||||||
|
*.data
|
||||||
BIN
hardware/eda/sim/plots/input_loading.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
hardware/eda/sim/plots/stage1_cmrr.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
hardware/eda/sim/plots/stage1_phantom.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
hardware/eda/sim/plots/stage2_recon.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
hardware/eda/sim/plots/stage4_driver.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
57
hardware/eda/sim/stage1_cmrr.cir
Normal 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
|
||||||
40
hardware/eda/sim/stage1_phantom.cir
Normal 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
|
||||||
28
hardware/eda/sim/stage1b_di.cir
Normal 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
|
||||||
36
hardware/eda/sim/stage2_recon.cir
Normal 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
|
||||||
40
hardware/eda/sim/stage3_sum.cir
Normal 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
|
||||||
39
hardware/eda/sim/stage4_driver.cir
Normal 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
|
|
@ -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
|
||||||
4
hardware/kicad/fp-lib-table
Normal 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"))
|
||||||
|
)
|
||||||
145
hardware/kicad/pm_k1.pretty/RV-8803-C7.kicad_mod
Normal 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)
|
||||||
|
)
|
||||||
|
|
@ -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)
|
||||||
|
)
|
||||||
92
hardware/kicad/pm_k1_core.kicad_pro
Normal 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": {}
|
||||||
|
}
|
||||||
56
hardware/kicad/pm_k1_core.kicad_sch
Normal 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"))
|
||||||
|
)
|
||||||
|
)
|
||||||
109
hardware/pcb_layout_tutorial.md
Normal 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 1–6 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 (~$300–1500 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.
|
||||||
291
index.html
|
|
@ -2,68 +2,253 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>VARASYS PolyMeter</title>
|
<title>VARASYS PolyMeter — Concepts (polymetric groove trainer & metronome)</title>
|
||||||
<meta name="theme-color" content="#eef3f9" media="(prefers-color-scheme: light)" />
|
<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." />
|
||||||
<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@">
|
||||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@" />
|
|
||||||
<script>
|
<script>
|
||||||
(function(){ try{
|
(function(){ try{ var p = localStorage.getItem("metronome.theme");
|
||||||
var p = localStorage.getItem("metronome.theme");
|
|
||||||
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
|
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
|
||||||
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
|
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
|
||||||
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
|
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#e7edf5; --muted:#8b96a5; --link:#6cb6ff;
|
/*@BUILD:include:src/base.css@*/
|
||||||
--card:#161b22; --card-bd:#2a313c; --cyan:#0AB3F7; }
|
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
|
||||||
:root[data-theme="light"]{ --bg1:#eef3f9; --bg2:#cfd9e6; --txt:#10202f; --muted:#5c6776; --link:#1769c4;
|
--panel-bg:#161b22; --panel-bd:#2a313c; --field-bg:#0e1116; --field-bd:#2a313c; }
|
||||||
--card:#ffffff; --card-bd:#d2dae4; --cyan:#0AB3F7; }
|
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
|
||||||
html,body{ height:100%; }
|
--panel-bg:#ffffff; --panel-bd:#d2dae4; --field-bg:#f1f4f8; --field-bd:#d2dae4; }
|
||||||
body{ margin:0; color:var(--txt);
|
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt);
|
||||||
background:radial-gradient(circle at 50% -10%, var(--bg1), var(--bg2));
|
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); }
|
||||||
font-family:"Segoe UI", Roboto, Helvetica, Arial, sans-serif; -webkit-text-size-adjust:100%; }
|
a{ color:var(--link); }
|
||||||
[data-theme="light"] .logo-dark{ display:none; } [data-theme="dark"] .logo-light{ display:none; }
|
main{ width:100%; max-width:1040px; margin:0 auto; }
|
||||||
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; }
|
.intro{ text-align:center; padding:34px 12px 18px; }
|
||||||
.logo{ height:clamp(34px,7vmin,56px); width:auto; }
|
.intro h1{ font-size:clamp(32px, 6.5vw, 54px); margin:0; letter-spacing:-.02em; line-height:1;
|
||||||
.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; }
|
background:linear-gradient(90deg, var(--cyan), #6cb6ff); -webkit-background-clip:text; background-clip:text; color:transparent; }
|
||||||
.choices{ display:flex; flex-wrap:wrap; gap:clamp(16px,3vmin,28px); justify-content:center; width:100%; max-width:620px; }
|
.intro .tagline{ margin:13px auto 0; font-size:clamp(15px,2.4vw,19px); color:var(--txt); font-weight:600; }
|
||||||
.choice{ flex:1 1 240px; min-width:0; max-width:300px; text-decoration:none; color:var(--txt);
|
.intro p{ margin:11px auto 0; max-width:66ch; color:var(--muted); font-size:14.5px; line-height:1.6; }
|
||||||
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;
|
.section-label{ text-align:center; font-size:11px; text-transform:uppercase; letter-spacing:.12em; color:var(--muted); margin:26px 0 12px; }
|
||||||
display:flex; flex-direction:column; align-items:center; gap:10px;
|
/* summary panes — click to load that version into the viewport */
|
||||||
box-shadow:0 6px 22px rgba(0,0,0,.18); transition:transform .12s ease, border-color .12s ease, box-shadow .12s ease; }
|
.panes{ display:grid; grid-template-columns:repeat(auto-fit, minmax(225px, 1fr)); gap:12px; }
|
||||||
.choice:hover, .choice:focus-visible{ border-color:var(--cyan); transform:translateY(-3px); box-shadow:0 12px 30px rgba(10,179,247,.18); outline:none; }
|
.pane{ text-align:left; background:var(--panel-bg); border:1px solid var(--panel-bd); border-radius:12px; padding:12px 13px;
|
||||||
.choice:active{ transform:translateY(0); }
|
cursor:pointer; display:flex; flex-direction:column; gap:6px; transition:border-color .14s, box-shadow .14s; }
|
||||||
.choice .ic{ font-size:clamp(34px,7vmin,52px); line-height:1; }
|
.pane:hover{ border-color:var(--cyan); }
|
||||||
.choice .lbl{ font-size:clamp(19px,3.4vmin,26px); font-weight:700; letter-spacing:.01em; }
|
.pane.active{ border-color:var(--cyan); box-shadow:0 0 0 1px var(--cyan) inset; }
|
||||||
.choice .sub{ font-size:clamp(11px,2vmin,13px); color:var(--muted); line-height:1.4; }
|
.pane .ph{ display:flex; align-items:center; gap:8px; }
|
||||||
footer{ color:var(--muted); font-size:12px; line-height:1.7; }
|
.pane h3{ margin:0; font-size:14px; }
|
||||||
footer a{ color:var(--link); text-decoration:none; }
|
.pane .chip{ font-size:9px; text-transform:uppercase; letter-spacing:.07em; padding:2px 7px; border-radius:999px;
|
||||||
footer a:hover{ text-decoration:underline; }
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
|
||||||
<img class="logo logo-dark" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS PolyMeter" />
|
/*@BUILD:include:src/header.html@*/
|
||||||
<img class="logo logo-light" src="data:image/png;base64,@BUILD:logo-light@" alt="VARASYS PolyMeter" />
|
|
||||||
<p class="tagline">A polymetric groove trainer & metronome. Pick how you want to play.</p>
|
<main>
|
||||||
<div class="choices">
|
<section class="intro">
|
||||||
<a class="choice" href="/mobile.html">
|
<h1>PolyMeter</h1>
|
||||||
<span class="ic" aria-hidden="true">📱</span>
|
<p class="tagline">Polymetric grooves — one engine, one program string, every form factor.</p>
|
||||||
<span class="lbl">Mobile</span>
|
<p>Stack independent meter lanes — each with its own subdivision, drum voice and per‑step accents — to build
|
||||||
<span class="sub">Touch-first phone & tablet app — tap a beat, set the tempo, practice. Installable, works offline.</span>
|
true polymeter and ratio polyrhythm. Design a groove once; it saves to a compact <b>program string</b> that
|
||||||
</a>
|
plays back identically on the web editor, the hardware concepts, or an embedded widget. The editor is open
|
||||||
<a class="choice" href="/pm_e-2.html">
|
below — or pick any form factor to load and play the same groove on it.</p>
|
||||||
<span class="ic" aria-hidden="true">🎼</span>
|
</section>
|
||||||
<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>
|
<section class="philosophy">
|
||||||
</a>
|
<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>
|
||||||
|
<div class="phil">
|
||||||
|
<h3>🔌 USB‑C power everywhere — no batteries</h3>
|
||||||
|
<p>Every device runs over a single <b>USB‑C</b> port (the larger ones add a pass‑through to daisy‑chain).
|
||||||
|
No internal battery to wear out; bring a power bank. One connector keeps it all <b>future‑proof</b>.</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
</section>
|
||||||
VARASYS PolyMeter · <a href="https://codeberg.org/VARASYS/metronome" target="_blank" rel="noopener">source on Codeberg</a>
|
|
||||||
</footer>
|
<div class="section-label">Pick a form factor — it loads live below</div>
|
||||||
</main>
|
<div class="panes" id="panes"></div>
|
||||||
|
|
||||||
|
<div class="viewport">
|
||||||
|
<div class="vp-bar"><span id="vpName"><b>PM_E‑2 Editor</b></span><span><a id="vpInfo" href="/info-pm_e-2.html" target="_blank" rel="noopener">Specs & info ⓘ</a> · <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 set‑list 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>🦀 Native‑Rust firmware — PM_K‑1 (RP2350 / Pico 2)</h3>
|
||||||
|
<p>An experimental native‑Rust 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 per‑board drivers.
|
||||||
|
It now runs as a working metronome — boots, drives the ST7796 display, plays built‑in grooves with audio
|
||||||
|
clicks, and has a drum‑notation view. Controls: <b>A</b> = play/stop, <b>B</b> = grid/notation, joystick =
|
||||||
|
tempo & 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 pm‑kit.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 & 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_E‑2 Editor", chip:"app", h:640, sum:"The PolyMeter editor, built around engraved drum notation — a 5‑line percussion staff (Bravura/SMuFL) with Staff / TUBS / Konnakol views, edit‑on‑staff, plus flams/drags/rolls, odd meters & clave." },
|
||||||
|
{ key:"mobile", file:"/mobile.html", name:"PM_M‑1 Mobile", chip:"app", h:600, sum:"Phone & tablet app — a touch‑first, full‑screen player you can “Add to Home Screen.” Big tap targets, drag‑to‑scrub tempo, a pulsing beat display, screen‑wake‑lock, and an iOS fix for the ring/silent switch. Installable & works offline." },
|
||||||
|
{ key:"kit", file:"/kit.html", name:"PM_K‑1 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_X‑1 Explorer", chip:"hw", h:500, sum:"Off‑the‑shelf — the Pimoroni Explorer (RP2350, 2.8″ LCD, 6 buttons, piezo) as a button‑driven 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_G‑1 Grid", chip:"hw", h:470, sum:"Off‑the‑shelf — 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_T‑1 Teacher", chip:"hw", h:440, sum:"Studio / lesson desk console — colour TFT of every lane, arcade buttons, instrument pass‑through." },
|
||||||
|
{ key:"stage", file:"/stage.html", name:"PM_S‑1 Stage", chip:"hw", h:430, sum:"Live foot pedal — two footswitches, expression‑pedal tempo, a big floor‑readable RGB beat light." },
|
||||||
|
{ key:"micro", file:"/micro.html", name:"PM_P‑1 Practice", chip:"hw", h:240, sum:"Inline practice bar — clickable thumb‑roller, amber 14‑segment, instrument in/out pass‑through." },
|
||||||
|
{ key:"showcase", file:"/showcase.html",name:"PM_D‑1 Display", chip:"hw", h:540, sum:"Pyramid display piece — an RGB‑light pendulum combining every lane's subdivisions & accents." },
|
||||||
|
{ key:"initial", file:"/player.html", name:"PM_C‑1 Concept", chip:"", h:440, sum:"The idealized concept render — full multi‑lane display and set‑list 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 & 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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
68
info-editor.html
Normal 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_E‑1 PolyMeter Editor — what it is</title>
|
||||||
|
<meta name="description" content="PM_E‑1 PolyMeter Editor — the web workbench for the family: stack independent meter lanes with their own subdivision, drum voice, per‑step accents/ghosts/mutes, swing, polyrhythm, set lists and per‑lane 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_E‑1 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 per‑step <b>accents, ghosts and mutes</b>, plus swing, ratio polyrhythm, set lists and a
|
||||||
|
per‑lane <b>dB gain</b>. It's zero‑install — 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 share‑language. 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><div></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_E‑1 PolyMeter Editor" };
|
||||||
|
/*@BUILD:include:src/infoembed.js@*/
|
||||||
|
/*@BUILD:include:src/chrome.js@*/
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
177
info-explorer.html
Normal 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 & 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" 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" 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" 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" 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">≈ $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>
|
||||||
|
· <a href="https://github.com/pimoroni/explorer" target="_blank" rel="noopener">vendor code</a>
|
||||||
|
· <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 · web-driven editing via Live sync · MIDI audio · 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 ↓</a>
|
||||||
|
<a class="dl alt" href="https://codeberg.org/VARASYS/metronome/src/branch/main/pico-explorer" target="_blank" rel="noopener">Source + README ↗</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>🔗 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>···</b> menu →
|
||||||
|
<b>📟 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>🎹 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> → <b>Practice log</b>. Plays over 5 s appear (time · BPM · duration · bars).</li>
|
||||||
|
<li><b>Firmware updates:</b> ··· menu → <b>⬆ 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
|
|
@ -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 & 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×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×7 matrix</b> driven by an
|
||||||
|
<b>IS31FL3731</b> over I²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 × 25 × 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 × 17-column
|
||||||
|
matrix maps directly onto the editor's <b>lane × 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>🎹 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×7 white, IS31FL3731 (I²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 & 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 → Grid → 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×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° 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 > 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×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">≈ $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>
|
||||||
|
· <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) ↓</a>
|
||||||
|
<a class="dl alt" href="https://codeberg.org/VARASYS/metronome/src/branch/main/rust/pm-grid" target="_blank" rel="noopener">Source ↗</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>🎹 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
|
|
@ -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_K‑1 Kit — wiring, parts & firmware (Raspberry Pi Pico build)</title>
|
||||||
|
<meta name="description" content="PM_K‑1 Kit — build a touchscreen polymeter metronome from a Raspberry Pi Pico on the 52Pi EP‑0172 breadboard kit (3.5in ST7796 cap‑touch, 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_K‑1 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 one‑click updates over USB‑MIDI.</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 EP‑0172 kit</span><span>~$45 incl. Pico</span></div>
|
||||||
|
<p>This is the first member of the family you can actually build today from off‑the‑shelf parts: a
|
||||||
|
<b>Raspberry Pi Pico</b> seated on the <b>52Pi EP‑0172 "Pico Breadboard Kit Plus"</b>, which carries a
|
||||||
|
3.5″ <b>ST7796</b> 320×480 capacitive‑touch screen (<b>GT911</b>), a PSP <b>joystick</b>, a <b>WS2812 RGB</b>
|
||||||
|
LED, a <b>speaker</b> and two buttons — all pre‑wired, 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 USB‑MIDI (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 EP‑0172 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 & 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 off‑the‑shelf kit, not a custom board — ballpark one‑off 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 EP‑0172 "Pico Breadboard Kit Plus" <span class="spec">— 3.5″ ST7796 cap‑touch, 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 (one‑off)</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 EP‑0172 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 — self‑contained appliance (USB drive · on‑device editing · push programming · MIDI audio · practice log)</summary>
|
||||||
|
<div class="spec-body">
|
||||||
|
<p class="sub">The firmware turns the Pico 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 full lanes/pads touchscreen with <b>on‑device 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 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_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 power‑cycle. 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 set‑list <b>⋯</b> menu → <b>📟 Save to device</b>. It's pushed over USB‑MIDI 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 USB‑MIDI, 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 USB‑MIDI, and 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">Embed this widget elsewhere with one <code><div></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_K‑1 Kit" };
|
||||||
|
/*@BUILD:include:src/infoembed.js@*/
|
||||||
|
/*@BUILD:include:src/chrome.js@*/
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
129
info-micro.html
Normal 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_P‑1 Practice — purpose, dimensions & bill of materials</title>
|
||||||
|
<meta name="description" content="PM_P‑1 Practice — a long, narrow inline practice bar: one clickable thumb‑roller, amber 14‑segment display, analog instrument pass‑through 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_P‑1 Practice</h1>
|
||||||
|
<p class="sub">A long, narrow inline practice bar — patch it into your signal, drive everything from one clickable thumb‑roller, and read tempo and track names off an amber 14‑segment 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 one‑off</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 thumb‑roller does everything (roll = tempo, press = start/stop,
|
||||||
|
hold + roll = switch track), and an amber 14‑segment 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
|
||||||
|
USB‑C — a wall adapter for a permanent practice‑space 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 & 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">14‑seg display</div>
|
||||||
|
<div class="jl" style="left:58%; bottom:5px">thumb‑roller</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">USB‑C · 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 & bill of materials</summary>
|
||||||
|
<div class="spec-body">
|
||||||
|
<p class="sub">Rough parts list — a USB‑C‑powered RP2040 inline bar with analog click injection.
|
||||||
|
Ballpark one‑off 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 & display</td></tr>
|
||||||
|
<tr><td class="part">RP2040 board, USB‑C <span class="spec">— e.g. Waveshare RP2040‑Zero</span></td><td class="q">1</td><td class="c">4</td></tr>
|
||||||
|
<tr><td class="part">4‑char 14‑segment alphanumeric LED + I²C driver <span class="spec">— amber; HT16K33. Shows BPM & 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 thumb‑roller <span class="spec">— EC11 encoder + roller wheel · roll / press / hold‑roll</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">— line‑level click</span></td><td class="q">1</td><td class="c">3</td></tr>
|
||||||
|
<tr><td class="part">Dual op‑amp, NE5532 / OPA2134 <span class="spec">— hi‑Z instrument buffer + summing mixer</span></td><td class="q">1</td><td class="c">1</td></tr>
|
||||||
|
<tr><td class="part">PAM8302A mono Class‑D + 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 & 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">USB‑C 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">— bead‑blasted, matte‑black anodised</span></td><td class="q">1</td><td class="c">8</td></tr>
|
||||||
|
<tr class="total"><td>Total (one‑off)</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><div></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_P‑1 Practice" };
|
||||||
|
/*@BUILD:include:src/infoembed.js@*/
|
||||||
|
/*@BUILD:include:src/chrome.js@*/
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
68
info-player.html
Normal 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_C‑1 Concept — the idealized player</title>
|
||||||
|
<meta name="description" content="PM_C‑1 Concept — the idealized, screen‑first player render: full set‑list 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_C‑1 Concept</h1>
|
||||||
|
<p class="sub">The idealized concept render — a clean, screen‑first player with a colour beat display, set‑list 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_C‑1): the player as a clean, screen‑first device with no concession to mechanical parts yet.
|
||||||
|
It's the look we design <i>toward</i> — full set‑list 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_T‑1 Teacher</a> (full priced BOM there);
|
||||||
|
for the smallest practical unit, see the <a href="/info-micro.html">PM_P‑1 Practice</a>.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<p class="sub" style="max-width:760px;margin:14px auto 0">Embed this widget elsewhere with one <code><div></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_C‑1 Concept" };
|
||||||
|
/*@BUILD:include:src/infoembed.js@*/
|
||||||
|
/*@BUILD:include:src/chrome.js@*/
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
74
info-pm_e-2.html
Normal 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_E‑2 PolyMeter Editor (Notation) — what it is</title>
|
||||||
|
<meta name="description" content="PM_E‑2 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_E‑2 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_E‑1 editor</a> is built around a step-pad grid,
|
||||||
|
<b>PM_E‑2 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 ka di 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><div></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_E‑2 PolyMeter Editor" };
|
||||||
|
/*@BUILD:include:src/infoembed.js@*/
|
||||||
|
/*@BUILD:include:src/chrome.js@*/
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
123
info-showcase.html
Normal 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_D‑1 Display — purpose, dimensions & bill of materials</title>
|
||||||
|
<meta name="description" content="PM_D‑1 Display — a pyramid display‑piece 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_D‑1 Display</h1>
|
||||||
|
<p class="sub">A display‑piece metronome — the pendulum is an RGB light bar that combines every lane's subdivisions & 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 one‑off</span></div>
|
||||||
|
<p>A metronome as an object: the silhouette of a classic pyramid wind‑up unit, but the swinging pendulum is
|
||||||
|
pure <b>RGB light</b>. The whole bar is the display — every lane's subdivisions & 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 USB‑C with a second "thru" port to daisy‑chain. 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 & profile — ≈ 4.7 × 7.1 × 3.1 in (120 × 180 × 80 mm), a truncated‑pyramid 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">RGB‑light 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">USB‑C in base</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dvx">↔ 3.1 in (80 mm) deep</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="spec" open>
|
||||||
|
<summary>Spec & bill of materials</summary>
|
||||||
|
<div class="spec-body">
|
||||||
|
<p class="sub">Rough parts list — a USB‑C‑powered RP2040 display piece driving addressable RGB light.
|
||||||
|
Ballpark one‑off 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, USB‑C <span class="spec">— e.g. Waveshare RP2040‑Zero</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 / light‑guide <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 & build</td></tr>
|
||||||
|
<tr><td class="part">2× USB‑C (data+power & power‑thru) + PWR LED <span class="spec">— daisy‑chain</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 (one‑off)</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><div></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_D‑1 Display" };
|
||||||
|
/*@BUILD:include:src/infoembed.js@*/
|
||||||
|
/*@BUILD:include:src/chrome.js@*/
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
130
info-stage.html
Normal 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_S‑1 Stage — purpose, dimensions & bill of materials</title>
|
||||||
|
<meta name="description" content="PM_S‑1 Stage — a foot‑pedal polymeter stompbox: hands‑free footswitches, expression‑pedal tempo, a floor‑readable RGB beat light, analog instrument pass‑through. 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_S‑1 Stage</h1>
|
||||||
|
<p class="sub">A foot‑pedal polymeter stompbox — hands‑free footswitches, expression‑pedal tempo, a floor‑readable RGB beat light, and analog instrument pass‑through 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>Foot‑pedal stompbox</span><span>~$52 one‑off</span></div>
|
||||||
|
<p>A foot‑operated polymeter stompbox for the stage: drive it hands‑free 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″ expression‑pedal 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 USB‑C — with a second USB‑C <b>"thru"</b> port so several pedals
|
||||||
|
daisy‑chain off one charger or power bank.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="dview">
|
||||||
|
<p class="cap">Dimensions & layout — ≈ 4.7 × 3.7 × 1.5 in (120 × 93 × 38 mm), a 1590BB‑style 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 · USB‑C · USB‑C thru</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dvx">↔ 4.7 in (120 mm)</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="spec" open>
|
||||||
|
<summary>Spec & bill of materials</summary>
|
||||||
|
<div class="spec-body">
|
||||||
|
<p class="sub">Rough parts list — a foot‑operated RP2040 stompbox (USB‑C, dual‑port) with analog click injection.
|
||||||
|
Ballpark one‑off 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 & display</td></tr>
|
||||||
|
<tr><td class="part">RP2040 board, USB‑C <span class="spec">— e.g. Waveshare RP2040‑Zero</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">High‑bright diffused RGB beat indicator <span class="spec">— floor‑readable</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">Heavy‑duty momentary footswitch (soft‑touch) <span class="spec">— Tap · Next</span></td><td class="q">2</td><td class="c">6</td></tr>
|
||||||
|
<tr><td class="part">1/4″ expression‑pedal 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">— line‑level click</span></td><td class="q">1</td><td class="c">3</td></tr>
|
||||||
|
<tr><td class="part">Dual op‑amp, NE5532 / OPA2134 <span class="spec">— hi‑Z 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 & 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× USB‑C (data+power & power‑thru) + power‑path/protection + PWR LED <span class="spec">— daisy‑chain 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">Die‑cast aluminium stompbox (Hammond 1590BB‑style) <span class="spec">— bead‑blasted, matte‑black Type II anodise, laser‑etched</span></td><td class="q">1</td><td class="c">12</td></tr>
|
||||||
|
<tr class="total"><td>Total (one‑off)</td><td class="q"></td><td class="c">≈ $52</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p class="sub" style="margin-top:12px">No built‑in speaker — the Stage feeds your amp / PA. The click is summed in
|
||||||
|
the <b>analog domain</b> (hi‑Z instrument buffer + DAC → balanced line driver), so your instrument is never
|
||||||
|
re‑digitised (no added latency).</p>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<p class="sub" style="max-width:760px;margin:14px auto 0">Embed this widget elsewhere with one <code><div></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_S‑1 Stage" };
|
||||||
|
/*@BUILD:include:src/infoembed.js@*/
|
||||||
|
/*@BUILD:include:src/chrome.js@*/
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
105
info-teacher.html
Normal 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_T‑1 Teacher — purpose, spec & bill of materials</title>
|
||||||
|
<meta name="description" content="PM_T‑1 Teacher — a full‑feature studio / lesson desk console: a colour TFT of every lane, arcade buttons, a thumb‑roller, and analog instrument pass‑through 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_T‑1 Teacher</h1>
|
||||||
|
<p class="sub">The full‑feature studio / lesson desk console — a colour TFT showing every lane, arcade buttons and a thumb‑roller, 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 one‑off</span></div>
|
||||||
|
<p>The full‑feature desktop console: a colour readout of every lane, fast set‑list navigation, and your
|
||||||
|
instrument running straight through with the click mixed in — the hands‑on unit for a studio desk or a
|
||||||
|
teaching room, on a non‑reflective matte‑black case. (For hands‑free live use, see the foot‑operated
|
||||||
|
<a href="/info-stage.html">Stage</a> stompbox.)</p>
|
||||||
|
<p>Top‑mounted 1/4″ jacks keep cabling tidy; the metronome click is summed into the signal in the
|
||||||
|
<b>analog domain</b> (no re‑digitising, no added latency) and sent to a balanced 1/4″ TRS output for the
|
||||||
|
desk or interface, plus a small monitor speaker. Powered over USB‑C — a wall adapter or a power bank. The
|
||||||
|
colour TFT shows tempo, the item name and all lane patterns; arcade buttons + a recessed thumb‑roller make
|
||||||
|
it quick to drive while you teach or track.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<details class="spec" open>
|
||||||
|
<summary>Spec & bill of materials</summary>
|
||||||
|
<div class="spec-body">
|
||||||
|
<p class="sub">Rough parts list — a desk/studio RP2040 build (USB‑C powered) with analog click injection.
|
||||||
|
Ballpark one‑off 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 & display</td></tr>
|
||||||
|
<tr><td class="part">RP2040 board, USB‑C <span class="spec">— e.g. Waveshare RP2040‑Zero / Pico‑clone</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) + side‑mount thumb‑roller <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">— line‑level click</span></td><td class="q">1</td><td class="c">3</td></tr>
|
||||||
|
<tr><td class="part">Dual op‑amp, NE5532 / OPA2134 <span class="spec">— hi‑Z 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 cross‑coupled op‑amp) → 1/4″ TRS out</span></td><td class="q">1</td><td class="c">4</td></tr>
|
||||||
|
<tr><td class="part">PAM8302A mono Class‑D + 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 & 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">USB‑C 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">Die‑cast aluminium enclosure (Hammond 1590‑style) <span class="spec">— bead‑blasted, matte‑black Type II anodise, laser‑etched legends</span></td><td class="q">1</td><td class="c">12</td></tr>
|
||||||
|
<tr class="total"><td>Total (one‑off)</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
|
||||||
|
high‑impedance 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 re‑digitised (no added latency).</p>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<p class="sub" style="max-width:760px;margin:14px auto 0">Embed this widget elsewhere with one <code><div></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_T‑1 Teacher" };
|
||||||
|
/*@BUILD:include:src/infoembed.js@*/
|
||||||
|
/*@BUILD:include:src/chrome.js@*/
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
307
kit.html
Normal 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_K‑1 — 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_K‑1 Kit</h1>
|
||||||
|
<p class="ff-sum">The build‑it‑yourself touchscreen unit — a Raspberry Pi Pico on the 52Pi breadboard kit (3.5″ cap‑touch, 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_K‑1 Kit</span></div>
|
||||||
|
<span class="pin">Pico · USB‑C</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 on‑screen 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 & 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_K‑1 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
|
|
@ -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_P‑1 — 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_P‑1 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 thumb‑roller, an amber 14‑segment display.</p>
|
||||||
|
|
||||||
|
<div class="device">
|
||||||
|
<!-- LEFT END: instrument / aux in -->
|
||||||
|
<div class="endcap left">
|
||||||
|
<div class="jk" title="1/4" TRS input — plug your instrument (or an aux source) in; the click is mixed into it"><i></i><b>TRS 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_P‑1 Practice</span></div>
|
||||||
|
<div class="meta">
|
||||||
|
<div class="pwr" title="Powered over USB‑C — wall adapter or power bank"><span class="dot"></span>USB‑C 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="USB‑C — power (5 V) & set-list transfer"><i></i><b>USB‑C</b></div>
|
||||||
|
<div class="jk" title="1/4" TRS output — instrument + click, to your amp, headphones or the desk"><i></i><b>TRS Out</b></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hint">Roll = <b>tempo</b> · press = <b>start / stop</b> · hold & 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 & bill of materials →</a></p>
|
||||||
|
|
||||||
|
/*@BUILD:include:src/footer.html@*/
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
159
pico-cp/README.md
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
# PM_K‑1 "Kit" — CircuitPython edition (USB drive · push programming · MIDI audio · practice log)
|
||||||
|
|
||||||
|
The **CircuitPython** firmware for the 52Pi EP‑0172 Pico kit, set up as a self‑contained appliance.
|
||||||
|
It runs the same program‑string language as <https://metronome.varasys.io>. The simpler
|
||||||
|
**MicroPython** firmware (`../pico/main.py`) stays as a rock‑solid 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 + anti‑aliased text, plays the
|
||||||
|
speaker + RGB beat light, **logs your practice to `/history.json`**, accepts new set lists **pushed
|
||||||
|
from the web editor over USB‑MIDI**, and plays through your **computer's speakers** over USB‑MIDI.
|
||||||
|
|
||||||
|
## Two power‑on 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 USB‑MIDI. The drive is then
|
||||||
|
**read‑only 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 `RPI‑RP2`
|
||||||
|
(<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. **Power‑cycle** (so `boot.py` takes effect). It boots into appliance mode and runs.
|
||||||
|
|
||||||
|
## Program it from the web (push over USB‑MIDI)
|
||||||
|
|
||||||
|
In the editor (Chrome / Edge / **Firefox**), build a set list → set‑list **⋯** menu → **📟 Save to device**.
|
||||||
|
The editor sends it to the Pico over USB‑MIDI (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 (one‑click, A/B with auto‑rollback)
|
||||||
|
|
||||||
|
`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 USB‑MIDI**
|
||||||
|
(base64, in flow‑controlled 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 USB‑MIDI 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 auto‑mutes** (the computer plays instead).
|
||||||
|
The editor also syncs the device clock, so the practice log gets real wall‑clock 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 power‑cycles.
|
||||||
|
|
||||||
|
## 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 EP‑0172 kit. On the custom PM_K‑1 board GP19/20/21 are already taken by SIG/CLIP LED + ground‑lift,
|
||||||
|
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** (hardware‑timed), so the arm stays
|
||||||
|
smooth even while the screen redraws the pendulum graphic. Coils **de‑energize when stopped**. The on‑screen
|
||||||
|
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 end‑to‑end, in degrees (default **120**). Single source of truth:
|
||||||
|
drives the screen graphic exactly and the motor.
|
||||||
|
- `STEPPER_STEPS_PER_REV` — your motor's half‑steps per full turn (28BYJ‑48 half‑step ≈ 4096); maps
|
||||||
|
degrees → steps.
|
||||||
|
- `STEPPER_MAX_RATE` — top half‑steps/sec the motor sustains smoothly. **Jog mode spins at this rate**, and
|
||||||
|
the pendulum **auto‑shrinks** its arc (rather than desync) when a beat is too short to sweep the full angle.
|
||||||
|
- `STEPPER_ACCEL` — ramp (half‑steps/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 pull‑in 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,
|
||||||
|
power‑cycle.
|
||||||
|
- **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 on‑screen needle + RGB LED and a **live step count + rate readout**. The step pulses are generated by **PIO + DMA**
|
||||||
|
(hardware‑timed 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`. Power‑cycle (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 tearing‑effect 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 anti‑aliased blobs from `../pico/gen_font.py`. `protect-firmware.sh` (hide
|
||||||
|
the firmware files) is mainly for editor mode — appliance mode already keeps the drive read‑only.
|
||||||
2053
pico-cp/app.py
Normal file
25
pico-cp/boot.py
Normal 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
|
|
@ -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
BIN
pico-cp/font_m.bin
Normal file
BIN
pico-cp/font_s.bin
Normal file
139
pico-cp/gen_assets.py
Normal 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()
|
||||||