Copyright © 2026 Ulf Bertilsson. Licensed under the GNU General Public License v3.0 or later.
New public name: SIDTrace2MIDI. The historical script
sid2midi.pyremains fully supported, and the newsidtrace2midi.pylauncher is provided as a clearer entrypoint. This tool is SID → MIDI, notmidi2sid: it runs SID player code, traces SID chip register writes, then exports MIDI.
A self-contained Commodore 64 SID → MIDI extraction toolkit with a hardened NMOS 6502/6510 CPU core, PSID/RSID loader, C64 memory/banking model, SID/CIA/VIC register capture, high-resolution MIDI export, and Top-100 HVSC batch tooling for classic/game tunes, demos and cracktros.
For a concise capability overview, see docs/FEATURES.md.
For one file, run:
python3 sidtrace2midi.py tune.sid --auto --seconds 600 --ppq 9600 -o tune.midFor a guided first session, see docs/tutorials/FIRST_30_MINUTES.md; for
batch operations, begin with docs/steps/03_top100_batch_conversion.md.
This project is built for SID player execution and MIDI conversion. It is not a transistor-level C64 emulator. The design goal is to execute enough of the real SID init/play code to capture musically meaningful SID register writes and convert them into editable DAW MIDI: notes, pitch bend, pulse-width movement, filter movement, waveform changes, ADSR metadata, noise/drum extraction and timing.
This release now includes a dedicated documentation tree for module-by-module and step-by-step learning. Start here:
docs/README_INDEX.md
Module documents:
docs/modules/cpu6502.md CPU6502 core, tracing, diagnostics, 6510 port
docs/modules/sid2midi.md SID parser, C64 runtime, SidFrame, MIDI writer
docs/modules/top100_batch.md classic/demo/cracktro batch system
docs/modules/validators.md release, SHA and manifest validators
docs/modules/tools.md helper tools and manifest analysis
docs/modules/tests.md regression test groups
docs/modules/roms.md BASIC/KERNAL/CHARGEN ROM handling
Step documents:
docs/steps/00_end_to_end_pipeline.md
docs/steps/01_validation_and_release_checks.md
docs/steps/02_single_sid_conversion.md
docs/steps/03_top100_batch_conversion.md
docs/steps/04_manifest_triage_and_reruns.md
docs/steps/05_debugging_register_export.md
docs/steps/06_accuracy_boundaries.md
Use these files when you want a slower, clearer explanation of every module and each conversion stage instead of only the quick command examples.
docs/branding/PROJECT_NAME.md
docs/reference/NAMING_AND_COMPATIBILITY.md
docs/reference/RELEASE_NAMING.md
docs/deepdives/SID_REGISTER_TRACE_MODEL.md
docs/deepdives/CPU_TO_MIDI_MAPPING_FLOW.md
docs/tutorials/DAW_IMPORT_GUIDE.md
docs/tutorials/BATCH_RERUN_PLAYBOOK.md
docs/operations/HVSC_SETUP.md
docs/operations/DOCUMENTATION_STYLE_GUIDE.md
This release adds an expanded documentation layer for release building, log reading, file-tree lookup and onboarding:
LOGO_ASCII.txt
docs/branding/ASCII_ART_LOGO.md
docs/architecture/COMPLETE_SYSTEM_MAP.md
docs/howto/MAKE_A_RELEASE.md
docs/howto/READ_THE_OUTPUT_LOGS.md
docs/reference/FILE_TREE.md
docs/reference/GLOSSARY.md
docs/deepdives/WHY_TRACE_NOT_EMULATE_AUDIO.md
docs/operations/DOCUMENTATION_MAINTENANCE.md
docs/tutorials/FIRST_30_MINUTES.md
Use docs/README_INDEX.md as the table of contents when learning the project from scratch.
This release also adds final operator/reference documentation for completing real HVSC runs and publishing releases:
docs/release/FINAL_RELEASE_SUMMARY.md
docs/architecture/DATA_FLOW_DETAILED.md
docs/reference/OUTPUT_FILES.md
docs/reference/ERROR_REASON_MATRIX.md
docs/howto/CONTINUE_FAILED_TOP100_RUN.md
docs/deepdives/REGISTER_TO_MIDI_DECISIONS.md
docs/operations/PERFORMANCE_TUNING.md
docs/operations/FINAL_QA_MATRIX.md
These files explain the full pipeline, output artifacts, error reasons, manifest repair, performance tuning, register-to-MIDI mapping decisions and final QA checklist.
Use the new clear launcher when writing new documentation or commands:
python3 sidtrace2midi.py tune.sid --auto --seconds 600 --ppq 9600 -o tune.midThe old command remains supported for compatibility:
python3 sid2midi.py tune.sid --auto --seconds 600 --ppq 9600 -o tune.midBoth call the same conversion engine.
Core conversion:
cpu6502.py NMOS 6502/6510 CPU core
sid2midi.py PSID/RSID SID-register extractor and MIDI writer
roms/basic.bin C64 BASIC ROM for RSID/KERNAL path
roms/kernal.bin C64 KERNAL ROM for RSID/KERNAL path
roms/chargen.bin C64 character ROM for proper C64 memory map
Validation:
validate_release.sh full syntax/test/smoke/SHA validation
validate_distribution.sh package/CLI/Top-100 distribution validation
validate_nmos_cpu_release.sh CPU-focused validation
tools/verify_sha256.py portable SHA256 verifier
tools/cpu_opcode_coverage_report.py
Examples:
examples/simple_pulse.sid tiny smoke-test SID
run_example.sh converts the smoke-test SID
Top-100 batch tooling:
convert_top100_exact.sh classic/game Top-100 wrapper
convert_top100_hvsc.py classic/game Top-100 resolver/converter
validate_top100_manifest.py
TOP100_AUTO_README.md
convert_top100_demos_exact.sh demo-scene Top-100 wrapper
convert_top100_demos_hvsc.py
validate_top100_demos_manifest.py
TOP100_DEMOS_README.md
convert_top100_cracktros_exact.sh
convert_top100_cracktros_hvsc.py
validate_top100_cracktros_manifest.py
TOP100_CRACKTROS_README.md
This release closes the current CPU + SID2MIDI + Top-100 + logic-order + README/distribution audit chain.
Validated capabilities:
CPU:
- 256/256 opcode handlers present
- 151/151 official NMOS 6502 opcodes covered
- common NMOS undocumented opcodes implemented for SID players/decrunchers
- configurable decimal policy: nmos / binary / adjusted / strict
- safe KIL/JAM handling with strict_jam option
- IRQ + NMI support
- BRK/JAM/unstable opcode diagnostics
- bounded deque trace buffer with exact `trace_seen` / `trace_total` / `trace_dropped` accounting and `clear_trace()`
- 6510 $0000/$0001 processor-port latch, optional CPU-level interception and configurable host side-effect mirroring
SID2MIDI:
- PSID and RSID loading
- PSID v2/v3/v4 metadata handling
- 2SID and 3SID base support
- BASIC SYS scan for simple RSID/BASIC loaders
- C64 RAM/ROM/I/O memory map with CPU-side 6510 banking
- ROM presence reporting and optional --require-roms hard failure
- CIA Timer A/B period detection and improved timer advancement
- dynamic IRQ/KERNAL vector handling
- optional --respect-irq-disable strict IRQ behavior
- named SidFrame access model instead of fragile magic-only tuple indexing
- multi-SID aware loop signature
- robust MIDI VLQ/meta-event writer
- high-resolution MIDI timing, default PPQ 9600
Batch:
- Top-100 classic/game, demo and cracktro wrappers
- strict resolver policy, no broad generic-name collapse
- retry profiles for stubborn init/play paths
- keep-going batch behavior
- manifest JSON/CSV output
- per-song debug logs
- skip-existing with note-count recovery
Python 3.10+ recommended
No third-party Python packages required
macOS or Linux shell for bundled .sh wrappers
HVSC tree required only for Top-100 batch conversion
The release is intentionally dependency-light. Everything required for CPU tests, the smoke SID conversion, SHA verification and normal conversion is inside the package.
unzip sidtrace2midi_documentation_ultra_release.zip
cd sidtrace2midiMake scripts executable if your unzip tool did not preserve mode bits:
chmod +x *.sh *.pyRun the complete distribution check:
./validate_distribution.shRun the full CPU/SID2MIDI validation:
./validate_release.shExpected high-level result:
Ran 78 tests
OK
CPU6502 opcode coverage
handlers: 256/256
official opcodes covered: 151/151
simple_pulse.sid: 6 tracks, 1 notes, 41 CC, PPQ 9600
SHA256SUMS OK
SID2MIDI release validation OK
SID2MIDI distribution validation OK
Verify only file integrity:
python3 tools/verify_sha256.py./run_example.sh /tmp/simple_pulse_sid2midi.midThis converts the bundled tiny SID and proves that CPU, SID loader, ROM path, MIDI writer and output file creation work.
Recommended full-song mode:
python3 sid2midi.py /path/to/tune.sid \
--song 1 \
--auto \
--seconds 600 \
--auto-min-seconds 540 \
--auto-confirm-windows 3 \
--ppq 9600 \
--bpm 125 \
--drumvoice 3 \
--report \
-o tune.midFixed-duration mode:
python3 sid2midi.py /path/to/tune.sid \
--song 1 \
--seconds 180 \
--ppq 9600 \
--bpm 125 \
--drumvoice 3 \
--report \
-o tune_180s.midStrict ROM mode for RSID/KERNAL-sensitive files:
python3 sid2midi.py /path/to/tune.sid \
--auto \
--seconds 600 \
--require-roms \
--report \
-o tune.midStubborn init/play path:
python3 sid2midi.py /path/to/tune.sid \
--seconds 600 \
--max-ins-init 16000000 \
--max-ins-call 1000000 \
--salvage-init \
--report \
-o tune.midDo not put blank lines after a trailing \ in zsh/bash. This is wrong:
./convert_top100_demos_exact.sh \
/Users/ulfbertilsson/Downloads/C64Music \
~/Downloads/c64_top100_demos_midiThe shell will try to execute the path as a command and can print permission denied.
Use one line:
./convert_top100_demos_exact.sh /Users/ulfbertilsson/Downloads/C64Music ~/Downloads/c64_top100_demos_midiOr use backslashes with no blank lines:
./convert_top100_demos_exact.sh \
/Users/ulfbertilsson/Downloads/C64Music \
~/Downloads/c64_top100_demos_midiInput/output:
sidfile input .sid file
-o, --out FILE output .mid path
--song N subtune number, 1-based
--report print conversion and ROM/timing summary
--require-roms fail if required C64 ROMs are missing
Length and timing:
--seconds S maximum render duration
--auto detect loop/full-song length and trim
--auto-min-seconds S ignore false loops below this duration
--auto-confirm-windows N require repeated confirmation windows
--bpm B DAW grid BPM label
--auto-bpm derive BPM label from frame/row settings
--frames-per-row N tracker-style auto-BPM helper
--rows-per-beat N tracker-style auto-BPM helper
--ppq N MIDI pulses per quarter note; default 9600
MIDI mapping:
--drumvoice 1|2|3 route selected voice/noise to channel 10 drums
--no-bend disable pitch bend output
--no-cc disable CC automation output
Safety and compatibility:
--max-ins-init N SID init instruction budget
--max-ins-call N per-frame play/IRQ instruction budget
--max-stuck-frames N abort after repeated over-budget frames
--strict-init make init timeout fatal
--salvage-init continue extraction after init timeout when useful
--respect-irq-disable strict I-flag behavior for KERNAL IRQ path
View exact current help:
python3 sid2midi.py --helpTypical channels:
Channel 1 primary SID voice 1
Channel 2 primary SID voice 2
Channel 3 primary SID voice 3
Channel 10 drums/noise if --drumvoice is selected
Extra channels 2SID/3SID voices when present
Important CC lanes:
CC20 waveform
CC21 sync
CC22 ring modulation
CC23 test bit
CC70 pulse width / PWM
CC73 attack
CC75 decay
CC79 sustain
CC72 release
CC74 filter cutoff
CC71 resonance
CC24 filter mode
CC25 filter routing
CC7 master volume
CC26 $D418 digi activity
Pitch bend is used for SID frequency remainder, slides, vibrato and detune. Keep pitch bend lanes when importing into a DAW.
Recommended DAW import:
Quantize: off
Humanize: off
Pitch bend import: on
CC import: on
Pitch bend range: usually +/-2 semitones
You need a local HVSC checkout, for example:
/Users/ulfbertilsson/Downloads/C64Music
./convert_top100_exact.sh /Users/ulfbertilsson/Downloads/C64Music ~/Downloads/c64_top100_midiValidate:
python3 validate_top100_manifest.py ~/Downloads/c64_top100_midi/top100_manifest.json --min-notes 100./convert_top100_demos_exact.sh /Users/ulfbertilsson/Downloads/C64Music ~/Downloads/c64_top100_demos_midiValidate:
python3 validate_top100_demos_manifest.py ~/Downloads/c64_top100_demos_midi/top100_demos_manifest.json --min-notes 100./convert_top100_cracktros_exact.sh /Users/ulfbertilsson/Downloads/C64Music ~/Downloads/c64_top100_cracktros_midiValidate:
python3 validate_top100_cracktros_manifest.py ~/Downloads/c64_top100_cracktros_midi/top100_cracktros_manifest.json --min-notes 80The exact wrappers use safe defaults:
--auto
--seconds 600
--auto-min-seconds 540
--auto-confirm-windows 3
--ppq 9600
--timeout 300
--skip-existing
--keep-going
--scan-policy preferred-first
Batch retry profiles are used for stubborn tunes. If a tune times out during init, the converter can retry with larger init/call budgets and salvage-init policy rather than failing the whole batch.
Outputs:
*.mid converted MIDI files
*_manifest.json machine-readable manifest
*_manifest.csv spreadsheet-friendly manifest
debug/*.log per-tune debug logs
The Top-100 scripts intentionally avoid dangerous broad fuzzy matching.
- exact seeded paths preferred
- local HVSC auto-fill used when seeds differ across HVSC versions
- generic names such as Intro.sid, Loader.sid, Music.sid and Demo_Tune.sid are not broadly matched
- duplicate resolved SID sources are rejected
- suspicious one-letter junk and helper/test/sfx/basic/picture files are avoided
This prevents the old failure mode where many ranks collapse into the same generic file.
The final CPU pass closes the last audit-risk items around diagnostics and classification:
- trace accounting is derived from
trace_seen - trace_total, sodeque(maxlen=...)eviction cannot create off-by-one dropped counts; trace_limit=0means counter-only tracing,trace_limit=Nonemeans unbounded tracing, andclear_trace()resets all trace counters;OFFICIAL_OPSis regression-tested as the exact 151-opcode NMOS 6502 set, so official JMP/JSR/RTS/BRK/branch opcodes do not inflate illegal-opcode counts;disassemble_at()gives a safe, non-mutating one-line debug view for the current PC or a supplied address;- 6510
$0000/$0001mirroring is explicit viamirror_6510_port_writesandmirror_6510_port_reads.
Decimal ADC/SBC remains intentionally documented as practical NMOS/SID extraction behavior. Strict mode rejects invalid BCD inputs, but the package does not claim transistor-level invalid-BCD analog flag quirks.
This pass is a documentation and packaging closure on top of the logic-order-perfect codebase. It does not remove the previous CPU/SID2MIDI/Top-100 fixes; it makes the release easier to unpack, validate, run and audit.
What this closure adds:
- README.md expanded into a final operator/developer manual
- release name and validation expectations updated to the current package
- Quickstart aligned with the current package and common macOS/zsh usage
- manifest and release notes aligned with the actual file tree
- final audit document for the README/distribution cleanup
- SHA256SUMS regenerated after all documentation changes
- validation re-run from the cleaned package tree
The package is intended to be used directly from a normal Downloads directory. No build step is required.
./validate_distribution.shThis is the highest-level check and runs the practical release contract: syntax, tests, smoke SID conversion, opcode coverage, CLI flag checks, Top-100 wrapper checks, SHA verification and cache cleanup.
For this project, complete means:
- correct release packaging
- deterministic validation
- reproducible SHA file integrity
- tested CPU opcode coverage and diagnostics
- tested SID2MIDI conversion path
- tested frame/timing/event ordering behavior
- integrated classic/demo/cracktro Top-100 batch tooling
- documented accuracy boundary
It does not mean transistor-level emulation. The package remains an instruction-level SID register extractor designed for high-quality MIDI generation.
from cpu6502 import CPU6502
cpu = CPU6502(read, write, intercept_6510_port=True)
cpu.reset()
cycles = cpu.call(0x1000, a=0, x=0, y=0, max_ins=4_000_000)
irq_cycles = cpu.irq()
nmi_cycles = cpu.nmi()
print(cpu.status())Useful constructor options:
CPU6502(
read,
write,
strict_jam=False,
strict_unstable=False,
trace=False,
trace_limit=4096,
ane_magic=0xEE,
lxa_magic=0xEE,
decimal_flags="nmos", # nmos / binary / adjusted / strict
intercept_6510_port=False,
mirror_6510_port_writes=True,
mirror_6510_port_reads=False,
)Diagnostics:
cpu.status()
cpu.step_dump()
cpu.trace_snapshot()
cpu.reset_jam()The CPU core tracks the C64 6510 processor port:
$0000 = data direction
$0001 = data latch
Effective port value:
effective = (port_data & port_dir) | (~port_dir & 0x3F)
sid2midi.py uses this effective port for BASIC/KERNAL/CHARGEN/I/O banking. This prevents split-brain between CPU state and the C64 host memory mapper.
In scope:
- SID init/play execution
- C64 register capture for musical extraction
- instruction-level NMOS 6502/6510 behavior with exact official opcode classification
- common undocumented opcodes
- 6510 banking
- CIA Timer A/B period extraction and practical timer advancement
- KERNAL IRQ exit behavior sufficient for RSID-style players
- high-resolution MIDI event timing
Out of scope:
- transistor-level Visual6502 accuracy
- per-PHI2 bus trace
- VIC badline/sprite DMA cycle stealing
- analog SID filter/audio emulation
- exact unstable-opcode silicon variation
- real digi sample waveform reconstruction
For this project, bounded extraction is preferred over hardware-accurate hangs. JAM/KIL can be made strict for debugging, but safe stop is the default for batch conversion.
Use the batch wrappers first; they include retry profiles. For a single file:
python3 sid2midi.py tune.sid --seconds 600 --max-ins-init 16000000 --salvage-init --report -o tune.midYou probably inserted a blank line after \. Use one physical command line or no blank lines between continued lines.
Run with --report and check ROM status. For strict behavior:
python3 sid2midi.py tune.sid --require-roms --report -o tune.midIncrease minimum loop length:
python3 sid2midi.py tune.sid --auto --seconds 600 --auto-min-seconds 120 -o tune.midDo not strip pitch bend or CC lanes. SID expression is often in bend, PWM and filter CC data rather than only notes.
sidtrace2midi/
├── README.md
├── QUICKSTART.md
├── PROJECT_MANIFEST.md
├── RELEASE_NOTES_NMOS_CPU.md
├── FINAL_CLOSURE_REPORT.md
├── REQUIREMENTS.md
├── SHA256SUMS.txt
├── cpu6502.py
├── sid2midi.py
├── run_example.sh
├── validate_release.sh
├── validate_distribution.sh
├── validate_nmos_cpu_release.sh
├── convert_top100_*.py / .sh
├── validate_top100_*.py
├── TOP100_*_README.md
├── examples/
├── roms/
├── tests/
├── tools/
└── docs/audits/
This release is the completed cleaned distribution for the current SID2MIDI line: CPU closure, SID2MIDI feature closure, audit-finding closure, Top-100 integration and batch-retry closure. The package is intended to be directly unzipped, validated and used on a local HVSC tree.
The last CPU pass fixes the remaining API/diagnostic edge cases from the final audit:
- `OFFICIAL_OPS` is regression-tested as the exact 151 official NMOS 6502 opcodes.
- bounded trace accounting is now exact: `trace_seen - trace_total == trace_dropped`.
- `clear_trace()` resets trace state for long batch/debug sessions.
- direct manual `step()` updates `last_stop_reason` for normal step, BRK and JAM.
- 6510 `$0000/$0001` interception can mirror host write/read side effects when tests or loggers need it.
- unused BCD converter helpers were removed; decimal behavior is documented as practical instruction-level policy, not transistor-level invalid-BCD truth.
- MIDI event order is now explicit and stable: meta, program, CC/bend, note-off, note-on.
- SidFrame is the canonical internal frame model; legacy tuple frames are normalized at API boundaries.
convert()validates frame/timing alignment before MIDI rendering.- Multi-SID frame access no longer depends on raw magic tuple indices.
- Loop detection now prefers the best-evidenced loop candidate instead of the first early tail match.
run_tune()uses a pre-call elapsed-period contract so timer writes affect the next frame cleanly.- Added
docs/audits/SID2MIDI_LOGIC_ORDER_FRAME_TIMING_FINAL_AUDIT.mdand regression tests.
- README now documents the current release name, validation order, shell gotchas, CPU API, SidFrame model, timing/event ordering closure, Top-100 batch workflow, ROM handling and accuracy boundary in one place.
- QUICKSTART is aligned with the final package name and includes the safest one-line demo/classic/cracktro batch commands.
- PROJECT_MANIFEST and RELEASE_NOTES were refreshed so they match the actual cleaned archive.
- SHA256SUMS was regenerated after documentation cleanup.
- Added
docs/audits/README_DISTRIBUTION_FINAL_RELEASE_AUDIT.md.
The Top-100 wrappers now render longer by default because demo/cracktro material often contains short intro loops that can otherwise be accepted too early.
Default batch values:
--seconds 600
--auto-min-seconds 540
--timeout 300
--auto-confirm-windows 3
--ppq 9600
This means the converter may capture up to ten minutes, ignores loop candidates shorter than three minutes, and gives each SID more host time before timing out. You can still override everything from the command line:
./convert_top100_demos_exact.sh /Users/ulfbertilsson/Downloads/C64Music ~/Downloads/c64_top100_demos_midi --seconds 900 --auto-min-seconds 240 --timeout 480Weak salvage outputs are no longer kept as future --skip-existing successes. If a retry profile produces a 0-note or under-min-note MIDI, the batch marks the row failed and removes that weak MIDI file by default. Use --allow-weak-midi only for forensic debugging when you intentionally want to keep such artifacts.
The Top-100 wrappers are intentionally biased toward long, DAW-ready captures:
--seconds 600
--auto-min-seconds 540
--auto-confirm-windows 3
--timeout 300
This means a tune can still loop-detect automatically, but a short or mid-length repeating intro around 90–300 seconds is not accepted as the final song length by default. The batch now tries to produce a near-ten-minute arrangement unless a very strong late loop is found.
Weak salvage outputs are not kept as success. If a retry profile produces 0 notes, 2 notes, or any known note count below --min-notes, the MIDI file is deleted and the row is marked failed unless --allow-weak-midi is explicitly set. If the SID has multiple subtunes, failed/weak song 1 now triggers automatic subtune scanning up to --song-scan-limit, so demo files where song 1 is a loader/wait loop can still resolve to a musical subtune.
The Top-100 wrappers now use a fast probe before spending the expensive retry stack on alternate subtunes. This fixes pathological demo SIDs where song 1 and many alternate songs all enter loader/wait-loop code and salvage-init returns 0-note MIDI.
Default alternate-subtune probe policy:
--subtune-probe-seconds 45
--subtune-probe-timeout 60
--subtune-probe-min-notes 20
--subtune-zero-streak-limit 4
The seeded/requested song still gets the full retry stack. Alternate subtunes are promoted to full 600-second rendering only if the cheap probe shows real musical activity. Use --exhaustive-subtune-scan when you intentionally want the old slow forensic behavior for every subtune.
For log lines like notes=0 or repeated salvage-init over many subtunes, this release now stops early and marks the tune failed instead of wasting time and keeping fake MIDI.
This release adds one more protection for pathological demo SIDs where alternate
subtune probes fail or time out without even producing a 0 notes report. The
batch engine now has two independent guards:
--subtune-zero-streak-limit 4
--subtune-bad-streak-limit 6
--subtune-scan-time-budget 240
zero-streak stops obvious repeated 0-note loader subtunes. bad-streak also
stops repeated probe failures, timeouts, unknown note counts and under-threshold
weak probes. subtune-scan-time-budget caps wall-clock probing time per SID so a
single broken demo file cannot stall the whole Top-100 run.
The manifest now includes reason, subtune_attempts and probe_attempts, so a
failed row explains whether it stopped because of weak notes, converter failure,
zero-streak, bad-streak or probe budget. Failed weak outputs are deleted by
default and no longer become future skip-existing false successes.
The Top-100 wrappers are tuned for long unattended HVSC runs. The default policy is now intentionally conservative:
- render target:
--seconds 600 - loop acceptance floor:
--auto-min-seconds 540 - loop confirmation:
--auto-confirm-windows 3 - alternate-subtune quick probe before expensive retries
- stop alternate scan on repeated weak/zero probes
- cap expensive alternate full renders with
--max-full-subtune-renders 4 - automatic
salvage-initretry is not enabled by default
salvage-init is useful as a forensic/manual tool, but in large demo batches it
can turn loader/wait-loop subtunes into 0-note placeholder MIDI files. The batch
engine therefore treats salvage as opt-in:
# normal safe batch, recommended
./convert_top100_demos_exact.sh /Users/ulfbertilsson/Downloads/C64Music ~/Downloads/c64_top100_demos_midi
# only when manually investigating one stubborn SID/rank
python3 convert_top100_demos_hvsc.py \
--hvsc /Users/ulfbertilsson/Downloads/C64Music \
--out ~/Downloads/c64_top100_demos_midi \
--start-at 63 --stop-after 1 \
--salvage-retry \
--debugAfter a long run, inspect the manifest and get targeted rerun commands:
python3 tools/analyze_top100_manifest.py \
~/Downloads/c64_top100_demos_midi/top100_demos_manifest.json \
--emit-rerunThe analyzer groups reason, profile, weak rows, failed rows, probe counts and
full-render counts. It does not modify files.
The final Top-100 batch defaults are designed to avoid wasting many minutes on
loader subtunes that repeatedly produce notes=0 through salvage-init.
Default automatic retries are now:
defaultlarge-initlarge-init-large-call
salvage-init is available only when explicitly requested with
--salvage-retry or when manually passing --salvage-init. This keeps normal
Top-100 classic/demo/cracktro runs fast and prevents 0-note placeholder MIDIs
from becoming future --skip-existing false successes.
The subtune scanner also has a hard cap for expensive full alternate renders:
--max-full-subtune-renders 4
For post-run triage, use:
python3 tools/analyze_top100_manifest.py \
~/Downloads/c64_top100_demos_midi/top100_demos_manifest.json \
--emit-rerunThis prints grouped failure reasons and exact rerun commands for weak/failed ranks.
This release closes the remaining low-level audit items in the combined cpu6502.py + sid2midi.py path:
- CPU trace counters now have explicit semantics:
trace_seenis lifetime traced instructions,trace_total/trace_lenare retained rows, andtrace_dropped = trace_seen - trace_total. OFFICIAL_OPSis locked to the full 151 official NMOS 6502 opcodes, so diagnostics no longer misclassify BRK/JSR/JMP/RTS/RTI/branches as illegal.- 6510 port mirror-read failures are counted via
port_mirror_read_errorswithout overriding the CPU-owned$0000/$0001port state. - Malformed legacy frame tuples now fail loudly at helper boundaries instead of silently producing zero-register MIDI. Internal rendering still normalizes to
SidFrame. --cia-advance-modemakes the instruction-level CIA ordering explicit: defaultpre_irq_latchmodels an already-latched IRQ source before the handler;post_callexists for stricter debugging experiments.--export-register-jsonwrites the raw capturedSidFrameregister stream plusfcyctiming metadata for loop/debug/regression analysis.
The converter remains intentionally instruction-level SID-register extraction, not a transistor-level VIC/CIA/SID emulator.
This release adds a final batch/distribution polish pass:
- Direct
convert_top25_hvsc.py --top Nnow means “attempt N ranked entries” unless--limitis explicitly supplied. --no-delete-weak-midiis available for forensic/debug output preservation. Production batch defaults still delete weak/0-note MIDI files so future--skip-existingruns do not keep false successes.validate_top100_manifest.pynow validates manifest schema in addition to weak notes, missing MIDI, duplicate SID sources and suspicious short generic matches.run_full_tests.shdelegates totools/run_test_files.pyfor isolated per-file regression execution.- Documentation now describes the engine as cycle-stamped instruction-level SID-register capture, not full transistor-level C64 emulation.
_____ _____ _____ _______ ___ __ __ _____ _____ _____ _____
/ ____|_ _| __ \__ __| |__ \ \/ |_ _| __ \_ _|/ ____|
| (___ | | | | | | | |_ __ __ _ ___ ___ ) | \ / | | | | | | || | | (___
\___ \ | | | | | | | | '__/ _` |/ __/ _ \ / /| |\/| | | | | | | || | \___ \
____) |_| |_| |__| | | | | | (_| | (_| __// /_| | | |_| |_| |__| || |_ ____) |
|_____/|_____|_____/ |_|_| \__,_|\___\___|____|_| |_|_____|_____/_____|_____/
SID / PSID / RSID -> C64 player execution -> SID register trace -> MIDI
The logo is also available as LOGO_ASCII.txt and documented in docs/branding/ASCII_ART_LOGO.md.