trackmod 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trackmod-0.2.0/.gitignore +20 -0
- trackmod-0.2.0/CHANGELOG.md +21 -0
- trackmod-0.2.0/LICENSE +21 -0
- trackmod-0.2.0/PKG-INFO +170 -0
- trackmod-0.2.0/README.md +140 -0
- trackmod-0.2.0/docs/README.md +50 -0
- trackmod-0.2.0/docs/contributing/architecture.md +103 -0
- trackmod-0.2.0/docs/contributing/development.md +91 -0
- trackmod-0.2.0/docs/contributing/documentation.md +121 -0
- trackmod-0.2.0/docs/contributing/releasing.md +72 -0
- trackmod-0.2.0/docs/formats/README.md +76 -0
- trackmod-0.2.0/docs/formats/it.md +297 -0
- trackmod-0.2.0/docs/formats/mod.md +238 -0
- trackmod-0.2.0/docs/formats/s3m.md +287 -0
- trackmod-0.2.0/docs/formats/st.md +213 -0
- trackmod-0.2.0/docs/formats/xm.md +281 -0
- trackmod-0.2.0/docs/guide/budgeting.md +40 -0
- trackmod-0.2.0/docs/guide/converting.md +52 -0
- trackmod-0.2.0/docs/guide/instruments.md +95 -0
- trackmod-0.2.0/docs/guide/reading.md +90 -0
- trackmod-0.2.0/docs/guide/samples.md +79 -0
- trackmod-0.2.0/docs/guide/writing.md +62 -0
- trackmod-0.2.0/docs/reference/effects.md +102 -0
- trackmod-0.2.0/docs/reference/limits.md +266 -0
- trackmod-0.2.0/docs/reference/model.md +322 -0
- trackmod-0.2.0/docs/reference/volume.md +112 -0
- trackmod-0.2.0/pyproject.toml +110 -0
- trackmod-0.2.0/src/trackmod/__init__.py +79 -0
- trackmod-0.2.0/src/trackmod/binary/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/binary/bits.py +35 -0
- trackmod-0.2.0/src/trackmod/binary/cursor.py +101 -0
- trackmod-0.2.0/src/trackmod/binary/layout.py +12 -0
- trackmod-0.2.0/src/trackmod/binary/nibble.py +46 -0
- trackmod-0.2.0/src/trackmod/binary/pcm/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/binary/pcm/blocks.py +20 -0
- trackmod-0.2.0/src/trackmod/binary/pcm/codec.py +53 -0
- trackmod-0.2.0/src/trackmod/binary/pcm/encoding.py +13 -0
- trackmod-0.2.0/src/trackmod/binary/pcm/quantize.py +19 -0
- trackmod-0.2.0/src/trackmod/binary/pcm/sign.py +39 -0
- trackmod-0.2.0/src/trackmod/binary/records/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/binary/records/field.py +50 -0
- trackmod-0.2.0/src/trackmod/binary/records/record.py +99 -0
- trackmod-0.2.0/src/trackmod/binary/records/values.py +44 -0
- trackmod-0.2.0/src/trackmod/binary/text.py +31 -0
- trackmod-0.2.0/src/trackmod/binary/volume.py +114 -0
- trackmod-0.2.0/src/trackmod/binary/warnings.py +40 -0
- trackmod-0.2.0/src/trackmod/core/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/core/effects/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/core/effects/catalog.py +35 -0
- trackmod-0.2.0/src/trackmod/core/effects/effect.py +17 -0
- trackmod-0.2.0/src/trackmod/core/envelopes/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/core/envelopes/curve.py +85 -0
- trackmod-0.2.0/src/trackmod/core/envelopes/envelope.py +43 -0
- trackmod-0.2.0/src/trackmod/core/envelopes/kind.py +10 -0
- trackmod-0.2.0/src/trackmod/core/envelopes/point.py +16 -0
- trackmod-0.2.0/src/trackmod/core/envelopes/repair.py +85 -0
- trackmod-0.2.0/src/trackmod/core/envelopes/span.py +22 -0
- trackmod-0.2.0/src/trackmod/core/instruments/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/core/instruments/behavior.py +30 -0
- trackmod-0.2.0/src/trackmod/core/instruments/fade.py +61 -0
- trackmod-0.2.0/src/trackmod/core/instruments/instrument.py +122 -0
- trackmod-0.2.0/src/trackmod/core/instruments/keymap.py +38 -0
- trackmod-0.2.0/src/trackmod/core/instruments/repair.py +49 -0
- trackmod-0.2.0/src/trackmod/core/instruments/transfer.py +95 -0
- trackmod-0.2.0/src/trackmod/core/instruments/unit.py +42 -0
- trackmod-0.2.0/src/trackmod/core/notes/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/core/notes/checks.py +33 -0
- trackmod-0.2.0/src/trackmod/core/notes/codec.py +24 -0
- trackmod-0.2.0/src/trackmod/core/notes/command.py +20 -0
- trackmod-0.2.0/src/trackmod/core/notes/pitch.py +93 -0
- trackmod-0.2.0/src/trackmod/core/patterns/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/core/patterns/builder.py +59 -0
- trackmod-0.2.0/src/trackmod/core/patterns/cell.py +22 -0
- trackmod-0.2.0/src/trackmod/core/patterns/codec.py +38 -0
- trackmod-0.2.0/src/trackmod/core/patterns/column.py +19 -0
- trackmod-0.2.0/src/trackmod/core/patterns/grid.py +148 -0
- trackmod-0.2.0/src/trackmod/core/patterns/repair.py +36 -0
- trackmod-0.2.0/src/trackmod/core/repairs/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/core/repairs/report.py +47 -0
- trackmod-0.2.0/src/trackmod/core/samples/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/core/samples/depth.py +36 -0
- trackmod-0.2.0/src/trackmod/core/samples/loop.py +58 -0
- trackmod-0.2.0/src/trackmod/core/samples/repair.py +86 -0
- trackmod-0.2.0/src/trackmod/core/samples/sample.py +140 -0
- trackmod-0.2.0/src/trackmod/core/samples/vibrato.py +22 -0
- trackmod-0.2.0/src/trackmod/core/songs/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/core/songs/order.py +37 -0
- trackmod-0.2.0/src/trackmod/core/songs/playback.py +17 -0
- trackmod-0.2.0/src/trackmod/core/songs/repair.py +20 -0
- trackmod-0.2.0/src/trackmod/core/songs/song.py +67 -0
- trackmod-0.2.0/src/trackmod/core/timing/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/core/timing/clock.py +50 -0
- trackmod-0.2.0/src/trackmod/core/timing/lattice.py +122 -0
- trackmod-0.2.0/src/trackmod/core/timing/timing.py +14 -0
- trackmod-0.2.0/src/trackmod/core/timing/timings.py +54 -0
- trackmod-0.2.0/src/trackmod/core/voices/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/core/voices/convert.py +110 -0
- trackmod-0.2.0/src/trackmod/core/voices/voices.py +68 -0
- trackmod-0.2.0/src/trackmod/core/volumes/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/core/volumes/checks.py +41 -0
- trackmod-0.2.0/src/trackmod/core/volumes/codec.py +37 -0
- trackmod-0.2.0/src/trackmod/core/volumes/command.py +60 -0
- trackmod-0.2.0/src/trackmod/limits/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/limits/bound.py +42 -0
- trackmod-0.2.0/src/trackmod/limits/capability.py +45 -0
- trackmod-0.2.0/src/trackmod/limits/capacity.py +41 -0
- trackmod-0.2.0/src/trackmod/limits/checklist.py +30 -0
- trackmod-0.2.0/src/trackmod/limits/compliance.py +22 -0
- trackmod-0.2.0/src/trackmod/limits/error.py +31 -0
- trackmod-0.2.0/src/trackmod/limits/guard.py +13 -0
- trackmod-0.2.0/src/trackmod/limits/reach.py +35 -0
- trackmod-0.2.0/src/trackmod/limits/table.py +99 -0
- trackmod-0.2.0/src/trackmod/limits/tier.py +14 -0
- trackmod-0.2.0/src/trackmod/limits/violation.py +40 -0
- trackmod-0.2.0/src/trackmod/module/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/module/instrument.py +96 -0
- trackmod-0.2.0/src/trackmod/module/protocol.py +112 -0
- trackmod-0.2.0/src/trackmod/module/provenance.py +39 -0
- trackmod-0.2.0/src/trackmod/module/reaching.py +44 -0
- trackmod-0.2.0/src/trackmod/module/size.py +33 -0
- trackmod-0.2.0/src/trackmod/module/storage.py +106 -0
- trackmod-0.2.0/src/trackmod/py.typed +0 -0
- trackmod-0.2.0/src/trackmod/schema/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/schema/array.py +52 -0
- trackmod-0.2.0/src/trackmod/schema/config.py +6 -0
- trackmod-0.2.0/src/trackmod/schema/scalars.py +39 -0
- trackmod-0.2.0/src/trackmod/spec/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/spec/application.py +13 -0
- trackmod-0.2.0/src/trackmod/spec/clock.py +7 -0
- trackmod-0.2.0/src/trackmod/spec/grid.py +14 -0
- trackmod-0.2.0/src/trackmod/spec/levels.py +17 -0
- trackmod-0.2.0/src/trackmod/spec/pitch.py +24 -0
- trackmod-0.2.0/src/trackmod/spec/text.py +5 -0
- trackmod-0.2.0/src/trackmod/spec/volumes.py +8 -0
- trackmod-0.2.0/src/trackmod/spec/width.py +15 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/checks.py +65 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/layout/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/layout/file.py +10 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/layout/sample.py +17 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/note.py +122 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/patterns/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/patterns/packer.py +94 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/patterns/parser.py +109 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/patterns/sizing.py +11 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/reading.py +140 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/samples/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/samples/parser.py +87 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/samples/writer.py +122 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/sizing.py +32 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/spec/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/spec/cells.py +19 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/spec/defaults.py +7 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/spec/periods.py +34 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/spec/ranges.py +21 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/spec/sizes.py +11 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/spec/storage.py +9 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/tuning.py +31 -0
- trackmod-0.2.0/src/trackmod/trackers/amiga/writing.py +50 -0
- trackmod-0.2.0/src/trackmod/trackers/it/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/it/addressing.py +29 -0
- trackmod-0.2.0/src/trackmod/trackers/it/checks.py +120 -0
- trackmod-0.2.0/src/trackmod/trackers/it/detection.py +15 -0
- trackmod-0.2.0/src/trackmod/trackers/it/effects/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/it/effects/catalog.py +115 -0
- trackmod-0.2.0/src/trackmod/trackers/it/effects/command.py +59 -0
- trackmod-0.2.0/src/trackmod/trackers/it/extensions.py +118 -0
- trackmod-0.2.0/src/trackmod/trackers/it/fade.py +19 -0
- trackmod-0.2.0/src/trackmod/trackers/it/instrument_file.py +182 -0
- trackmod-0.2.0/src/trackmod/trackers/it/instruments/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/it/instruments/envelope.py +109 -0
- trackmod-0.2.0/src/trackmod/trackers/it/instruments/keymap.py +53 -0
- trackmod-0.2.0/src/trackmod/trackers/it/instruments/parser.py +100 -0
- trackmod-0.2.0/src/trackmod/trackers/it/instruments/writer.py +82 -0
- trackmod-0.2.0/src/trackmod/trackers/it/layout/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/it/layout/envelope.py +51 -0
- trackmod-0.2.0/src/trackmod/trackers/it/layout/file.py +38 -0
- trackmod-0.2.0/src/trackmod/trackers/it/layout/instrument.py +54 -0
- trackmod-0.2.0/src/trackmod/trackers/it/layout/pattern.py +14 -0
- trackmod-0.2.0/src/trackmod/trackers/it/layout/sample.py +34 -0
- trackmod-0.2.0/src/trackmod/trackers/it/limits.py +8 -0
- trackmod-0.2.0/src/trackmod/trackers/it/message.py +50 -0
- trackmod-0.2.0/src/trackmod/trackers/it/module.py +229 -0
- trackmod-0.2.0/src/trackmod/trackers/it/note.py +43 -0
- trackmod-0.2.0/src/trackmod/trackers/it/panning.py +12 -0
- trackmod-0.2.0/src/trackmod/trackers/it/parser.py +309 -0
- trackmod-0.2.0/src/trackmod/trackers/it/patterns/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/it/patterns/cost.py +16 -0
- trackmod-0.2.0/src/trackmod/trackers/it/patterns/encoded.py +9 -0
- trackmod-0.2.0/src/trackmod/trackers/it/patterns/memory.py +19 -0
- trackmod-0.2.0/src/trackmod/trackers/it/patterns/packer.py +178 -0
- trackmod-0.2.0/src/trackmod/trackers/it/patterns/parser.py +211 -0
- trackmod-0.2.0/src/trackmod/trackers/it/patterns/sizing.py +117 -0
- trackmod-0.2.0/src/trackmod/trackers/it/patterns/width.py +36 -0
- trackmod-0.2.0/src/trackmod/trackers/it/samples/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/it/samples/compression.py +159 -0
- trackmod-0.2.0/src/trackmod/trackers/it/samples/parser.py +288 -0
- trackmod-0.2.0/src/trackmod/trackers/it/samples/writer.py +97 -0
- trackmod-0.2.0/src/trackmod/trackers/it/settings.py +68 -0
- trackmod-0.2.0/src/trackmod/trackers/it/sizing.py +55 -0
- trackmod-0.2.0/src/trackmod/trackers/it/spec/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/it/spec/capacities.py +122 -0
- trackmod-0.2.0/src/trackmod/trackers/it/spec/cells.py +52 -0
- trackmod-0.2.0/src/trackmod/trackers/it/spec/defaults.py +24 -0
- trackmod-0.2.0/src/trackmod/trackers/it/spec/effects.py +19 -0
- trackmod-0.2.0/src/trackmod/trackers/it/spec/extensions.py +13 -0
- trackmod-0.2.0/src/trackmod/trackers/it/spec/flags.py +62 -0
- trackmod-0.2.0/src/trackmod/trackers/it/spec/identity.py +14 -0
- trackmod-0.2.0/src/trackmod/trackers/it/spec/orders.py +5 -0
- trackmod-0.2.0/src/trackmod/trackers/it/spec/ranges.py +56 -0
- trackmod-0.2.0/src/trackmod/trackers/it/spec/sizes.py +20 -0
- trackmod-0.2.0/src/trackmod/trackers/it/spec/storage.py +27 -0
- trackmod-0.2.0/src/trackmod/trackers/it/spec/volume.py +51 -0
- trackmod-0.2.0/src/trackmod/trackers/it/timing.py +10 -0
- trackmod-0.2.0/src/trackmod/trackers/it/version.py +100 -0
- trackmod-0.2.0/src/trackmod/trackers/it/writer.py +133 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/detection.py +11 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/dialect.py +60 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/effects/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/effects/catalog.py +106 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/effects/command.py +63 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/layout/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/layout/file.py +17 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/limits.py +8 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/module.py +261 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/parser.py +102 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/settings.py +27 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/sizing.py +9 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/spec/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/spec/capacities.py +58 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/spec/dialects.py +34 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/spec/effects.py +19 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/spec/identity.py +12 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/spec/ranges.py +21 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/spec/sizes.py +13 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/spec/storage.py +17 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/tag.py +79 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/timing.py +6 -0
- trackmod-0.2.0/src/trackmod/trackers/mod/writer.py +58 -0
- trackmod-0.2.0/src/trackmod/trackers/registry.py +277 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/channels.py +38 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/checks.py +91 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/detection.py +10 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/effects/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/effects/catalog.py +105 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/effects/command.py +54 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/layout/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/layout/file.py +36 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/layout/instrument.py +28 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/layout/pattern.py +10 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/limits.py +8 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/module.py +252 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/note.py +101 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/panning.py +22 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/parapointers.py +41 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/parser.py +233 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/patterns/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/patterns/packer.py +103 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/patterns/parser.py +144 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/patterns/sizing.py +37 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/placement.py +89 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/samples/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/samples/parser.py +211 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/samples/writer.py +102 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/settings.py +67 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/sizing.py +44 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/spec/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/spec/capacities.py +100 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/spec/cells.py +45 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/spec/defaults.py +17 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/spec/effects.py +19 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/spec/flags.py +57 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/spec/identity.py +18 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/spec/keys.py +16 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/spec/orders.py +4 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/spec/ranges.py +58 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/spec/sizes.py +20 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/spec/storage.py +35 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/spec/volume.py +24 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/timing.py +10 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/version.py +98 -0
- trackmod-0.2.0/src/trackmod/trackers/s3m/writer.py +176 -0
- trackmod-0.2.0/src/trackmod/trackers/st/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/st/detection.py +36 -0
- trackmod-0.2.0/src/trackmod/trackers/st/effects/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/st/effects/catalog.py +110 -0
- trackmod-0.2.0/src/trackmod/trackers/st/effects/command.py +25 -0
- trackmod-0.2.0/src/trackmod/trackers/st/layout/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/st/layout/file.py +15 -0
- trackmod-0.2.0/src/trackmod/trackers/st/limits.py +8 -0
- trackmod-0.2.0/src/trackmod/trackers/st/module.py +244 -0
- trackmod-0.2.0/src/trackmod/trackers/st/parser.py +95 -0
- trackmod-0.2.0/src/trackmod/trackers/st/settings.py +20 -0
- trackmod-0.2.0/src/trackmod/trackers/st/sizing.py +9 -0
- trackmod-0.2.0/src/trackmod/trackers/st/spec/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/st/spec/capacities.py +42 -0
- trackmod-0.2.0/src/trackmod/trackers/st/spec/defaults.py +3 -0
- trackmod-0.2.0/src/trackmod/trackers/st/spec/effects.py +10 -0
- trackmod-0.2.0/src/trackmod/trackers/st/spec/identity.py +3 -0
- trackmod-0.2.0/src/trackmod/trackers/st/spec/ranges.py +21 -0
- trackmod-0.2.0/src/trackmod/trackers/st/spec/sizes.py +14 -0
- trackmod-0.2.0/src/trackmod/trackers/st/spec/storage.py +17 -0
- trackmod-0.2.0/src/trackmod/trackers/st/timing.py +10 -0
- trackmod-0.2.0/src/trackmod/trackers/st/writer.py +51 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/addressing.py +22 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/checks.py +122 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/detection.py +20 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/effects/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/effects/catalog.py +113 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/effects/command.py +62 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/fade.py +19 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/instrument_file.py +188 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/instruments/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/instruments/envelope.py +118 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/instruments/group.py +45 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/instruments/grouping.py +100 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/instruments/keymap.py +26 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/instruments/parser.py +83 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/instruments/writer.py +144 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/layout/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/layout/envelope.py +75 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/layout/file.py +30 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/layout/instrument.py +79 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/layout/pattern.py +15 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/layout/sample.py +21 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/limits.py +8 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/module.py +246 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/note.py +83 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/parser.py +166 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/patterns/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/patterns/encoded.py +32 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/patterns/packer.py +80 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/patterns/parser.py +163 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/patterns/sizing.py +37 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/samples/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/samples/parser.py +139 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/samples/writer.py +73 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/settings.py +23 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/sizing.py +52 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/spec/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/spec/capacities.py +110 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/spec/cells.py +36 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/spec/defaults.py +13 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/spec/effects.py +19 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/spec/flags.py +37 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/spec/identity.py +13 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/spec/ranges.py +70 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/spec/sizes.py +32 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/spec/storage.py +27 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/spec/tuning.py +12 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/spec/volume.py +67 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/timing.py +10 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/tuning.py +73 -0
- trackmod-0.2.0/src/trackmod/trackers/xm/writer.py +62 -0
- trackmod-0.2.0/src/trackmod/utils/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/utils/arithmetic.py +47 -0
- trackmod-0.2.0/src/trackmod/wave/__init__.py +0 -0
- trackmod-0.2.0/src/trackmod/wave/chunks.py +77 -0
- trackmod-0.2.0/src/trackmod/wave/layout.py +88 -0
- trackmod-0.2.0/src/trackmod/wave/parser.py +225 -0
- trackmod-0.2.0/src/trackmod/wave/settings.py +25 -0
- trackmod-0.2.0/src/trackmod/wave/spec.py +64 -0
- trackmod-0.2.0/src/trackmod/wave/writer.py +213 -0
- trackmod-0.2.0/tests/binary/test_bits.py +33 -0
- trackmod-0.2.0/tests/binary/test_cursor.py +65 -0
- trackmod-0.2.0/tests/binary/test_nibble.py +37 -0
- trackmod-0.2.0/tests/binary/test_pcm.py +125 -0
- trackmod-0.2.0/tests/binary/test_record.py +79 -0
- trackmod-0.2.0/tests/binary/test_text.py +34 -0
- trackmod-0.2.0/tests/binary/test_values.py +39 -0
- trackmod-0.2.0/tests/binary/test_volume.py +91 -0
- trackmod-0.2.0/tests/conftest.py +208 -0
- trackmod-0.2.0/tests/core/test_clock.py +47 -0
- trackmod-0.2.0/tests/core/test_curve.py +80 -0
- trackmod-0.2.0/tests/core/test_envelopes.py +42 -0
- trackmod-0.2.0/tests/core/test_fade.py +79 -0
- trackmod-0.2.0/tests/core/test_instruments.py +61 -0
- trackmod-0.2.0/tests/core/test_notes.py +72 -0
- trackmod-0.2.0/tests/core/test_patterns.py +173 -0
- trackmod-0.2.0/tests/core/test_repairs.py +205 -0
- trackmod-0.2.0/tests/core/test_samples.py +115 -0
- trackmod-0.2.0/tests/core/test_songs.py +111 -0
- trackmod-0.2.0/tests/core/test_timing.py +98 -0
- trackmod-0.2.0/tests/core/test_transfer.py +127 -0
- trackmod-0.2.0/tests/core/test_voices.py +117 -0
- trackmod-0.2.0/tests/core/test_volumes.py +59 -0
- trackmod-0.2.0/tests/limits/test_limits.py +212 -0
- trackmod-0.2.0/tests/limits/test_tables.py +152 -0
- trackmod-0.2.0/tests/module/test_provenance.py +32 -0
- trackmod-0.2.0/tests/module/test_storage.py +52 -0
- trackmod-0.2.0/tests/test_boundaries.py +95 -0
- trackmod-0.2.0/tests/test_formats.py +1272 -0
- trackmod-0.2.0/tests/test_package.py +147 -0
- trackmod-0.2.0/tests/trackers/amiga/conftest.py +112 -0
- trackmod-0.2.0/tests/trackers/amiga/test_patterns.py +126 -0
- trackmod-0.2.0/tests/trackers/amiga/test_periods.py +139 -0
- trackmod-0.2.0/tests/trackers/amiga/test_samples.py +173 -0
- trackmod-0.2.0/tests/trackers/it/conftest.py +33 -0
- trackmod-0.2.0/tests/trackers/it/test_addressing.py +97 -0
- trackmod-0.2.0/tests/trackers/it/test_compression.py +106 -0
- trackmod-0.2.0/tests/trackers/it/test_effects.py +55 -0
- trackmod-0.2.0/tests/trackers/it/test_extensions.py +185 -0
- trackmod-0.2.0/tests/trackers/it/test_instrument_file.py +151 -0
- trackmod-0.2.0/tests/trackers/it/test_instruments.py +23 -0
- trackmod-0.2.0/tests/trackers/it/test_layout.py +55 -0
- trackmod-0.2.0/tests/trackers/it/test_limits.py +250 -0
- trackmod-0.2.0/tests/trackers/it/test_message.py +71 -0
- trackmod-0.2.0/tests/trackers/it/test_module.py +274 -0
- trackmod-0.2.0/tests/trackers/it/test_panning.py +32 -0
- trackmod-0.2.0/tests/trackers/it/test_patterns.py +266 -0
- trackmod-0.2.0/tests/trackers/it/test_samples.py +270 -0
- trackmod-0.2.0/tests/trackers/it/test_version.py +63 -0
- trackmod-0.2.0/tests/trackers/mod/conftest.py +63 -0
- trackmod-0.2.0/tests/trackers/mod/test_dialect.py +82 -0
- trackmod-0.2.0/tests/trackers/mod/test_effects.py +86 -0
- trackmod-0.2.0/tests/trackers/mod/test_layout.py +63 -0
- trackmod-0.2.0/tests/trackers/mod/test_limits.py +144 -0
- trackmod-0.2.0/tests/trackers/mod/test_module.py +324 -0
- trackmod-0.2.0/tests/trackers/mod/test_timing.py +31 -0
- trackmod-0.2.0/tests/trackers/s3m/conftest.py +259 -0
- trackmod-0.2.0/tests/trackers/s3m/test_channels.py +35 -0
- trackmod-0.2.0/tests/trackers/s3m/test_effects.py +70 -0
- trackmod-0.2.0/tests/trackers/s3m/test_layout.py +54 -0
- trackmod-0.2.0/tests/trackers/s3m/test_limits.py +241 -0
- trackmod-0.2.0/tests/trackers/s3m/test_module.py +460 -0
- trackmod-0.2.0/tests/trackers/s3m/test_notes.py +57 -0
- trackmod-0.2.0/tests/trackers/s3m/test_panning.py +27 -0
- trackmod-0.2.0/tests/trackers/s3m/test_patterns.py +176 -0
- trackmod-0.2.0/tests/trackers/s3m/test_placement.py +54 -0
- trackmod-0.2.0/tests/trackers/s3m/test_samples.py +240 -0
- trackmod-0.2.0/tests/trackers/s3m/test_timing.py +27 -0
- trackmod-0.2.0/tests/trackers/s3m/test_volume.py +42 -0
- trackmod-0.2.0/tests/trackers/st/conftest.py +62 -0
- trackmod-0.2.0/tests/trackers/st/test_detection.py +99 -0
- trackmod-0.2.0/tests/trackers/st/test_effects.py +47 -0
- trackmod-0.2.0/tests/trackers/st/test_layout.py +30 -0
- trackmod-0.2.0/tests/trackers/st/test_limits.py +58 -0
- trackmod-0.2.0/tests/trackers/st/test_module.py +137 -0
- trackmod-0.2.0/tests/trackers/test_detection.py +33 -0
- trackmod-0.2.0/tests/trackers/xm/conftest.py +124 -0
- trackmod-0.2.0/tests/trackers/xm/test_effects.py +76 -0
- trackmod-0.2.0/tests/trackers/xm/test_instrument_file.py +186 -0
- trackmod-0.2.0/tests/trackers/xm/test_layout.py +75 -0
- trackmod-0.2.0/tests/trackers/xm/test_limits.py +274 -0
- trackmod-0.2.0/tests/trackers/xm/test_module.py +446 -0
- trackmod-0.2.0/tests/trackers/xm/test_patterns.py +268 -0
- trackmod-0.2.0/tests/trackers/xm/test_samples.py +74 -0
- trackmod-0.2.0/tests/trackers/xm/test_timing.py +34 -0
- trackmod-0.2.0/tests/trackers/xm/test_tuning.py +85 -0
- trackmod-0.2.0/tests/wave/test_chunks.py +68 -0
- trackmod-0.2.0/tests/wave/test_samples.py +241 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# TrackMod
|
|
2
|
+
|
|
3
|
+
## v0.2.0 [2026-09-25]
|
|
4
|
+
|
|
5
|
+
* Added support for Amiga ProTracker and Soundtracker (`.mod`) and Scream Tracker 3 (`.s3m`).
|
|
6
|
+
* Added saving and loading sounds as `.wav` files, keeping loops, tuning, volume and panning.
|
|
7
|
+
* Supported modules that go beyond their tracker's original limits.
|
|
8
|
+
* Fixed FastTracker 2 modules losing or mixing up sounds when saved, and names with special characters changing.
|
|
9
|
+
* Fixed modules failing to open: cut-off files, very long patterns and out-of-range values now load.
|
|
10
|
+
* Improved the documentation.
|
|
11
|
+
* Published on PyPI: `pip install trackmod` (Python 3.12 or newer).
|
|
12
|
+
|
|
13
|
+
## v0.1.0 [2026-09-05]
|
|
14
|
+
|
|
15
|
+
The first working version of TrackMod, for Impulse Tracker and FastTracker 2.
|
|
16
|
+
|
|
17
|
+
* Added reading and writing of Impulse Tracker (`.it`) and FastTracker 2 (`.xm`) modules, including notes, effects, volumes and Impulse Tracker's song message and channel and pattern names.
|
|
18
|
+
* Added conversion of a song between the two formats.
|
|
19
|
+
* Added instruments with export to `.iti` or `.xi`.
|
|
20
|
+
* Added sounds, including stereo, loops and reading of Impulse Tracker's compressed sounds.
|
|
21
|
+
* Added size budgeting to plan how many sounds fit.
|
trackmod-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Jakim (Stage Magician)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
trackmod-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: trackmod
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Read, write and convert tracker music modules: Impulse Tracker, FastTracker 2, ProTracker, Scream Tracker 3 and Soundtracker
|
|
5
|
+
Project-URL: Homepage, https://github.com/JakimPL/TrackMod
|
|
6
|
+
Project-URL: Repository, https://github.com/JakimPL/TrackMod
|
|
7
|
+
Project-URL: Documentation, https://github.com/JakimPL/TrackMod/tree/main/docs
|
|
8
|
+
Project-URL: Changelog, https://github.com/JakimPL/TrackMod/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Issues, https://github.com/JakimPL/TrackMod/issues
|
|
10
|
+
Author-email: "Jakim (Stage Magician)" <jakimpl@gmail.com>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: chiptune,fasttracker,impulse-tracker,it,mod,module,music,protracker,s3m,scream-tracker,soundtracker,tracker,xm
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
23
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Conversion
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.12
|
|
27
|
+
Requires-Dist: numpy>=2.1
|
|
28
|
+
Requires-Dist: pydantic>=2.7.4
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# TrackMod
|
|
32
|
+
|
|
33
|
+
[](https://pypi.org/project/trackmod/)
|
|
34
|
+
[](https://pypi.org/project/trackmod/)
|
|
35
|
+
[](https://github.com/JakimPL/TrackMod/blob/main/LICENSE)
|
|
36
|
+
[](https://github.com/JakimPL/TrackMod/actions/workflows/ci.yml)
|
|
37
|
+
|
|
38
|
+
A tracker module is a complete piece of music in one file. It holds the notes, the effects and the recorded
|
|
39
|
+
sounds together. TrackMod reads these files, gives you what is inside, and writes them back.
|
|
40
|
+
|
|
41
|
+
TrackMod supports five formats:
|
|
42
|
+
|
|
43
|
+
| Tracker | Module | Single instrument |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| Impulse Tracker | `.it` | `.iti` |
|
|
46
|
+
| FastTracker 2 | `.xm` | `.xi` |
|
|
47
|
+
| Amiga ProTracker | `.mod` | — |
|
|
48
|
+
| Scream Tracker 3 | `.s3m` | — |
|
|
49
|
+
| Soundtracker | `.mod` | — |
|
|
50
|
+
|
|
51
|
+
All five formats share one model of a song. This lets you:
|
|
52
|
+
|
|
53
|
+
* open a file written by one tracker and save it for another,
|
|
54
|
+
* check whether a song fits a format before you write it.
|
|
55
|
+
|
|
56
|
+
TrackMod does not play audio. It gives you the notes, the settings and the waveforms. Use a player to hear
|
|
57
|
+
them.
|
|
58
|
+
|
|
59
|
+
## What you can do with it
|
|
60
|
+
|
|
61
|
+
* Read a module and get the song inside: its patterns, their order, the instruments and the waveforms.
|
|
62
|
+
* Write a song in any of the five formats.
|
|
63
|
+
* Check the file size and find values the format cannot store, before you write.
|
|
64
|
+
* Convert a song from one format to another.
|
|
65
|
+
* Save an instrument as an `.iti` or `.xi` file.
|
|
66
|
+
* Save a waveform as a `.wav` file.
|
|
67
|
+
|
|
68
|
+
## Installing
|
|
69
|
+
|
|
70
|
+
TrackMod needs Python 3.12 or newer. Install it from PyPI:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install trackmod
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
or add it to a [uv](https://docs.astral.sh/uv/) project:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
uv add trackmod
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
This also installs the two libraries TrackMod uses, `numpy` and `pydantic`.
|
|
83
|
+
|
|
84
|
+
## Reading a file
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from pathlib import Path
|
|
88
|
+
|
|
89
|
+
from trackmod import load_module
|
|
90
|
+
|
|
91
|
+
module = load_module(Path("song.it"))
|
|
92
|
+
print(module.song.name)
|
|
93
|
+
print(module.song.patterns[0].cell(row=0, channel=3))
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
TrackMod reads the format from the file contents, not from its name. A file with a wrong or missing
|
|
97
|
+
extension still opens.
|
|
98
|
+
|
|
99
|
+
## Writing a song
|
|
100
|
+
|
|
101
|
+
Pass a song to a format class. You get a module, which can:
|
|
102
|
+
|
|
103
|
+
* tell you how large the file will be,
|
|
104
|
+
* list the values the format cannot store,
|
|
105
|
+
* write the file.
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from pathlib import Path
|
|
109
|
+
|
|
110
|
+
from trackmod import Compliance, ITModule
|
|
111
|
+
|
|
112
|
+
module = ITModule.from_song(song, compliance=Compliance.CANONICAL)
|
|
113
|
+
print(module.size().total) # how many bytes the file will take
|
|
114
|
+
print(module.violations()) # values the format cannot store, empty when the song is writable
|
|
115
|
+
module.save(Path("song.it"))
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`Compliance` sets how strict the check is. See [`docs/reference/limits.md`](https://github.com/JakimPL/TrackMod/blob/main/docs/reference/limits.md).
|
|
119
|
+
|
|
120
|
+
To write the same song in another format, use another class:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from trackmod import XMModule
|
|
124
|
+
|
|
125
|
+
XMModule.from_song(song, compliance=Compliance.EXTENDED).save(Path("song.xm"))
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Saving instruments and waveforms
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from pathlib import Path
|
|
132
|
+
|
|
133
|
+
from trackmod import Compliance, ITInstrumentFile, load_module, save_sample, units
|
|
134
|
+
|
|
135
|
+
sounds = Path("sounds")
|
|
136
|
+
for path in Path("modules").iterdir():
|
|
137
|
+
module = load_module(path)
|
|
138
|
+
for index, unit in enumerate(units(module.song.voices)):
|
|
139
|
+
instrument = ITInstrumentFile.from_unit(unit, compliance=Compliance.CANONICAL)
|
|
140
|
+
instrument.save(sounds / f"{path.stem}-{index:02d}.iti")
|
|
141
|
+
|
|
142
|
+
for index, sample in enumerate(module.song.voices.samples):
|
|
143
|
+
save_sample(sample, sounds / f"{path.stem}-{index:02d}.wav")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`units` gives you every instrument with the waveforms it plays. It works the same for all five formats.
|
|
147
|
+
|
|
148
|
+
Each `.wav` file keeps the loop points, the tuning, the volume, the panning and the auto-vibrato. You can
|
|
149
|
+
open these files in a tracker or in any audio editor.
|
|
150
|
+
|
|
151
|
+
## Documentation
|
|
152
|
+
|
|
153
|
+
See [`docs/`](https://github.com/JakimPL/TrackMod/tree/main/docs):
|
|
154
|
+
|
|
155
|
+
* [`docs/guide/`](https://github.com/JakimPL/TrackMod/tree/main/docs/guide) — how to do each task,
|
|
156
|
+
* [`docs/reference/`](https://github.com/JakimPL/TrackMod/tree/main/docs/reference) — the song model and the format limits,
|
|
157
|
+
* [`docs/formats/`](https://github.com/JakimPL/TrackMod/tree/main/docs/formats) — the byte layout of each format.
|
|
158
|
+
|
|
159
|
+
Start at [`docs/README.md`](https://github.com/JakimPL/TrackMod/blob/main/docs/README.md). What changed in each release is in
|
|
160
|
+
[`CHANGELOG.md`](https://github.com/JakimPL/TrackMod/blob/main/CHANGELOG.md).
|
|
161
|
+
|
|
162
|
+
## Development
|
|
163
|
+
|
|
164
|
+
[`docs/contributing/development.md`](https://github.com/JakimPL/TrackMod/blob/main/docs/contributing/development.md) covers the tools, the tests
|
|
165
|
+
and the checks a change passes, and [`docs/contributing/releasing.md`](https://github.com/JakimPL/TrackMod/blob/main/docs/contributing/releasing.md)
|
|
166
|
+
covers how a release reaches PyPI.
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
TrackMod is released under the [MIT License](https://github.com/JakimPL/TrackMod/blob/main/LICENSE).
|
trackmod-0.2.0/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# TrackMod
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/trackmod/)
|
|
4
|
+
[](https://pypi.org/project/trackmod/)
|
|
5
|
+
[](https://github.com/JakimPL/TrackMod/blob/main/LICENSE)
|
|
6
|
+
[](https://github.com/JakimPL/TrackMod/actions/workflows/ci.yml)
|
|
7
|
+
|
|
8
|
+
A tracker module is a complete piece of music in one file. It holds the notes, the effects and the recorded
|
|
9
|
+
sounds together. TrackMod reads these files, gives you what is inside, and writes them back.
|
|
10
|
+
|
|
11
|
+
TrackMod supports five formats:
|
|
12
|
+
|
|
13
|
+
| Tracker | Module | Single instrument |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Impulse Tracker | `.it` | `.iti` |
|
|
16
|
+
| FastTracker 2 | `.xm` | `.xi` |
|
|
17
|
+
| Amiga ProTracker | `.mod` | — |
|
|
18
|
+
| Scream Tracker 3 | `.s3m` | — |
|
|
19
|
+
| Soundtracker | `.mod` | — |
|
|
20
|
+
|
|
21
|
+
All five formats share one model of a song. This lets you:
|
|
22
|
+
|
|
23
|
+
* open a file written by one tracker and save it for another,
|
|
24
|
+
* check whether a song fits a format before you write it.
|
|
25
|
+
|
|
26
|
+
TrackMod does not play audio. It gives you the notes, the settings and the waveforms. Use a player to hear
|
|
27
|
+
them.
|
|
28
|
+
|
|
29
|
+
## What you can do with it
|
|
30
|
+
|
|
31
|
+
* Read a module and get the song inside: its patterns, their order, the instruments and the waveforms.
|
|
32
|
+
* Write a song in any of the five formats.
|
|
33
|
+
* Check the file size and find values the format cannot store, before you write.
|
|
34
|
+
* Convert a song from one format to another.
|
|
35
|
+
* Save an instrument as an `.iti` or `.xi` file.
|
|
36
|
+
* Save a waveform as a `.wav` file.
|
|
37
|
+
|
|
38
|
+
## Installing
|
|
39
|
+
|
|
40
|
+
TrackMod needs Python 3.12 or newer. Install it from PyPI:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install trackmod
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
or add it to a [uv](https://docs.astral.sh/uv/) project:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
uv add trackmod
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This also installs the two libraries TrackMod uses, `numpy` and `pydantic`.
|
|
53
|
+
|
|
54
|
+
## Reading a file
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from pathlib import Path
|
|
58
|
+
|
|
59
|
+
from trackmod import load_module
|
|
60
|
+
|
|
61
|
+
module = load_module(Path("song.it"))
|
|
62
|
+
print(module.song.name)
|
|
63
|
+
print(module.song.patterns[0].cell(row=0, channel=3))
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
TrackMod reads the format from the file contents, not from its name. A file with a wrong or missing
|
|
67
|
+
extension still opens.
|
|
68
|
+
|
|
69
|
+
## Writing a song
|
|
70
|
+
|
|
71
|
+
Pass a song to a format class. You get a module, which can:
|
|
72
|
+
|
|
73
|
+
* tell you how large the file will be,
|
|
74
|
+
* list the values the format cannot store,
|
|
75
|
+
* write the file.
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from pathlib import Path
|
|
79
|
+
|
|
80
|
+
from trackmod import Compliance, ITModule
|
|
81
|
+
|
|
82
|
+
module = ITModule.from_song(song, compliance=Compliance.CANONICAL)
|
|
83
|
+
print(module.size().total) # how many bytes the file will take
|
|
84
|
+
print(module.violations()) # values the format cannot store, empty when the song is writable
|
|
85
|
+
module.save(Path("song.it"))
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`Compliance` sets how strict the check is. See [`docs/reference/limits.md`](https://github.com/JakimPL/TrackMod/blob/main/docs/reference/limits.md).
|
|
89
|
+
|
|
90
|
+
To write the same song in another format, use another class:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from trackmod import XMModule
|
|
94
|
+
|
|
95
|
+
XMModule.from_song(song, compliance=Compliance.EXTENDED).save(Path("song.xm"))
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Saving instruments and waveforms
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from pathlib import Path
|
|
102
|
+
|
|
103
|
+
from trackmod import Compliance, ITInstrumentFile, load_module, save_sample, units
|
|
104
|
+
|
|
105
|
+
sounds = Path("sounds")
|
|
106
|
+
for path in Path("modules").iterdir():
|
|
107
|
+
module = load_module(path)
|
|
108
|
+
for index, unit in enumerate(units(module.song.voices)):
|
|
109
|
+
instrument = ITInstrumentFile.from_unit(unit, compliance=Compliance.CANONICAL)
|
|
110
|
+
instrument.save(sounds / f"{path.stem}-{index:02d}.iti")
|
|
111
|
+
|
|
112
|
+
for index, sample in enumerate(module.song.voices.samples):
|
|
113
|
+
save_sample(sample, sounds / f"{path.stem}-{index:02d}.wav")
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`units` gives you every instrument with the waveforms it plays. It works the same for all five formats.
|
|
117
|
+
|
|
118
|
+
Each `.wav` file keeps the loop points, the tuning, the volume, the panning and the auto-vibrato. You can
|
|
119
|
+
open these files in a tracker or in any audio editor.
|
|
120
|
+
|
|
121
|
+
## Documentation
|
|
122
|
+
|
|
123
|
+
See [`docs/`](https://github.com/JakimPL/TrackMod/tree/main/docs):
|
|
124
|
+
|
|
125
|
+
* [`docs/guide/`](https://github.com/JakimPL/TrackMod/tree/main/docs/guide) — how to do each task,
|
|
126
|
+
* [`docs/reference/`](https://github.com/JakimPL/TrackMod/tree/main/docs/reference) — the song model and the format limits,
|
|
127
|
+
* [`docs/formats/`](https://github.com/JakimPL/TrackMod/tree/main/docs/formats) — the byte layout of each format.
|
|
128
|
+
|
|
129
|
+
Start at [`docs/README.md`](https://github.com/JakimPL/TrackMod/blob/main/docs/README.md). What changed in each release is in
|
|
130
|
+
[`CHANGELOG.md`](https://github.com/JakimPL/TrackMod/blob/main/CHANGELOG.md).
|
|
131
|
+
|
|
132
|
+
## Development
|
|
133
|
+
|
|
134
|
+
[`docs/contributing/development.md`](https://github.com/JakimPL/TrackMod/blob/main/docs/contributing/development.md) covers the tools, the tests
|
|
135
|
+
and the checks a change passes, and [`docs/contributing/releasing.md`](https://github.com/JakimPL/TrackMod/blob/main/docs/contributing/releasing.md)
|
|
136
|
+
covers how a release reaches PyPI.
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
TrackMod is released under the [MIT License](https://github.com/JakimPL/TrackMod/blob/main/LICENSE).
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Documentation
|
|
2
|
+
|
|
3
|
+
TrackMod reads and writes tracker modules through one shared model of a song. These documents cover it from
|
|
4
|
+
three sides: how to use the library, what the model holds, and what each format can store.
|
|
5
|
+
|
|
6
|
+
## Guides
|
|
7
|
+
|
|
8
|
+
How to do each task.
|
|
9
|
+
|
|
10
|
+
| Document | What it covers |
|
|
11
|
+
|---|---|
|
|
12
|
+
| [`guide/reading.md`](guide/reading.md) | Open a file and read what is inside it |
|
|
13
|
+
| [`guide/writing.md`](guide/writing.md) | Write a song to a file in a format you choose |
|
|
14
|
+
| [`guide/converting.md`](guide/converting.md) | Convert a song from one format to another |
|
|
15
|
+
| [`guide/instruments.md`](guide/instruments.md) | Take instruments out of a module and save them as files |
|
|
16
|
+
| [`guide/samples.md`](guide/samples.md) | Read the waveforms and save them as audio |
|
|
17
|
+
| [`guide/budgeting.md`](guide/budgeting.md) | Find out how large a file will be before you write it |
|
|
18
|
+
|
|
19
|
+
## Reference
|
|
20
|
+
|
|
21
|
+
The shared model and its limits.
|
|
22
|
+
|
|
23
|
+
| Document | What it covers |
|
|
24
|
+
|---|---|
|
|
25
|
+
| [`reference/model.md`](reference/model.md) | Songs, patterns, voices, samples, instruments, timing |
|
|
26
|
+
| [`reference/limits.md`](reference/limits.md) | Capabilities, compliance levels, and where every bound comes from |
|
|
27
|
+
| [`reference/effects.md`](reference/effects.md) | The effect column, and the one vocabulary each format spells its own way |
|
|
28
|
+
| [`reference/volume.md`](reference/volume.md) | The volume column: one vocabulary, and what each format's byte reaches |
|
|
29
|
+
|
|
30
|
+
## Formats
|
|
31
|
+
|
|
32
|
+
The byte layout of each format. See [`formats/README.md`](formats/README.md) for the five side by side, and
|
|
33
|
+
for the fields they disagree about.
|
|
34
|
+
|
|
35
|
+
| Document | Format |
|
|
36
|
+
|---|---|
|
|
37
|
+
| [`formats/it.md`](formats/it.md) | Impulse Tracker, `.it` |
|
|
38
|
+
| [`formats/xm.md`](formats/xm.md) | FastTracker 2, `.xm` |
|
|
39
|
+
| [`formats/mod.md`](formats/mod.md) | Amiga ProTracker, `.mod` |
|
|
40
|
+
| [`formats/s3m.md`](formats/s3m.md) | Scream Tracker 3, `.s3m` |
|
|
41
|
+
| [`formats/st.md`](formats/st.md) | Soundtracker, the fifteen-sample `.mod` layout |
|
|
42
|
+
|
|
43
|
+
## Working on TrackMod
|
|
44
|
+
|
|
45
|
+
| Document | What it covers |
|
|
46
|
+
|---|---|
|
|
47
|
+
| [`contributing/architecture.md`](contributing/architecture.md) | How the packages are layered, and what each one owns |
|
|
48
|
+
| [`contributing/documentation.md`](contributing/documentation.md) | How these documents are written |
|
|
49
|
+
| [`contributing/development.md`](contributing/development.md) | The tools, the tests and the gates a change passes |
|
|
50
|
+
| [`contributing/releasing.md`](contributing/releasing.md) | How a tag becomes a release on PyPI |
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
The decisions the library's own shape rests on: how the packages are layered, what each one owns, and
|
|
4
|
+
which of them may read which.
|
|
5
|
+
|
|
6
|
+
## Layers
|
|
7
|
+
|
|
8
|
+
The library is layered downward: every package reads its own layer and the ones above it in this table.
|
|
9
|
+
|
|
10
|
+
| Package | Owns |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `trackmod/spec` | Constants every layer shares: the pitch numbering, level ranges, the volume-column grid, the grid sentinel, integer widths, the tracker clock, the tracker character set, this library's own name and version |
|
|
13
|
+
| `trackmod/utils` | Arithmetic the timing lattice leans on: the divisors of a number, and the two of them nearest a candidate |
|
|
14
|
+
| `trackmod/schema` | Pydantic plumbing: the frozen model config, the constrained scalar aliases, the numpy array annotations |
|
|
15
|
+
| `trackmod/limits` | The capability vocabulary, bounds, compliance levels, violations |
|
|
16
|
+
| `trackmod/core` | The format-agnostic music: notes, patterns, samples, instruments, envelopes, voices, songs, timing |
|
|
17
|
+
| `trackmod/binary` | Byte-level machinery: declarative records, a cursor, fixed-width text, PCM quantization and encoding |
|
|
18
|
+
| `trackmod/wave` | The RIFF audio container: its chunk layouts, and a sample written and read as a `.wav` |
|
|
19
|
+
| `trackmod/module` | What a format binding offers: the size report, the storage table, how far a file's values reach, and the `TrackerModule` and `InstrumentFile` protocols |
|
|
20
|
+
| `trackmod/trackers/<lineage>` | What a family of formats inherited from the one that settled it: the Amiga period tables, the fixed cell, the thirty-byte sample record |
|
|
21
|
+
| `trackmod/trackers/<format>` | One format each: its constants, its record layouts, its packers, parsers, size model, module class and instrument-file class |
|
|
22
|
+
|
|
23
|
+
`tests/test_boundaries.py` reads the import graph and holds this order, so knowing where a name lives is
|
|
24
|
+
knowing what may reach it.
|
|
25
|
+
|
|
26
|
+
## The package root
|
|
27
|
+
|
|
28
|
+
`trackmod/__init__.py` carries what a caller needs to **open a file, reach the music inside it, and write
|
|
29
|
+
it back out**. Everything else is imported from the module that defines it.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from trackmod import Compliance, ITModule, load_module, save_sample, units
|
|
33
|
+
from trackmod.core.patterns.builder import PatternBuilder
|
|
34
|
+
from trackmod.trackers.s3m.settings import S3MSettings
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Patterns, cells, envelopes, timing, the settings models, the effect catalogs, the storage and size
|
|
38
|
+
reports and everything under `binary` keep their module paths. They belong to callers building songs
|
|
39
|
+
rather than reading them, and the root stays a surface a newcomer can read in one screen. The root stands
|
|
40
|
+
above every layer and reads all of them; every other `__init__.py` is empty.
|
|
41
|
+
|
|
42
|
+
## A format package
|
|
43
|
+
|
|
44
|
+
Each format package repeats the same internal shape, so knowing one is knowing the next:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
<format>/
|
|
48
|
+
spec/ constants only: identity sizes ranges defaults flags cells effects storage capacities
|
|
49
|
+
layout/ the record layouts, as data: file pattern sample instrument envelope
|
|
50
|
+
effects/ the command enumeration and the catalog that spells the shared vocabulary
|
|
51
|
+
patterns/ packer, parser, and the size model that is their exact counterpart
|
|
52
|
+
samples/ waveform and header serialization
|
|
53
|
+
instruments/ header serialization, keymaps, envelopes, the standalone instrument file
|
|
54
|
+
detection limits timing settings sizing writer parser module
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The last line is what every format has: what its bytes state about themselves, its capacities read at one
|
|
58
|
+
level, its clock, its own settings, its size model, and the pair that writes and reads a whole file behind
|
|
59
|
+
the module class.
|
|
60
|
+
|
|
61
|
+
A package holds the subdirectories its own format keeps records for. The three that keep no instrument
|
|
62
|
+
records — Amiga ProTracker, Scream Tracker 3 and Soundtracker — have no `instruments/` and no envelopes,
|
|
63
|
+
and the two Amiga layouts read their `patterns/` and `samples/` from the lineage that holds both.
|
|
64
|
+
|
|
65
|
+
Each format then adds the files its layout calls for: `note`, `fade`, `checks` and `instrument_file` where
|
|
66
|
+
a format keeps instrument records, `dialect` and `tag` for the four characters that name an Amiga layout,
|
|
67
|
+
`parapointers` and `placement` for the paragraphs Scream Tracker 3's blocks open on, `tuning` where a
|
|
68
|
+
header states a transposition rather than a rate, and `version` where a file numbers its writer.
|
|
69
|
+
|
|
70
|
+
## Lineage packages
|
|
71
|
+
|
|
72
|
+
A lineage package sits beside the formats in the same shape and holds what a family of them shares.
|
|
73
|
+
`amiga/` is the one this library has: the period tables, the four-byte cell, the thirty-byte sample record
|
|
74
|
+
and the eight-bit waveform that Ultimate Soundtracker settled and every tracker on that machine kept,
|
|
75
|
+
together with the walk that reads a file of either and the checks that grade a song for both. Its own
|
|
76
|
+
files carry the same names — `reading`, `writing`, `sizing`, `checks`, `note`, `tuning` beside `spec/`,
|
|
77
|
+
`layout/`, `patterns/` and `samples/` — so the shape above reads the same there.
|
|
78
|
+
|
|
79
|
+
A format package owns every decision its own file layout makes, and every import it makes reaches its
|
|
80
|
+
lineage or a layer beneath it. Two formats sharing a decision because one inherited it from the other
|
|
81
|
+
share it through the lineage, which owns it outright; where they disagree, the lineage takes the answer as
|
|
82
|
+
an argument, so each of them keeps its own. `tests/test_boundaries.py` holds that line.
|
|
83
|
+
|
|
84
|
+
## Validating versus repairing
|
|
85
|
+
|
|
86
|
+
> **A validator constrains what the library writes. A parser repairs what it reads, and says so.**
|
|
87
|
+
|
|
88
|
+
| Situation | Mechanism | Where it is documented |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| A quantity past what a format holds | `Violation`, collected into one `LimitError` | [`../reference/limits.md`](../reference/limits.md), and the format's capacities |
|
|
91
|
+
| Content with no encoding at all | `ValueError` where it is met | the format document's refusals table |
|
|
92
|
+
| A value a real file states that the model holds no room for | drawn into range, gathered in `Repairs`, reported once as a `RepairWarning` | the format document, under the section that reads it |
|
|
93
|
+
|
|
94
|
+
A bound says *use a smaller number*. A `ValueError` says *this idea has no home here, express it another
|
|
95
|
+
way*. A repair says *the file stated something odd, and this is what was heard*.
|
|
96
|
+
|
|
97
|
+
## Types
|
|
98
|
+
|
|
99
|
+
- Every validated or serialized type is a **frozen** Pydantic model. Bounds live in `Field(...)`
|
|
100
|
+
constraints, and cross-field rules in `model_validator(mode="after")`.
|
|
101
|
+
- Constants live in `spec/` packages and nowhere else, so the constants read as the specification.
|
|
102
|
+
- Protocols are preferred to base classes, and composition to inheritance. `Reaching` is the one mixin,
|
|
103
|
+
carrying no fields, no construction and no format knowledge, which is what earns it the exception.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Development
|
|
2
|
+
|
|
3
|
+
## The tools
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
make format # isort + black
|
|
7
|
+
make lint # mypy --strict + pylint
|
|
8
|
+
make test # pytest
|
|
9
|
+
make test-docs # the examples in docstrings, through --doctest-modules
|
|
10
|
+
make coverage # pytest with a coverage report
|
|
11
|
+
make build # the sdist and the wheel in dist/, checked with twine
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Dependencies are managed with `uv`, and the package is built with `hatchling`. Every command above runs
|
|
15
|
+
through `uv run`, so a checkout needs no environment of its own.
|
|
16
|
+
|
|
17
|
+
`pre-commit` runs the same checks on the files a commit touches: trailing whitespace, final newlines,
|
|
18
|
+
YAML and TOML syntax, `isort`, `black`, `zizmor` over the GitHub Actions workflows, `mypy`, `pylint` and
|
|
19
|
+
`make test-docs`, with the full test suite as a pre-push hook. Install it once with
|
|
20
|
+
`uv run pre-commit install`, and run it over everything with `uv run pre-commit run --all-files`.
|
|
21
|
+
|
|
22
|
+
## Typing
|
|
23
|
+
|
|
24
|
+
`mypy` runs in `--strict` mode over `src/trackmod`, with the pydantic plugin and `init_typed`. Every
|
|
25
|
+
signature states its input and return types, including `None`, and generic types are filled in:
|
|
26
|
+
`dict[str, int]` rather than a bare `dict`. A cast or an `ignore` is for an untyped or mistyped
|
|
27
|
+
third-party boundary, and nothing else.
|
|
28
|
+
|
|
29
|
+
## Linting
|
|
30
|
+
|
|
31
|
+
`pylint` runs over `src/trackmod` with `fail-under=9.9` and the `pylint_pydantic` plugin. Docstring
|
|
32
|
+
requirements for modules, classes and functions are off, because the library carries no module docstrings
|
|
33
|
+
and states class and function intent in its own words rather than to a checker.
|
|
34
|
+
|
|
35
|
+
Duplicate-code reporting is off, because the five format packages repeat one shape on purpose and every
|
|
36
|
+
remaining report is that shape. What two formats share for a reason belongs to their lineage, and
|
|
37
|
+
`tests/test_boundaries.py` is what holds the line instead.
|
|
38
|
+
|
|
39
|
+
Both `black` and `isort` run at a line length of 120, `isort` under the `black` profile with `trackmod`
|
|
40
|
+
as the first-party package.
|
|
41
|
+
|
|
42
|
+
## Tests
|
|
43
|
+
|
|
44
|
+
The test tree mirrors the source tree: `tests/binary`, `tests/core`, `tests/limits`, `tests/module`,
|
|
45
|
+
`tests/wave` and `tests/trackers/<format>`, with the shared fixtures — a song, its voices, an envelope,
|
|
46
|
+
the waveform helpers — in `tests/conftest.py`.
|
|
47
|
+
|
|
48
|
+
Three suites sit at the top level and cross those boundaries:
|
|
49
|
+
|
|
50
|
+
| Suite | What it holds |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `tests/test_formats.py` | One property stated once for every format, through a `Binding` per format |
|
|
53
|
+
| `tests/test_package.py` | What the package root offers, and the loops the guide shows, end to end |
|
|
54
|
+
| `tests/test_boundaries.py` | The import graph: the layer order, and what a format or lineage package may read |
|
|
55
|
+
|
|
56
|
+
`filterwarnings = ["error"]` means a stray `RepairWarning` fails a test rather than printing, so a parser
|
|
57
|
+
that starts repairing something new is caught where it happens. Coverage gates at 90 percent.
|
|
58
|
+
|
|
59
|
+
## Style
|
|
60
|
+
|
|
61
|
+
- Keep code modular around clear ownership boundaries, and prefer subpackages to large modules.
|
|
62
|
+
- A function with several meaningful steps splits into helpers with one responsibility each.
|
|
63
|
+
- Names are written out: `note`, not `n`.
|
|
64
|
+
- Default values are for what is rarely changed, and each one is a `Final` constant at module level.
|
|
65
|
+
- Let failures crash unless the code can recover meaningfully. Bare `except` and `except Exception` are
|
|
66
|
+
out; an error-handling block covers only what can fail.
|
|
67
|
+
- The library carries no module docstrings and no code comments. Class and function docstrings state
|
|
68
|
+
intent; the domain and format narrative lives in these documents. A comment is for a tensor shape, a
|
|
69
|
+
third-party quirk, or an invariant the code cannot show on its own.
|
|
70
|
+
- An example in a docstring is written as a doctest, so `make test-docs` runs it. Write one only where it
|
|
71
|
+
is self-contained: `filterwarnings = ["error"]` applies there too, so an example that trips a
|
|
72
|
+
`RepairWarning` fails.
|
|
73
|
+
|
|
74
|
+
## Continuous integration
|
|
75
|
+
|
|
76
|
+
[`ci.yml`](../../.github/workflows/ci.yml) runs on every push to `main` and every pull request:
|
|
77
|
+
|
|
78
|
+
| Job | What it runs |
|
|
79
|
+
|---|---|
|
|
80
|
+
| Checks | Every `pre-commit` hook over the whole tree, `make coverage` and `make build` |
|
|
81
|
+
| Tests | The suite on Linux, Windows and macOS, under Python 3.12, 3.13 and 3.14 |
|
|
82
|
+
| Lowest dependencies | The suite on the oldest `numpy` and `pydantic` that `pyproject.toml` allows |
|
|
83
|
+
|
|
84
|
+
Every job installs from `uv.lock` and fails when the lock is out of date, except the lowest-dependencies
|
|
85
|
+
job, which resolves the floors afresh. The actions are pinned to commit hashes, and Dependabot proposes
|
|
86
|
+
new pins once a month. Releases have their own workflow, described in [`releasing.md`](releasing.md).
|
|
87
|
+
|
|
88
|
+
## Commit messages
|
|
89
|
+
|
|
90
|
+
One line, in the form *Did: what* — `Added: the fifteen-sample Soundtracker layout`, `Fixed: the
|
|
91
|
+
truncations that read as silence`, `Rewrote: the README in plain English`.
|