samplerdisc 0.5.1__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.
Files changed (141) hide show
  1. samplerdisc-0.5.1/.dockerignore +8 -0
  2. samplerdisc-0.5.1/.github/ISSUE_TEMPLATE/config.yml +1 -0
  3. samplerdisc-0.5.1/.github/ISSUE_TEMPLATE/disc-wont-read.yml +43 -0
  4. samplerdisc-0.5.1/.github/ISSUE_TEMPLATE/new-format.yml +33 -0
  5. samplerdisc-0.5.1/.github/pull_request_template.md +24 -0
  6. samplerdisc-0.5.1/.github/workflows/ci.yml +60 -0
  7. samplerdisc-0.5.1/.github/workflows/release.yml +71 -0
  8. samplerdisc-0.5.1/.gitignore +44 -0
  9. samplerdisc-0.5.1/CHANGELOG.md +313 -0
  10. samplerdisc-0.5.1/CLAUDE.md +73 -0
  11. samplerdisc-0.5.1/CODE_OF_CONDUCT.md +27 -0
  12. samplerdisc-0.5.1/CONTRIBUTING.md +47 -0
  13. samplerdisc-0.5.1/Containerfile +31 -0
  14. samplerdisc-0.5.1/LICENSE +21 -0
  15. samplerdisc-0.5.1/PKG-INFO +226 -0
  16. samplerdisc-0.5.1/README.md +207 -0
  17. samplerdisc-0.5.1/SECURITY.md +80 -0
  18. samplerdisc-0.5.1/docs/README.md +106 -0
  19. samplerdisc-0.5.1/docs/adr/0001-pure-python-stdlib-only.md +27 -0
  20. samplerdisc-0.5.1/docs/adr/0002-mit-license.md +23 -0
  21. samplerdisc-0.5.1/docs/adr/0003-brand-neutral-pluggable-backends.md +33 -0
  22. samplerdisc-0.5.1/docs/adr/0004-detect-by-signature.md +25 -0
  23. samplerdisc-0.5.1/docs/adr/0005-probe-for-the-filesystem-origin.md +31 -0
  24. samplerdisc-0.5.1/docs/adr/0006-mdx-blocks-classified-by-decode-attempt.md +35 -0
  25. samplerdisc-0.5.1/docs/adr/0007-emit-mono-and-stereo.md +27 -0
  26. samplerdisc-0.5.1/docs/adr/0008-no-media-in-the-repo.md +35 -0
  27. samplerdisc-0.5.1/docs/adr/0009-export-iso-escape-hatch.md +27 -0
  28. samplerdisc-0.5.1/docs/adr/0010-build-the-instrument-layer-ourselves.md +31 -0
  29. samplerdisc-0.5.1/docs/adr/0011-the-deliverable-is-daw-ready-wav.md +35 -0
  30. samplerdisc-0.5.1/docs/adr/0012-a-probe-must-confirm-a-file.md +50 -0
  31. samplerdisc-0.5.1/docs/adr/0013-cueless-audio-is-reported-not-guessed.md +43 -0
  32. samplerdisc-0.5.1/docs/adr/0014-one-backend-per-on-disc-format.md +45 -0
  33. samplerdisc-0.5.1/docs/adr/0015-locate-banks-by-signature.md +52 -0
  34. samplerdisc-0.5.1/docs/adr/0016-the-s7xx-hierarchy-is-located-not-walked.md +35 -0
  35. samplerdisc-0.5.1/docs/adr/0017-the-stereo-side-marker-is-a-character-class.md +39 -0
  36. samplerdisc-0.5.1/docs/adr/0018-the-s7xx-sample-rate-is-measured.md +35 -0
  37. samplerdisc-0.5.1/docs/adr/0019-prefer-joliet-names.md +37 -0
  38. samplerdisc-0.5.1/docs/adr/0020-read-e-iv-through-its-sample-directory.md +57 -0
  39. samplerdisc-0.5.1/docs/adr/0021-a-bank-owns-the-run-its-header-declares.md +69 -0
  40. samplerdisc-0.5.1/docs/adr/0022-a-volume-is-explained-by-the-allocation-map.md +71 -0
  41. samplerdisc-0.5.1/docs/adr/0023-partitions-come-from-the-table-the-disc-declares.md +65 -0
  42. samplerdisc-0.5.1/docs/adr/0024-the-aiff-twin-is-converted-and-deduplicated.md +59 -0
  43. samplerdisc-0.5.1/docs/adr/0025-the-loop-is-decoded-the-root-key-is-not.md +107 -0
  44. samplerdisc-0.5.1/docs/adr/0026-the-record-declares-the-channel-count.md +95 -0
  45. samplerdisc-0.5.1/docs/adr/0027-a-payload-must-be-the-file-its-entry-placed.md +121 -0
  46. samplerdisc-0.5.1/docs/adr/0028-a-displaced-partition-is-anchored-quantised-and-floored.md +109 -0
  47. samplerdisc-0.5.1/docs/adr/0029-a-record-is-closed-by-the-channel-it-declares.md +102 -0
  48. samplerdisc-0.5.1/docs/adr/0030-the-whole-extent-no-loop-is-refused-at-both-ends.md +73 -0
  49. samplerdisc-0.5.1/docs/adr/0031-a-bank-binds-the-near-named-header-its-placement-predicts.md +59 -0
  50. samplerdisc-0.5.1/docs/adr/0032-read-the-eiv-form-e4b0-bank-and-its-embedded-samples.md +49 -0
  51. samplerdisc-0.5.1/docs/adr/0033-ebl-is-converted-on-a-disc-and-verified-by-a-render.md +49 -0
  52. samplerdisc-0.5.1/docs/adr/0034-each-directory-entry-binds-its-own-header.md +52 -0
  53. samplerdisc-0.5.1/docs/adr/0035-kmsi-is-fat16-read-behind-the-kurzweil-signature.md +43 -0
  54. samplerdisc-0.5.1/docs/adr/0036-the-krz-bank-is-read-as-objects-and-verified-against-mpc2emu.md +47 -0
  55. samplerdisc-0.5.1/docs/adr/0037-a-fragmented-form-bank-is-read-along-the-fat-chain.md +49 -0
  56. samplerdisc-0.5.1/docs/adr/0038-the-container-is-a-runner-not-the-distribution-channel.md +35 -0
  57. samplerdisc-0.5.1/docs/adr/0039-samplecell-is-read-as-hfs-behind-an-apple-partition-map.md +38 -0
  58. samplerdisc-0.5.1/docs/adr/0040-sound-designer-ii-is-decoded-from-the-data-fork.md +41 -0
  59. samplerdisc-0.5.1/docs/adr/0041-stereo-ebl-is-interleaved-and-verified-by-the-render.md +40 -0
  60. samplerdisc-0.5.1/docs/adr/0042-loose-sample-banks-are-a-source-not-a-container.md +39 -0
  61. samplerdisc-0.5.1/docs/adr/0043-eiv-credits-are-metadata-read-from-the-e4p1-name-field.md +45 -0
  62. samplerdisc-0.5.1/docs/adr/0044-the-eiv-residual-loop-is-confirmed-by-its-period.md +54 -0
  63. samplerdisc-0.5.1/docs/adr/0045-a-displaced-sample-is-recovered-by-its-own-name-and-word-count.md +67 -0
  64. samplerdisc-0.5.1/docs/adr/0046-the-sd2-loop-is-decoded-from-sdll.md +49 -0
  65. samplerdisc-0.5.1/docs/adr/README.md +54 -0
  66. samplerdisc-0.5.1/docs/architecture.md +75 -0
  67. samplerdisc-0.5.1/docs/formats/README.md +56 -0
  68. samplerdisc-0.5.1/docs/formats/aiff.md +120 -0
  69. samplerdisc-0.5.1/docs/formats/akai-fs.md +361 -0
  70. samplerdisc-0.5.1/docs/formats/audio-cd.md +56 -0
  71. samplerdisc-0.5.1/docs/formats/emu-ebl.md +150 -0
  72. samplerdisc-0.5.1/docs/formats/emu3.md +820 -0
  73. samplerdisc-0.5.1/docs/formats/hfs.md +150 -0
  74. samplerdisc-0.5.1/docs/formats/iso9660.md +93 -0
  75. samplerdisc-0.5.1/docs/formats/kurzweil-krz.md +122 -0
  76. samplerdisc-0.5.1/docs/formats/kurzweil.md +80 -0
  77. samplerdisc-0.5.1/docs/formats/mdx.md +169 -0
  78. samplerdisc-0.5.1/docs/formats/nrg.md +82 -0
  79. samplerdisc-0.5.1/docs/formats/rawcd.md +53 -0
  80. samplerdisc-0.5.1/docs/formats/roland-s7xx.md +301 -0
  81. samplerdisc-0.5.1/pyproject.toml +47 -0
  82. samplerdisc-0.5.1/scripts/benchmark.py +264 -0
  83. samplerdisc-0.5.1/src/samplerdisc/__init__.py +3 -0
  84. samplerdisc-0.5.1/src/samplerdisc/audiocd.py +212 -0
  85. samplerdisc-0.5.1/src/samplerdisc/banks.py +83 -0
  86. samplerdisc-0.5.1/src/samplerdisc/batch.py +260 -0
  87. samplerdisc-0.5.1/src/samplerdisc/cli.py +450 -0
  88. samplerdisc-0.5.1/src/samplerdisc/container/__init__.py +1 -0
  89. samplerdisc-0.5.1/src/samplerdisc/container/base.py +110 -0
  90. samplerdisc-0.5.1/src/samplerdisc/container/detect.py +74 -0
  91. samplerdisc-0.5.1/src/samplerdisc/container/flat.py +16 -0
  92. samplerdisc-0.5.1/src/samplerdisc/container/mdsmdf.py +62 -0
  93. samplerdisc-0.5.1/src/samplerdisc/container/mdx.py +312 -0
  94. samplerdisc-0.5.1/src/samplerdisc/container/nrg.py +174 -0
  95. samplerdisc-0.5.1/src/samplerdisc/container/rawcd.py +79 -0
  96. samplerdisc-0.5.1/src/samplerdisc/cue.py +108 -0
  97. samplerdisc-0.5.1/src/samplerdisc/export.py +48 -0
  98. samplerdisc-0.5.1/src/samplerdisc/extract.py +749 -0
  99. samplerdisc-0.5.1/src/samplerdisc/fs/__init__.py +17 -0
  100. samplerdisc-0.5.1/src/samplerdisc/fs/akai.py +781 -0
  101. samplerdisc-0.5.1/src/samplerdisc/fs/base.py +187 -0
  102. samplerdisc-0.5.1/src/samplerdisc/fs/emu3.py +1545 -0
  103. samplerdisc-0.5.1/src/samplerdisc/fs/hfs.py +451 -0
  104. samplerdisc-0.5.1/src/samplerdisc/fs/iso9660.py +268 -0
  105. samplerdisc-0.5.1/src/samplerdisc/fs/kurzweil.py +818 -0
  106. samplerdisc-0.5.1/src/samplerdisc/fs/probe.py +53 -0
  107. samplerdisc-0.5.1/src/samplerdisc/fs/roland_s7xx.py +472 -0
  108. samplerdisc-0.5.1/src/samplerdisc/sample/__init__.py +38 -0
  109. samplerdisc-0.5.1/src/samplerdisc/sample/aiff.py +248 -0
  110. samplerdisc-0.5.1/src/samplerdisc/sample/akai.py +271 -0
  111. samplerdisc-0.5.1/src/samplerdisc/sample/emu3.py +259 -0
  112. samplerdisc-0.5.1/src/samplerdisc/sample/emu_ebl.py +291 -0
  113. samplerdisc-0.5.1/src/samplerdisc/sample/kurzweil.py +163 -0
  114. samplerdisc-0.5.1/src/samplerdisc/sample/roland_s7xx.py +113 -0
  115. samplerdisc-0.5.1/src/samplerdisc/sample/sd2.py +231 -0
  116. samplerdisc-0.5.1/src/samplerdisc/stereo.py +164 -0
  117. samplerdisc-0.5.1/src/samplerdisc/wav.py +224 -0
  118. samplerdisc-0.5.1/tests/__init__.py +0 -0
  119. samplerdisc-0.5.1/tests/fixtures.py +1895 -0
  120. samplerdisc-0.5.1/tests/test_aiff.py +154 -0
  121. samplerdisc-0.5.1/tests/test_akai_fs.py +929 -0
  122. samplerdisc-0.5.1/tests/test_akai_payload.py +232 -0
  123. samplerdisc-0.5.1/tests/test_audiocd.py +193 -0
  124. samplerdisc-0.5.1/tests/test_banks.py +95 -0
  125. samplerdisc-0.5.1/tests/test_batch.py +173 -0
  126. samplerdisc-0.5.1/tests/test_cli.py +86 -0
  127. samplerdisc-0.5.1/tests/test_containers.py +460 -0
  128. samplerdisc-0.5.1/tests/test_discs.py +2877 -0
  129. samplerdisc-0.5.1/tests/test_emu3.py +1313 -0
  130. samplerdisc-0.5.1/tests/test_emu_ebl.py +186 -0
  131. samplerdisc-0.5.1/tests/test_extract.py +441 -0
  132. samplerdisc-0.5.1/tests/test_hfs.py +292 -0
  133. samplerdisc-0.5.1/tests/test_iso9660.py +348 -0
  134. samplerdisc-0.5.1/tests/test_keep_originals.py +111 -0
  135. samplerdisc-0.5.1/tests/test_kurzweil_fs.py +233 -0
  136. samplerdisc-0.5.1/tests/test_kurzweil_sample.py +103 -0
  137. samplerdisc-0.5.1/tests/test_loops.py +163 -0
  138. samplerdisc-0.5.1/tests/test_roland_s7xx.py +473 -0
  139. samplerdisc-0.5.1/tests/test_stereo.py +260 -0
  140. samplerdisc-0.5.1/tests/test_wav.py +107 -0
  141. samplerdisc-0.5.1/uv.lock +108 -0
