kicad-visaton-lib moved to ssh://git@src.bundespruefstelle.ch:2022/magdev/kicad-visaton-lib.git. Also corrects the image source label, which named a repository that does not exist: this repo is magdev/boxsim-fileformat-rev, not boxdev/re-boxsim. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
162 lines
7.6 KiB
Markdown
162 lines
7.6 KiB
Markdown
# boxsim
|
|
|
|
Read Boxsim loudspeaker projects and turn their crossovers into KiCad projects.
|
|
|
|
[Boxsim](https://www.visaton.de/) is Visaton's free loudspeaker simulation program. Its project files (`.bpj`) and driver files (`.bch`) use an undocumented container, reverse engineered here. `boxsim` decodes them and generates a KiCad 10 project whose schematic mirrors the crossover you drew in Boxsim, with every part bound to a symbol and footprint from [kicad-visaton-lib](https://src.bundespruefstelle.ch/magdev/kicad-visaton-lib).
|
|
|
|
## Install
|
|
|
|
```bash
|
|
pip install .
|
|
```
|
|
|
|
Nothing is required at runtime beyond Python 3.11+. KiCad 10 is a hard dependency of the verification gate (`boxsim build --verify`) only, and lives on the host.
|
|
|
|
Point the tool at the Visaton library and the Boxsim parts catalogue:
|
|
|
|
```bash
|
|
git clone ssh://git@src.bundespruefstelle.ch:2022/magdev/kicad-visaton-lib.git
|
|
export BOXSIM_KICAD_LIB=/path/to/kicad-visaton-lib
|
|
export BOXSIM_CATALOG="/path/to/BoxSim 2.1 DE/Weichenbauteile/Visatonteile.txt"
|
|
```
|
|
|
|
The symbol library must be in the current KiCad format. Convert an older one with `kicad-cli sym upgrade --force symbols/Visaton.kicad_sym`.
|
|
|
|
## Use
|
|
|
|
```bash
|
|
boxsim decode project.bpj -o project.ini # raw INI payload
|
|
boxsim extract project.bpj -o project.json # structured model, symbols resolved
|
|
boxsim netlist project.bpj # crossover netlist
|
|
boxsim build project.bpj out/ --verify # a KiCad 10 project
|
|
```
|
|
|
|
`build` writes `<name>.kicad_sch`, `<name>.kicad_pro` and project-local `sym-lib-table` / `fp-lib-table`. No board is generated: use KiCad's *Update PCB from Schematic*, which works because every Visaton symbol carries a `Footprint` property.
|
|
|
|
### Docker
|
|
|
|
```bash
|
|
./tools/build-image.sh
|
|
docker run --rm -v "$PWD:/work" boxsim:0.1.0 build project.bpj out/
|
|
```
|
|
|
|
The image bundles the tool, the Visaton library and the catalogue. It contains no KiCad and no `Boxsim.exe`, so `decode`, `extract`, `netlist` and `build` work inside it while `--verify` does not. Run the gate on the host.
|
|
|
|
## The file format
|
|
|
|
| Layer | Content |
|
|
| ----- | ------- |
|
|
| 1 | 8-byte plaintext magic `MyIni2CK` |
|
|
| 2 | XOR against a keystream from a table-shuffled three-LCG generator, seeded (123, 456) for every file ever written |
|
|
| 3 | A custom LZ plus canonical-Huffman codec ("A2F") |
|
|
| 4 | INI-style text: `#Section` headers, `key<TAB>value`, CRLF, German decimal commas, `;`-separated arrays |
|
|
|
|
Files written by Boxsim 1.2 use an older container without the magic and are rejected.
|
|
|
|
### The Krypt keystream
|
|
|
|
Three linear congruential generators feeding a 256-entry shuffle table (Boxsim 2.1: init at VA `0xa12be4`, next byte at `0xa12c9c`). All arithmetic mod 2^32:
|
|
|
|
```text
|
|
init(s1=123, s2=456):
|
|
s3 = (s1 mod 0xFFFE) * (s2 mod 0x7F23)
|
|
256 times: step(); table[i] = s1 + s2
|
|
|
|
step(): s1 = s1*0x10B95 + 1
|
|
s2 = s2*0x10BA9 + 1 (reset to 0 if >= 0xFFFFFFFF)
|
|
s3 = s3*0x1155D + 1
|
|
|
|
next(): step(); i = s3 and 0xFF
|
|
out = table[i]; table[i] = s1 + s2
|
|
return out mod 256
|
|
```
|
|
|
|
### The A2F codec
|
|
|
|
A bit stream read MSB first through a 31-bit accumulator, with symbols from a canonical Huffman code over 356 symbols decoded via a 65536-entry direct lookup table. All code lengths start at 9.
|
|
|
|
| Symbol | Meaning |
|
|
| ------ | ------- |
|
|
| `0x000`-`0x0FF` | literal byte |
|
|
| `0x100` | end of stream |
|
|
| `0x101` | new code lengths follow, rebuild the tables |
|
|
| `0x102` | define N entries of the 64-entry digram dictionary |
|
|
| `0x103`-`0x10A` | match, length 3, distance in k+3 bits |
|
|
| `0x10B`-`0x114` | match, length 4, distance in k+3 bits |
|
|
| `0x115`-`0x123` | match, unary-coded length class, distance in k+3 bits |
|
|
| `0x124`-`0x163` | emit the two bytes of digram dictionary entry k |
|
|
|
|
Distances take `k+3` raw bits biased by `2**(k+3) - 7`, so the classes tile 1.. with no gaps. Code lengths are coded as two zero bits meaning "same as previous", otherwise a nibble plus one, where the value 5 escapes to a second nibble.
|
|
|
|
Boxsim decodes from a staging buffer and reads past the end of the input for the final symbol's lookup window, so the decoder tolerates a bounded number of zero padding bytes. Beyond that a truncated file fails loudly.
|
|
|
|
### The crossover grid
|
|
|
|
The crossover editor is an 11 x 18 node grid. A slot number encodes position and orientation:
|
|
|
|
| Range | Meaning |
|
|
| ----- | ------- |
|
|
| 1-180 | horizontal, 10 per row, joins node(r,c) to node(r,c+1) |
|
|
| 181-367 | vertical, 11 per row, joins node(r,c) to node(r+1,c) |
|
|
| 368-375 | free link, endpoints given by `_nod1` and `_nod2` |
|
|
|
|
Nodes are numbered row-major and zero based: `node = row * 11 + col`.
|
|
|
|
Keys in a `#Weiche<n>` section:
|
|
|
|
| Key | Meaning |
|
|
| --- | ------- |
|
|
| `_Part<N>_typ` | 0 empty, 1 wire, 2 resistor, 3 capacitor, 4 inductor, 5 driver, 6 amplifier input |
|
|
| `_Part<N>_value` | SI units: Ohm, Farad, Henry |
|
|
| `_Part<N>_Rvalue` | inductor DC resistance in Ohm |
|
|
| `_Part<N>_bem` | Visaton series name, matching `Visatonteile.txt` exactly |
|
|
| `_Part<N>_chassisnr` | zero-based driver index |
|
|
| `_Part<N>_verpolt` | polarity, -1 meaning inverted |
|
|
|
|
Other sections: `#General`, `#AktFilter<n>` and `#Aktfilter<n>` (active filters, note the differing case), `#Chassis<n>` (name plus Thiele-Small parameters and measured response arrays), `#Einbau<n>` (enclosure, `Vb` in m³), `#Schallwand<n>` (baffle geometry in m), `#GemGeh` (shared enclosure).
|
|
|
|
## How the parts are resolved
|
|
|
|
`Weichenbauteile/Visatonteile.txt` is the authoritative parts list: 95 parts across 11 series, with inductor DC resistance in the fourth column. Its series strings are exactly the `_bem` values Boxsim writes, so resolution is a lookup rather than a guess.
|
|
|
|
Values are matched numerically against the library rather than formatted into symbol names, because the library's own strings are irregular (`MKP 1.0μF` beside `MKP 10μF`, `SP-Spule 1.0mH (0.6)` beside `LR-Spule 10mH`).
|
|
|
|
- An empty `_bem` is inferred from a sibling part of the same type and value in the same crossover, else from the widest series for that type. Every inference is logged.
|
|
- A `_Rvalue` that differs from the catalogue is reported, never corrected: it is a legitimate edit in Boxsim.
|
|
- A value Visaton does not stock is a hard error naming the nearest stocked values. Nothing is substituted silently.
|
|
|
|
The library contains one symbol the catalogue does not list, `MKT 22μF`. It is a real Visaton part (radial film capacitor, 250 V) that Boxsim does not offer in its picker, so no project can reference it.
|
|
|
|
## Verification
|
|
|
|
ERC only proves a schematic is well formed; it would happily pass one wired to the wrong cells. So `boxsim build --verify` compares the netlist KiCad extracts from the generated sheet against the netlist derived independently from the Boxsim grid, and both derivations must agree.
|
|
|
|
The codec is held to the same standard. `tools/difftest.py` decodes every container in the Boxsim distribution twice, once with the pure Python codec and once by emulating Boxsim's own `ExpandA2F` under Unicorn, and requires byte-identical output:
|
|
|
|
```bash
|
|
.venv/bin/python tools/difftest.py --self-test
|
|
# 134 byte-identical, 270 rejected by both as pre-2.0 containers
|
|
```
|
|
|
|
Both gates are checked against a deliberately broken input so they are known to fail when they should: `--self-test` corrupts a digest, and `test_gate_has_teeth` mis-wires a component.
|
|
|
|
The oracle is development-only. It needs `unicorn`, `pefile` and a copy of `Boxsim.exe`:
|
|
|
|
```bash
|
|
pip install -e '.[oracle,dev]'
|
|
export BOXSIM_EXE="/path/to/Boxsim.exe"
|
|
```
|
|
|
|
## Tests
|
|
|
|
```bash
|
|
pytest # fast, no Boxsim.exe needed
|
|
ruff check boxsim tools tests
|
|
```
|
|
|
|
Tests needing KiCad or the Boxsim distribution skip themselves when those are absent.
|
|
|
|
## Licence
|
|
|
|
MIT.
|