@@ -0,0 +1,8 @@
1
+ # Keep the build context to what the hatchling build needs: pyproject.toml,
2
+ # src/, README.md and LICENSE are COPYed explicitly in the Containerfile, so
3
+ # everything else is noise that only slows the build and busts its cache.
4
+ *
5
+ !pyproject.toml
6
+ !README.md
7
+ !LICENSE
8
+ !src/
@@ -0,0 +1 @@
1
+ blank_issues_enabled: true
@@ -0,0 +1,43 @@
1
+ name: A disc won't read
2
+ description: A disc image fails to list or extract, or yields fewer samples than it should
3
+ labels: ["disc"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Thanks for the report. **Do not attach the disc image or any extracted audio** — these are commercial libraries and this repo is public. A description of the failure is what we need, not the library itself.
9
+ - type: dropdown
10
+ id: container
11
+ attributes:
12
+ label: Container format
13
+ description: The kind of image file. Detect by signature if you're unsure — the extension is often wrong.
14
+ options:
15
+ - .mdx (DAEMON Tools, often compressed)
16
+ - .nrg (Nero)
17
+ - .bin / .iso (raw)
18
+ - .cue / .bin pair
19
+ - Other / not sure
20
+ validations:
21
+ required: true
22
+ - type: input
23
+ id: source
24
+ attributes:
25
+ label: Where the image came from
26
+ description: e.g. archive.org, a personal FTP, a physical rip you made. Don't link to copyrighted material.
27
+ validations:
28
+ required: false
29
+ - type: textarea
30
+ id: command
31
+ attributes:
32
+ label: Command and full output
33
+ description: The exact command you ran and everything it printed. Paste text, not a screenshot.
34
+ render: shell
35
+ validations:
36
+ required: true
37
+ - type: textarea
38
+ id: expected
39
+ attributes:
40
+ label: What you expected
41
+ description: How many volumes or samples you believe are on the disc, and which specific one fails if you know it.
42
+ validations:
43
+ required: false
@@ -0,0 +1,33 @@
1
+ name: Support a new format
2
+ description: A container, filesystem, or sample format the tool doesn't handle yet
3
+ labels: ["enhancement"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ A new manufacturer is usually one new module in `fs/` and/or `sample/`, and nothing else — that separation is deliberate. Tell us which layer this is.
9
+ - type: dropdown
10
+ id: layer
11
+ attributes:
12
+ label: Which layer
13
+ options:
14
+ - Container (a new image wrapper, e.g. sector geometry / compression)
15
+ - Filesystem (how a sampler laid files onto the disc)
16
+ - Sample format (the bytes inside one file)
17
+ - Not sure
18
+ validations:
19
+ required: true
20
+ - type: input
21
+ id: hardware
22
+ attributes:
23
+ label: Sampler / hardware
24
+ description: e.g. E-mu ESI-32, Roland S-760, Ensoniq ASR-10
25
+ validations:
26
+ required: false
27
+ - type: textarea
28
+ id: what-we-know
29
+ attributes:
30
+ label: What's known about the format
31
+ description: Magic numbers, sector sizes, existing documentation or tools, sample images available. The more concrete the bytes, the better.
32
+ validations:
33
+ required: false
@@ -0,0 +1,24 @@
1
+ <!-- One deliverable per branch, one issue per deliverable. The issue is the spec. -->
2
+
3
+ Closes #
4
+
5
+ ## What this changes
6
+
7
+
8
+
9
+ ## Which layer
10
+
11
+ <!-- Keep the layers independent. Tick what this touches. -->
12
+
13
+ - [ ] `container/` — sectors in, flat 2048-byte sectors out (no sampler knowledge)
14
+ - [ ] `fs/` — a sampler filesystem
15
+ - [ ] `sample/` — a sample format
16
+ - [ ] `docs/` only
17
+
18
+ ## Checklist
19
+
20
+ - [ ] No disc images or extracted audio added (CI enforces this; see [ADR-0008](docs/adr/0008-no-media-in-the-repo.md))
21
+ - [ ] New format constants are documented in `docs/formats/` and asserted in a test
22
+ - [ ] `uv run ruff check .` and `uv run ruff format --check .` pass
23
+ - [ ] `uv run pytest -q` passes
24
+ - [ ] `uv tool install --editable . && samplerdisc --version` works
@@ -0,0 +1,60 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ verify:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - name: Install uv
15
+ uses: astral-sh/setup-uv@v5
16
+ with:
17
+ enable-cache: true
18
+
19
+ - run: uv sync --all-extras --dev
20
+
21
+ - name: Lint
22
+ run: uv run ruff check .
23
+
24
+ - name: Format
25
+ run: uv run ruff format --check .
26
+
27
+ - name: Test
28
+ run: uv run pytest -q
29
+
30
+ # A passing suite and a working installed entrypoint are not the same
31
+ # thing, and that gap is where a broken `bin` hides.
32
+ - name: Installed entrypoint
33
+ run: |
34
+ uv tool install --editable .
35
+ samplerdisc --version
36
+
37
+ # ADR-0008: no disc images or audio, ever. Enforced, not remembered.
38
+ - name: No media in tracked files
39
+ run: |
40
+ if git ls-files | grep -iE '\.(mdx|nrg|iso|img|bin|cue|mds|mdf|cdr|tao|wav|aif|aiff)$'; then
41
+ echo "::error::Disc image or audio file is tracked. See docs/adr/0008-no-media-in-the-repo.md"
42
+ exit 1
43
+ fi
44
+ echo "clean"
45
+
46
+ # The Containerfile is a runner for untrusted images (ADR-0038), and a
47
+ # Containerfile nobody builds is one that quietly stops working. This job
48
+ # builds it and smoke-tests the entrypoint. It is deliberately separate from
49
+ # `verify` and not part of branch protection's required check: a registry
50
+ # hiccup pulling the base image should not block a merge.
51
+ container:
52
+ runs-on: ubuntu-latest
53
+ steps:
54
+ - uses: actions/checkout@v4
55
+
56
+ - name: Build image
57
+ run: docker build -f Containerfile -t samplerdisc .
58
+
59
+ - name: Installed entrypoint
60
+ run: docker run --rm samplerdisc --version
@@ -0,0 +1,71 @@
1
+ name: Release
2
+
3
+ # Publish to PyPI when a v* tag is pushed. The tag is the trigger, so a release
4
+ # is `git tag vX.Y.Z && git push --tags` and nothing else — no token lives here.
5
+ # Uploads go through PyPI Trusted Publishing (OIDC): PyPI is configured to trust
6
+ # this repository's `pypi` environment, and the `publish` job mints a short-lived
7
+ # credential per run. There is no API token to leak from a public repo.
8
+ #
9
+ # One-time setup on PyPI (Trusted Publisher, "pending" until the first release):
10
+ # https://pypi.org/manage/account/publishing/
11
+ # PyPI project name: samplerdisc
12
+ # Owner: bmxcode
13
+ # Repository: samplerdisc
14
+ # Workflow name: release.yml
15
+ # Environment: pypi
16
+ # and create a repository environment named `pypi` (Settings -> Environments),
17
+ # optionally restricted to tags matching v*.
18
+
19
+ on:
20
+ push:
21
+ tags: ["v*"]
22
+
23
+ permissions: {}
24
+
25
+ jobs:
26
+ build:
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+
31
+ - name: Install uv
32
+ uses: astral-sh/setup-uv@v5
33
+
34
+ # The tag is the version, and a tag that disagrees with pyproject would
35
+ # publish the wrong number under the right name. Fail loud instead — the
36
+ # same "enforced, not remembered" reflex as the no-media check in ci.yml.
37
+ - name: Tag matches project version
38
+ run: |
39
+ tag="${GITHUB_REF_NAME#v}"
40
+ proj="$(uv version --short)"
41
+ if [ "$tag" != "$proj" ]; then
42
+ echo "::error::tag v$tag != pyproject version $proj"
43
+ exit 1
44
+ fi
45
+ echo "v$tag matches pyproject"
46
+
47
+ - name: Build sdist and wheel
48
+ run: uv build
49
+
50
+ - name: Check metadata renders
51
+ run: uvx twine check dist/*
52
+
53
+ - uses: actions/upload-artifact@v4
54
+ with:
55
+ name: dist
56
+ path: dist/
57
+
58
+ publish:
59
+ needs: build
60
+ runs-on: ubuntu-latest
61
+ environment: pypi
62
+ permissions:
63
+ id-token: write # OIDC token for Trusted Publishing; nothing else
64
+ steps:
65
+ - uses: actions/download-artifact@v4
66
+ with:
67
+ name: dist
68
+ path: dist/
69
+
70
+ - name: Publish to PyPI
71
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,44 @@
1
+ # Disc images and extracted audio are never committed. See ADR-0008.
2
+ # These are commercial sample libraries; the repo is public and git history
3
+ # is exposed retroactively. This list comes before anything else on purpose.
4
+ *.mdx
5
+ *.nrg
6
+ *.iso
7
+ *.img
8
+ *.bin
9
+ *.cue
10
+ *.mds
11
+ *.mdf
12
+ *.cdr
13
+ *.tao
14
+ *.ccd
15
+ *.wav
16
+ *.aif
17
+ *.aiff
18
+ out/
19
+ discs/
20
+ retro-sample-cds/
21
+
22
+ # Archives holding disc images are the same rule: a .zip of a sample library
23
+ # is still a sample library.
24
+ *.zip
25
+ *.7z
26
+ *.rar
27
+ *.tar
28
+ *.tar.gz
29
+ *.tgz
30
+
31
+ # Python
32
+ __pycache__/
33
+ *.py[cod]
34
+ .venv/
35
+ dist/
36
+ build/
37
+ *.egg-info/
38
+ .pytest_cache/
39
+ .ruff_cache/
40
+
41
+ # Editor / OS
42
+ .DS_Store
43
+ .idea/
44
+ .claude/