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.
Files changed (454) hide show
  1. trackmod-0.2.0/.gitignore +20 -0
  2. trackmod-0.2.0/CHANGELOG.md +21 -0
  3. trackmod-0.2.0/LICENSE +21 -0
  4. trackmod-0.2.0/PKG-INFO +170 -0
  5. trackmod-0.2.0/README.md +140 -0
  6. trackmod-0.2.0/docs/README.md +50 -0
  7. trackmod-0.2.0/docs/contributing/architecture.md +103 -0
  8. trackmod-0.2.0/docs/contributing/development.md +91 -0
  9. trackmod-0.2.0/docs/contributing/documentation.md +121 -0
  10. trackmod-0.2.0/docs/contributing/releasing.md +72 -0
  11. trackmod-0.2.0/docs/formats/README.md +76 -0
  12. trackmod-0.2.0/docs/formats/it.md +297 -0
  13. trackmod-0.2.0/docs/formats/mod.md +238 -0
  14. trackmod-0.2.0/docs/formats/s3m.md +287 -0
  15. trackmod-0.2.0/docs/formats/st.md +213 -0
  16. trackmod-0.2.0/docs/formats/xm.md +281 -0
  17. trackmod-0.2.0/docs/guide/budgeting.md +40 -0
  18. trackmod-0.2.0/docs/guide/converting.md +52 -0
  19. trackmod-0.2.0/docs/guide/instruments.md +95 -0
  20. trackmod-0.2.0/docs/guide/reading.md +90 -0
  21. trackmod-0.2.0/docs/guide/samples.md +79 -0
  22. trackmod-0.2.0/docs/guide/writing.md +62 -0
  23. trackmod-0.2.0/docs/reference/effects.md +102 -0
  24. trackmod-0.2.0/docs/reference/limits.md +266 -0
  25. trackmod-0.2.0/docs/reference/model.md +322 -0
  26. trackmod-0.2.0/docs/reference/volume.md +112 -0
  27. trackmod-0.2.0/pyproject.toml +110 -0
  28. trackmod-0.2.0/src/trackmod/__init__.py +79 -0
  29. trackmod-0.2.0/src/trackmod/binary/__init__.py +0 -0
  30. trackmod-0.2.0/src/trackmod/binary/bits.py +35 -0
  31. trackmod-0.2.0/src/trackmod/binary/cursor.py +101 -0
  32. trackmod-0.2.0/src/trackmod/binary/layout.py +12 -0
  33. trackmod-0.2.0/src/trackmod/binary/nibble.py +46 -0
  34. trackmod-0.2.0/src/trackmod/binary/pcm/__init__.py +0 -0
  35. trackmod-0.2.0/src/trackmod/binary/pcm/blocks.py +20 -0
  36. trackmod-0.2.0/src/trackmod/binary/pcm/codec.py +53 -0
  37. trackmod-0.2.0/src/trackmod/binary/pcm/encoding.py +13 -0
  38. trackmod-0.2.0/src/trackmod/binary/pcm/quantize.py +19 -0
  39. trackmod-0.2.0/src/trackmod/binary/pcm/sign.py +39 -0
  40. trackmod-0.2.0/src/trackmod/binary/records/__init__.py +0 -0
  41. trackmod-0.2.0/src/trackmod/binary/records/field.py +50 -0
  42. trackmod-0.2.0/src/trackmod/binary/records/record.py +99 -0
  43. trackmod-0.2.0/src/trackmod/binary/records/values.py +44 -0
  44. trackmod-0.2.0/src/trackmod/binary/text.py +31 -0
  45. trackmod-0.2.0/src/trackmod/binary/volume.py +114 -0
  46. trackmod-0.2.0/src/trackmod/binary/warnings.py +40 -0
  47. trackmod-0.2.0/src/trackmod/core/__init__.py +0 -0
  48. trackmod-0.2.0/src/trackmod/core/effects/__init__.py +0 -0
  49. trackmod-0.2.0/src/trackmod/core/effects/catalog.py +35 -0
  50. trackmod-0.2.0/src/trackmod/core/effects/effect.py +17 -0
  51. trackmod-0.2.0/src/trackmod/core/envelopes/__init__.py +0 -0
  52. trackmod-0.2.0/src/trackmod/core/envelopes/curve.py +85 -0
  53. trackmod-0.2.0/src/trackmod/core/envelopes/envelope.py +43 -0
  54. trackmod-0.2.0/src/trackmod/core/envelopes/kind.py +10 -0
  55. trackmod-0.2.0/src/trackmod/core/envelopes/point.py +16 -0
  56. trackmod-0.2.0/src/trackmod/core/envelopes/repair.py +85 -0
  57. trackmod-0.2.0/src/trackmod/core/envelopes/span.py +22 -0
  58. trackmod-0.2.0/src/trackmod/core/instruments/__init__.py +0 -0
  59. trackmod-0.2.0/src/trackmod/core/instruments/behavior.py +30 -0
  60. trackmod-0.2.0/src/trackmod/core/instruments/fade.py +61 -0
  61. trackmod-0.2.0/src/trackmod/core/instruments/instrument.py +122 -0
  62. trackmod-0.2.0/src/trackmod/core/instruments/keymap.py +38 -0
  63. trackmod-0.2.0/src/trackmod/core/instruments/repair.py +49 -0
  64. trackmod-0.2.0/src/trackmod/core/instruments/transfer.py +95 -0
  65. trackmod-0.2.0/src/trackmod/core/instruments/unit.py +42 -0
  66. trackmod-0.2.0/src/trackmod/core/notes/__init__.py +0 -0
  67. trackmod-0.2.0/src/trackmod/core/notes/checks.py +33 -0
  68. trackmod-0.2.0/src/trackmod/core/notes/codec.py +24 -0
  69. trackmod-0.2.0/src/trackmod/core/notes/command.py +20 -0
  70. trackmod-0.2.0/src/trackmod/core/notes/pitch.py +93 -0
  71. trackmod-0.2.0/src/trackmod/core/patterns/__init__.py +0 -0
  72. trackmod-0.2.0/src/trackmod/core/patterns/builder.py +59 -0
  73. trackmod-0.2.0/src/trackmod/core/patterns/cell.py +22 -0
  74. trackmod-0.2.0/src/trackmod/core/patterns/codec.py +38 -0
  75. trackmod-0.2.0/src/trackmod/core/patterns/column.py +19 -0
  76. trackmod-0.2.0/src/trackmod/core/patterns/grid.py +148 -0
  77. trackmod-0.2.0/src/trackmod/core/patterns/repair.py +36 -0
  78. trackmod-0.2.0/src/trackmod/core/repairs/__init__.py +0 -0
  79. trackmod-0.2.0/src/trackmod/core/repairs/report.py +47 -0
  80. trackmod-0.2.0/src/trackmod/core/samples/__init__.py +0 -0
  81. trackmod-0.2.0/src/trackmod/core/samples/depth.py +36 -0
  82. trackmod-0.2.0/src/trackmod/core/samples/loop.py +58 -0
  83. trackmod-0.2.0/src/trackmod/core/samples/repair.py +86 -0
  84. trackmod-0.2.0/src/trackmod/core/samples/sample.py +140 -0
  85. trackmod-0.2.0/src/trackmod/core/samples/vibrato.py +22 -0
  86. trackmod-0.2.0/src/trackmod/core/songs/__init__.py +0 -0
  87. trackmod-0.2.0/src/trackmod/core/songs/order.py +37 -0
  88. trackmod-0.2.0/src/trackmod/core/songs/playback.py +17 -0
  89. trackmod-0.2.0/src/trackmod/core/songs/repair.py +20 -0
  90. trackmod-0.2.0/src/trackmod/core/songs/song.py +67 -0
  91. trackmod-0.2.0/src/trackmod/core/timing/__init__.py +0 -0
  92. trackmod-0.2.0/src/trackmod/core/timing/clock.py +50 -0
  93. trackmod-0.2.0/src/trackmod/core/timing/lattice.py +122 -0
  94. trackmod-0.2.0/src/trackmod/core/timing/timing.py +14 -0
  95. trackmod-0.2.0/src/trackmod/core/timing/timings.py +54 -0
  96. trackmod-0.2.0/src/trackmod/core/voices/__init__.py +0 -0
  97. trackmod-0.2.0/src/trackmod/core/voices/convert.py +110 -0
  98. trackmod-0.2.0/src/trackmod/core/voices/voices.py +68 -0
  99. trackmod-0.2.0/src/trackmod/core/volumes/__init__.py +0 -0
  100. trackmod-0.2.0/src/trackmod/core/volumes/checks.py +41 -0
  101. trackmod-0.2.0/src/trackmod/core/volumes/codec.py +37 -0
  102. trackmod-0.2.0/src/trackmod/core/volumes/command.py +60 -0
  103. trackmod-0.2.0/src/trackmod/limits/__init__.py +0 -0
  104. trackmod-0.2.0/src/trackmod/limits/bound.py +42 -0
  105. trackmod-0.2.0/src/trackmod/limits/capability.py +45 -0
  106. trackmod-0.2.0/src/trackmod/limits/capacity.py +41 -0
  107. trackmod-0.2.0/src/trackmod/limits/checklist.py +30 -0
  108. trackmod-0.2.0/src/trackmod/limits/compliance.py +22 -0
  109. trackmod-0.2.0/src/trackmod/limits/error.py +31 -0
  110. trackmod-0.2.0/src/trackmod/limits/guard.py +13 -0
  111. trackmod-0.2.0/src/trackmod/limits/reach.py +35 -0
  112. trackmod-0.2.0/src/trackmod/limits/table.py +99 -0
  113. trackmod-0.2.0/src/trackmod/limits/tier.py +14 -0
  114. trackmod-0.2.0/src/trackmod/limits/violation.py +40 -0
  115. trackmod-0.2.0/src/trackmod/module/__init__.py +0 -0
  116. trackmod-0.2.0/src/trackmod/module/instrument.py +96 -0
  117. trackmod-0.2.0/src/trackmod/module/protocol.py +112 -0
  118. trackmod-0.2.0/src/trackmod/module/provenance.py +39 -0
  119. trackmod-0.2.0/src/trackmod/module/reaching.py +44 -0
  120. trackmod-0.2.0/src/trackmod/module/size.py +33 -0
  121. trackmod-0.2.0/src/trackmod/module/storage.py +106 -0
  122. trackmod-0.2.0/src/trackmod/py.typed +0 -0
  123. trackmod-0.2.0/src/trackmod/schema/__init__.py +0 -0
  124. trackmod-0.2.0/src/trackmod/schema/array.py +52 -0
  125. trackmod-0.2.0/src/trackmod/schema/config.py +6 -0
  126. trackmod-0.2.0/src/trackmod/schema/scalars.py +39 -0
  127. trackmod-0.2.0/src/trackmod/spec/__init__.py +0 -0
  128. trackmod-0.2.0/src/trackmod/spec/application.py +13 -0
  129. trackmod-0.2.0/src/trackmod/spec/clock.py +7 -0
  130. trackmod-0.2.0/src/trackmod/spec/grid.py +14 -0
  131. trackmod-0.2.0/src/trackmod/spec/levels.py +17 -0
  132. trackmod-0.2.0/src/trackmod/spec/pitch.py +24 -0
  133. trackmod-0.2.0/src/trackmod/spec/text.py +5 -0
  134. trackmod-0.2.0/src/trackmod/spec/volumes.py +8 -0
  135. trackmod-0.2.0/src/trackmod/spec/width.py +15 -0
  136. trackmod-0.2.0/src/trackmod/trackers/amiga/__init__.py +0 -0
  137. trackmod-0.2.0/src/trackmod/trackers/amiga/checks.py +65 -0
  138. trackmod-0.2.0/src/trackmod/trackers/amiga/layout/__init__.py +0 -0
  139. trackmod-0.2.0/src/trackmod/trackers/amiga/layout/file.py +10 -0
  140. trackmod-0.2.0/src/trackmod/trackers/amiga/layout/sample.py +17 -0
  141. trackmod-0.2.0/src/trackmod/trackers/amiga/note.py +122 -0
  142. trackmod-0.2.0/src/trackmod/trackers/amiga/patterns/__init__.py +0 -0
  143. trackmod-0.2.0/src/trackmod/trackers/amiga/patterns/packer.py +94 -0
  144. trackmod-0.2.0/src/trackmod/trackers/amiga/patterns/parser.py +109 -0
  145. trackmod-0.2.0/src/trackmod/trackers/amiga/patterns/sizing.py +11 -0
  146. trackmod-0.2.0/src/trackmod/trackers/amiga/reading.py +140 -0
  147. trackmod-0.2.0/src/trackmod/trackers/amiga/samples/__init__.py +0 -0
  148. trackmod-0.2.0/src/trackmod/trackers/amiga/samples/parser.py +87 -0
  149. trackmod-0.2.0/src/trackmod/trackers/amiga/samples/writer.py +122 -0
  150. trackmod-0.2.0/src/trackmod/trackers/amiga/sizing.py +32 -0
  151. trackmod-0.2.0/src/trackmod/trackers/amiga/spec/__init__.py +0 -0
  152. trackmod-0.2.0/src/trackmod/trackers/amiga/spec/cells.py +19 -0
  153. trackmod-0.2.0/src/trackmod/trackers/amiga/spec/defaults.py +7 -0
  154. trackmod-0.2.0/src/trackmod/trackers/amiga/spec/periods.py +34 -0
  155. trackmod-0.2.0/src/trackmod/trackers/amiga/spec/ranges.py +21 -0
  156. trackmod-0.2.0/src/trackmod/trackers/amiga/spec/sizes.py +11 -0
  157. trackmod-0.2.0/src/trackmod/trackers/amiga/spec/storage.py +9 -0
  158. trackmod-0.2.0/src/trackmod/trackers/amiga/tuning.py +31 -0
  159. trackmod-0.2.0/src/trackmod/trackers/amiga/writing.py +50 -0
  160. trackmod-0.2.0/src/trackmod/trackers/it/__init__.py +0 -0
  161. trackmod-0.2.0/src/trackmod/trackers/it/addressing.py +29 -0
  162. trackmod-0.2.0/src/trackmod/trackers/it/checks.py +120 -0
  163. trackmod-0.2.0/src/trackmod/trackers/it/detection.py +15 -0
  164. trackmod-0.2.0/src/trackmod/trackers/it/effects/__init__.py +0 -0
  165. trackmod-0.2.0/src/trackmod/trackers/it/effects/catalog.py +115 -0
  166. trackmod-0.2.0/src/trackmod/trackers/it/effects/command.py +59 -0
  167. trackmod-0.2.0/src/trackmod/trackers/it/extensions.py +118 -0
  168. trackmod-0.2.0/src/trackmod/trackers/it/fade.py +19 -0
  169. trackmod-0.2.0/src/trackmod/trackers/it/instrument_file.py +182 -0
  170. trackmod-0.2.0/src/trackmod/trackers/it/instruments/__init__.py +0 -0
  171. trackmod-0.2.0/src/trackmod/trackers/it/instruments/envelope.py +109 -0
  172. trackmod-0.2.0/src/trackmod/trackers/it/instruments/keymap.py +53 -0
  173. trackmod-0.2.0/src/trackmod/trackers/it/instruments/parser.py +100 -0
  174. trackmod-0.2.0/src/trackmod/trackers/it/instruments/writer.py +82 -0
  175. trackmod-0.2.0/src/trackmod/trackers/it/layout/__init__.py +0 -0
  176. trackmod-0.2.0/src/trackmod/trackers/it/layout/envelope.py +51 -0
  177. trackmod-0.2.0/src/trackmod/trackers/it/layout/file.py +38 -0
  178. trackmod-0.2.0/src/trackmod/trackers/it/layout/instrument.py +54 -0
  179. trackmod-0.2.0/src/trackmod/trackers/it/layout/pattern.py +14 -0
  180. trackmod-0.2.0/src/trackmod/trackers/it/layout/sample.py +34 -0
  181. trackmod-0.2.0/src/trackmod/trackers/it/limits.py +8 -0
  182. trackmod-0.2.0/src/trackmod/trackers/it/message.py +50 -0
  183. trackmod-0.2.0/src/trackmod/trackers/it/module.py +229 -0
  184. trackmod-0.2.0/src/trackmod/trackers/it/note.py +43 -0
  185. trackmod-0.2.0/src/trackmod/trackers/it/panning.py +12 -0
  186. trackmod-0.2.0/src/trackmod/trackers/it/parser.py +309 -0
  187. trackmod-0.2.0/src/trackmod/trackers/it/patterns/__init__.py +0 -0
  188. trackmod-0.2.0/src/trackmod/trackers/it/patterns/cost.py +16 -0
  189. trackmod-0.2.0/src/trackmod/trackers/it/patterns/encoded.py +9 -0
  190. trackmod-0.2.0/src/trackmod/trackers/it/patterns/memory.py +19 -0
  191. trackmod-0.2.0/src/trackmod/trackers/it/patterns/packer.py +178 -0
  192. trackmod-0.2.0/src/trackmod/trackers/it/patterns/parser.py +211 -0
  193. trackmod-0.2.0/src/trackmod/trackers/it/patterns/sizing.py +117 -0
  194. trackmod-0.2.0/src/trackmod/trackers/it/patterns/width.py +36 -0
  195. trackmod-0.2.0/src/trackmod/trackers/it/samples/__init__.py +0 -0
  196. trackmod-0.2.0/src/trackmod/trackers/it/samples/compression.py +159 -0
  197. trackmod-0.2.0/src/trackmod/trackers/it/samples/parser.py +288 -0
  198. trackmod-0.2.0/src/trackmod/trackers/it/samples/writer.py +97 -0
  199. trackmod-0.2.0/src/trackmod/trackers/it/settings.py +68 -0
  200. trackmod-0.2.0/src/trackmod/trackers/it/sizing.py +55 -0
  201. trackmod-0.2.0/src/trackmod/trackers/it/spec/__init__.py +0 -0
  202. trackmod-0.2.0/src/trackmod/trackers/it/spec/capacities.py +122 -0
  203. trackmod-0.2.0/src/trackmod/trackers/it/spec/cells.py +52 -0
  204. trackmod-0.2.0/src/trackmod/trackers/it/spec/defaults.py +24 -0
  205. trackmod-0.2.0/src/trackmod/trackers/it/spec/effects.py +19 -0
  206. trackmod-0.2.0/src/trackmod/trackers/it/spec/extensions.py +13 -0
  207. trackmod-0.2.0/src/trackmod/trackers/it/spec/flags.py +62 -0
  208. trackmod-0.2.0/src/trackmod/trackers/it/spec/identity.py +14 -0
  209. trackmod-0.2.0/src/trackmod/trackers/it/spec/orders.py +5 -0
  210. trackmod-0.2.0/src/trackmod/trackers/it/spec/ranges.py +56 -0
  211. trackmod-0.2.0/src/trackmod/trackers/it/spec/sizes.py +20 -0
  212. trackmod-0.2.0/src/trackmod/trackers/it/spec/storage.py +27 -0
  213. trackmod-0.2.0/src/trackmod/trackers/it/spec/volume.py +51 -0
  214. trackmod-0.2.0/src/trackmod/trackers/it/timing.py +10 -0
  215. trackmod-0.2.0/src/trackmod/trackers/it/version.py +100 -0
  216. trackmod-0.2.0/src/trackmod/trackers/it/writer.py +133 -0
  217. trackmod-0.2.0/src/trackmod/trackers/mod/__init__.py +0 -0
  218. trackmod-0.2.0/src/trackmod/trackers/mod/detection.py +11 -0
  219. trackmod-0.2.0/src/trackmod/trackers/mod/dialect.py +60 -0
  220. trackmod-0.2.0/src/trackmod/trackers/mod/effects/__init__.py +0 -0
  221. trackmod-0.2.0/src/trackmod/trackers/mod/effects/catalog.py +106 -0
  222. trackmod-0.2.0/src/trackmod/trackers/mod/effects/command.py +63 -0
  223. trackmod-0.2.0/src/trackmod/trackers/mod/layout/__init__.py +0 -0
  224. trackmod-0.2.0/src/trackmod/trackers/mod/layout/file.py +17 -0
  225. trackmod-0.2.0/src/trackmod/trackers/mod/limits.py +8 -0
  226. trackmod-0.2.0/src/trackmod/trackers/mod/module.py +261 -0
  227. trackmod-0.2.0/src/trackmod/trackers/mod/parser.py +102 -0
  228. trackmod-0.2.0/src/trackmod/trackers/mod/settings.py +27 -0
  229. trackmod-0.2.0/src/trackmod/trackers/mod/sizing.py +9 -0
  230. trackmod-0.2.0/src/trackmod/trackers/mod/spec/__init__.py +0 -0
  231. trackmod-0.2.0/src/trackmod/trackers/mod/spec/capacities.py +58 -0
  232. trackmod-0.2.0/src/trackmod/trackers/mod/spec/dialects.py +34 -0
  233. trackmod-0.2.0/src/trackmod/trackers/mod/spec/effects.py +19 -0
  234. trackmod-0.2.0/src/trackmod/trackers/mod/spec/identity.py +12 -0
  235. trackmod-0.2.0/src/trackmod/trackers/mod/spec/ranges.py +21 -0
  236. trackmod-0.2.0/src/trackmod/trackers/mod/spec/sizes.py +13 -0
  237. trackmod-0.2.0/src/trackmod/trackers/mod/spec/storage.py +17 -0
  238. trackmod-0.2.0/src/trackmod/trackers/mod/tag.py +79 -0
  239. trackmod-0.2.0/src/trackmod/trackers/mod/timing.py +6 -0
  240. trackmod-0.2.0/src/trackmod/trackers/mod/writer.py +58 -0
  241. trackmod-0.2.0/src/trackmod/trackers/registry.py +277 -0
  242. trackmod-0.2.0/src/trackmod/trackers/s3m/__init__.py +0 -0
  243. trackmod-0.2.0/src/trackmod/trackers/s3m/channels.py +38 -0
  244. trackmod-0.2.0/src/trackmod/trackers/s3m/checks.py +91 -0
  245. trackmod-0.2.0/src/trackmod/trackers/s3m/detection.py +10 -0
  246. trackmod-0.2.0/src/trackmod/trackers/s3m/effects/__init__.py +0 -0
  247. trackmod-0.2.0/src/trackmod/trackers/s3m/effects/catalog.py +105 -0
  248. trackmod-0.2.0/src/trackmod/trackers/s3m/effects/command.py +54 -0
  249. trackmod-0.2.0/src/trackmod/trackers/s3m/layout/__init__.py +0 -0
  250. trackmod-0.2.0/src/trackmod/trackers/s3m/layout/file.py +36 -0
  251. trackmod-0.2.0/src/trackmod/trackers/s3m/layout/instrument.py +28 -0
  252. trackmod-0.2.0/src/trackmod/trackers/s3m/layout/pattern.py +10 -0
  253. trackmod-0.2.0/src/trackmod/trackers/s3m/limits.py +8 -0
  254. trackmod-0.2.0/src/trackmod/trackers/s3m/module.py +252 -0
  255. trackmod-0.2.0/src/trackmod/trackers/s3m/note.py +101 -0
  256. trackmod-0.2.0/src/trackmod/trackers/s3m/panning.py +22 -0
  257. trackmod-0.2.0/src/trackmod/trackers/s3m/parapointers.py +41 -0
  258. trackmod-0.2.0/src/trackmod/trackers/s3m/parser.py +233 -0
  259. trackmod-0.2.0/src/trackmod/trackers/s3m/patterns/__init__.py +0 -0
  260. trackmod-0.2.0/src/trackmod/trackers/s3m/patterns/packer.py +103 -0
  261. trackmod-0.2.0/src/trackmod/trackers/s3m/patterns/parser.py +144 -0
  262. trackmod-0.2.0/src/trackmod/trackers/s3m/patterns/sizing.py +37 -0
  263. trackmod-0.2.0/src/trackmod/trackers/s3m/placement.py +89 -0
  264. trackmod-0.2.0/src/trackmod/trackers/s3m/samples/__init__.py +0 -0
  265. trackmod-0.2.0/src/trackmod/trackers/s3m/samples/parser.py +211 -0
  266. trackmod-0.2.0/src/trackmod/trackers/s3m/samples/writer.py +102 -0
  267. trackmod-0.2.0/src/trackmod/trackers/s3m/settings.py +67 -0
  268. trackmod-0.2.0/src/trackmod/trackers/s3m/sizing.py +44 -0
  269. trackmod-0.2.0/src/trackmod/trackers/s3m/spec/__init__.py +0 -0
  270. trackmod-0.2.0/src/trackmod/trackers/s3m/spec/capacities.py +100 -0
  271. trackmod-0.2.0/src/trackmod/trackers/s3m/spec/cells.py +45 -0
  272. trackmod-0.2.0/src/trackmod/trackers/s3m/spec/defaults.py +17 -0
  273. trackmod-0.2.0/src/trackmod/trackers/s3m/spec/effects.py +19 -0
  274. trackmod-0.2.0/src/trackmod/trackers/s3m/spec/flags.py +57 -0
  275. trackmod-0.2.0/src/trackmod/trackers/s3m/spec/identity.py +18 -0
  276. trackmod-0.2.0/src/trackmod/trackers/s3m/spec/keys.py +16 -0
  277. trackmod-0.2.0/src/trackmod/trackers/s3m/spec/orders.py +4 -0
  278. trackmod-0.2.0/src/trackmod/trackers/s3m/spec/ranges.py +58 -0
  279. trackmod-0.2.0/src/trackmod/trackers/s3m/spec/sizes.py +20 -0
  280. trackmod-0.2.0/src/trackmod/trackers/s3m/spec/storage.py +35 -0
  281. trackmod-0.2.0/src/trackmod/trackers/s3m/spec/volume.py +24 -0
  282. trackmod-0.2.0/src/trackmod/trackers/s3m/timing.py +10 -0
  283. trackmod-0.2.0/src/trackmod/trackers/s3m/version.py +98 -0
  284. trackmod-0.2.0/src/trackmod/trackers/s3m/writer.py +176 -0
  285. trackmod-0.2.0/src/trackmod/trackers/st/__init__.py +0 -0
  286. trackmod-0.2.0/src/trackmod/trackers/st/detection.py +36 -0
  287. trackmod-0.2.0/src/trackmod/trackers/st/effects/__init__.py +0 -0
  288. trackmod-0.2.0/src/trackmod/trackers/st/effects/catalog.py +110 -0
  289. trackmod-0.2.0/src/trackmod/trackers/st/effects/command.py +25 -0
  290. trackmod-0.2.0/src/trackmod/trackers/st/layout/__init__.py +0 -0
  291. trackmod-0.2.0/src/trackmod/trackers/st/layout/file.py +15 -0
  292. trackmod-0.2.0/src/trackmod/trackers/st/limits.py +8 -0
  293. trackmod-0.2.0/src/trackmod/trackers/st/module.py +244 -0
  294. trackmod-0.2.0/src/trackmod/trackers/st/parser.py +95 -0
  295. trackmod-0.2.0/src/trackmod/trackers/st/settings.py +20 -0
  296. trackmod-0.2.0/src/trackmod/trackers/st/sizing.py +9 -0
  297. trackmod-0.2.0/src/trackmod/trackers/st/spec/__init__.py +0 -0
  298. trackmod-0.2.0/src/trackmod/trackers/st/spec/capacities.py +42 -0
  299. trackmod-0.2.0/src/trackmod/trackers/st/spec/defaults.py +3 -0
  300. trackmod-0.2.0/src/trackmod/trackers/st/spec/effects.py +10 -0
  301. trackmod-0.2.0/src/trackmod/trackers/st/spec/identity.py +3 -0
  302. trackmod-0.2.0/src/trackmod/trackers/st/spec/ranges.py +21 -0
  303. trackmod-0.2.0/src/trackmod/trackers/st/spec/sizes.py +14 -0
  304. trackmod-0.2.0/src/trackmod/trackers/st/spec/storage.py +17 -0
  305. trackmod-0.2.0/src/trackmod/trackers/st/timing.py +10 -0
  306. trackmod-0.2.0/src/trackmod/trackers/st/writer.py +51 -0
  307. trackmod-0.2.0/src/trackmod/trackers/xm/__init__.py +0 -0
  308. trackmod-0.2.0/src/trackmod/trackers/xm/addressing.py +22 -0
  309. trackmod-0.2.0/src/trackmod/trackers/xm/checks.py +122 -0
  310. trackmod-0.2.0/src/trackmod/trackers/xm/detection.py +20 -0
  311. trackmod-0.2.0/src/trackmod/trackers/xm/effects/__init__.py +0 -0
  312. trackmod-0.2.0/src/trackmod/trackers/xm/effects/catalog.py +113 -0
  313. trackmod-0.2.0/src/trackmod/trackers/xm/effects/command.py +62 -0
  314. trackmod-0.2.0/src/trackmod/trackers/xm/fade.py +19 -0
  315. trackmod-0.2.0/src/trackmod/trackers/xm/instrument_file.py +188 -0
  316. trackmod-0.2.0/src/trackmod/trackers/xm/instruments/__init__.py +0 -0
  317. trackmod-0.2.0/src/trackmod/trackers/xm/instruments/envelope.py +118 -0
  318. trackmod-0.2.0/src/trackmod/trackers/xm/instruments/group.py +45 -0
  319. trackmod-0.2.0/src/trackmod/trackers/xm/instruments/grouping.py +100 -0
  320. trackmod-0.2.0/src/trackmod/trackers/xm/instruments/keymap.py +26 -0
  321. trackmod-0.2.0/src/trackmod/trackers/xm/instruments/parser.py +83 -0
  322. trackmod-0.2.0/src/trackmod/trackers/xm/instruments/writer.py +144 -0
  323. trackmod-0.2.0/src/trackmod/trackers/xm/layout/__init__.py +0 -0
  324. trackmod-0.2.0/src/trackmod/trackers/xm/layout/envelope.py +75 -0
  325. trackmod-0.2.0/src/trackmod/trackers/xm/layout/file.py +30 -0
  326. trackmod-0.2.0/src/trackmod/trackers/xm/layout/instrument.py +79 -0
  327. trackmod-0.2.0/src/trackmod/trackers/xm/layout/pattern.py +15 -0
  328. trackmod-0.2.0/src/trackmod/trackers/xm/layout/sample.py +21 -0
  329. trackmod-0.2.0/src/trackmod/trackers/xm/limits.py +8 -0
  330. trackmod-0.2.0/src/trackmod/trackers/xm/module.py +246 -0
  331. trackmod-0.2.0/src/trackmod/trackers/xm/note.py +83 -0
  332. trackmod-0.2.0/src/trackmod/trackers/xm/parser.py +166 -0
  333. trackmod-0.2.0/src/trackmod/trackers/xm/patterns/__init__.py +0 -0
  334. trackmod-0.2.0/src/trackmod/trackers/xm/patterns/encoded.py +32 -0
  335. trackmod-0.2.0/src/trackmod/trackers/xm/patterns/packer.py +80 -0
  336. trackmod-0.2.0/src/trackmod/trackers/xm/patterns/parser.py +163 -0
  337. trackmod-0.2.0/src/trackmod/trackers/xm/patterns/sizing.py +37 -0
  338. trackmod-0.2.0/src/trackmod/trackers/xm/samples/__init__.py +0 -0
  339. trackmod-0.2.0/src/trackmod/trackers/xm/samples/parser.py +139 -0
  340. trackmod-0.2.0/src/trackmod/trackers/xm/samples/writer.py +73 -0
  341. trackmod-0.2.0/src/trackmod/trackers/xm/settings.py +23 -0
  342. trackmod-0.2.0/src/trackmod/trackers/xm/sizing.py +52 -0
  343. trackmod-0.2.0/src/trackmod/trackers/xm/spec/__init__.py +0 -0
  344. trackmod-0.2.0/src/trackmod/trackers/xm/spec/capacities.py +110 -0
  345. trackmod-0.2.0/src/trackmod/trackers/xm/spec/cells.py +36 -0
  346. trackmod-0.2.0/src/trackmod/trackers/xm/spec/defaults.py +13 -0
  347. trackmod-0.2.0/src/trackmod/trackers/xm/spec/effects.py +19 -0
  348. trackmod-0.2.0/src/trackmod/trackers/xm/spec/flags.py +37 -0
  349. trackmod-0.2.0/src/trackmod/trackers/xm/spec/identity.py +13 -0
  350. trackmod-0.2.0/src/trackmod/trackers/xm/spec/ranges.py +70 -0
  351. trackmod-0.2.0/src/trackmod/trackers/xm/spec/sizes.py +32 -0
  352. trackmod-0.2.0/src/trackmod/trackers/xm/spec/storage.py +27 -0
  353. trackmod-0.2.0/src/trackmod/trackers/xm/spec/tuning.py +12 -0
  354. trackmod-0.2.0/src/trackmod/trackers/xm/spec/volume.py +67 -0
  355. trackmod-0.2.0/src/trackmod/trackers/xm/timing.py +10 -0
  356. trackmod-0.2.0/src/trackmod/trackers/xm/tuning.py +73 -0
  357. trackmod-0.2.0/src/trackmod/trackers/xm/writer.py +62 -0
  358. trackmod-0.2.0/src/trackmod/utils/__init__.py +0 -0
  359. trackmod-0.2.0/src/trackmod/utils/arithmetic.py +47 -0
  360. trackmod-0.2.0/src/trackmod/wave/__init__.py +0 -0
  361. trackmod-0.2.0/src/trackmod/wave/chunks.py +77 -0
  362. trackmod-0.2.0/src/trackmod/wave/layout.py +88 -0
  363. trackmod-0.2.0/src/trackmod/wave/parser.py +225 -0
  364. trackmod-0.2.0/src/trackmod/wave/settings.py +25 -0
  365. trackmod-0.2.0/src/trackmod/wave/spec.py +64 -0
  366. trackmod-0.2.0/src/trackmod/wave/writer.py +213 -0
  367. trackmod-0.2.0/tests/binary/test_bits.py +33 -0
  368. trackmod-0.2.0/tests/binary/test_cursor.py +65 -0
  369. trackmod-0.2.0/tests/binary/test_nibble.py +37 -0
  370. trackmod-0.2.0/tests/binary/test_pcm.py +125 -0
  371. trackmod-0.2.0/tests/binary/test_record.py +79 -0
  372. trackmod-0.2.0/tests/binary/test_text.py +34 -0
  373. trackmod-0.2.0/tests/binary/test_values.py +39 -0
  374. trackmod-0.2.0/tests/binary/test_volume.py +91 -0
  375. trackmod-0.2.0/tests/conftest.py +208 -0
  376. trackmod-0.2.0/tests/core/test_clock.py +47 -0
  377. trackmod-0.2.0/tests/core/test_curve.py +80 -0
  378. trackmod-0.2.0/tests/core/test_envelopes.py +42 -0
  379. trackmod-0.2.0/tests/core/test_fade.py +79 -0
  380. trackmod-0.2.0/tests/core/test_instruments.py +61 -0
  381. trackmod-0.2.0/tests/core/test_notes.py +72 -0
  382. trackmod-0.2.0/tests/core/test_patterns.py +173 -0
  383. trackmod-0.2.0/tests/core/test_repairs.py +205 -0
  384. trackmod-0.2.0/tests/core/test_samples.py +115 -0
  385. trackmod-0.2.0/tests/core/test_songs.py +111 -0
  386. trackmod-0.2.0/tests/core/test_timing.py +98 -0
  387. trackmod-0.2.0/tests/core/test_transfer.py +127 -0
  388. trackmod-0.2.0/tests/core/test_voices.py +117 -0
  389. trackmod-0.2.0/tests/core/test_volumes.py +59 -0
  390. trackmod-0.2.0/tests/limits/test_limits.py +212 -0
  391. trackmod-0.2.0/tests/limits/test_tables.py +152 -0
  392. trackmod-0.2.0/tests/module/test_provenance.py +32 -0
  393. trackmod-0.2.0/tests/module/test_storage.py +52 -0
  394. trackmod-0.2.0/tests/test_boundaries.py +95 -0
  395. trackmod-0.2.0/tests/test_formats.py +1272 -0
  396. trackmod-0.2.0/tests/test_package.py +147 -0
  397. trackmod-0.2.0/tests/trackers/amiga/conftest.py +112 -0
  398. trackmod-0.2.0/tests/trackers/amiga/test_patterns.py +126 -0
  399. trackmod-0.2.0/tests/trackers/amiga/test_periods.py +139 -0
  400. trackmod-0.2.0/tests/trackers/amiga/test_samples.py +173 -0
  401. trackmod-0.2.0/tests/trackers/it/conftest.py +33 -0
  402. trackmod-0.2.0/tests/trackers/it/test_addressing.py +97 -0
  403. trackmod-0.2.0/tests/trackers/it/test_compression.py +106 -0
  404. trackmod-0.2.0/tests/trackers/it/test_effects.py +55 -0
  405. trackmod-0.2.0/tests/trackers/it/test_extensions.py +185 -0
  406. trackmod-0.2.0/tests/trackers/it/test_instrument_file.py +151 -0
  407. trackmod-0.2.0/tests/trackers/it/test_instruments.py +23 -0
  408. trackmod-0.2.0/tests/trackers/it/test_layout.py +55 -0
  409. trackmod-0.2.0/tests/trackers/it/test_limits.py +250 -0
  410. trackmod-0.2.0/tests/trackers/it/test_message.py +71 -0
  411. trackmod-0.2.0/tests/trackers/it/test_module.py +274 -0
  412. trackmod-0.2.0/tests/trackers/it/test_panning.py +32 -0
  413. trackmod-0.2.0/tests/trackers/it/test_patterns.py +266 -0
  414. trackmod-0.2.0/tests/trackers/it/test_samples.py +270 -0
  415. trackmod-0.2.0/tests/trackers/it/test_version.py +63 -0
  416. trackmod-0.2.0/tests/trackers/mod/conftest.py +63 -0
  417. trackmod-0.2.0/tests/trackers/mod/test_dialect.py +82 -0
  418. trackmod-0.2.0/tests/trackers/mod/test_effects.py +86 -0
  419. trackmod-0.2.0/tests/trackers/mod/test_layout.py +63 -0
  420. trackmod-0.2.0/tests/trackers/mod/test_limits.py +144 -0
  421. trackmod-0.2.0/tests/trackers/mod/test_module.py +324 -0
  422. trackmod-0.2.0/tests/trackers/mod/test_timing.py +31 -0
  423. trackmod-0.2.0/tests/trackers/s3m/conftest.py +259 -0
  424. trackmod-0.2.0/tests/trackers/s3m/test_channels.py +35 -0
  425. trackmod-0.2.0/tests/trackers/s3m/test_effects.py +70 -0
  426. trackmod-0.2.0/tests/trackers/s3m/test_layout.py +54 -0
  427. trackmod-0.2.0/tests/trackers/s3m/test_limits.py +241 -0
  428. trackmod-0.2.0/tests/trackers/s3m/test_module.py +460 -0
  429. trackmod-0.2.0/tests/trackers/s3m/test_notes.py +57 -0
  430. trackmod-0.2.0/tests/trackers/s3m/test_panning.py +27 -0
  431. trackmod-0.2.0/tests/trackers/s3m/test_patterns.py +176 -0
  432. trackmod-0.2.0/tests/trackers/s3m/test_placement.py +54 -0
  433. trackmod-0.2.0/tests/trackers/s3m/test_samples.py +240 -0
  434. trackmod-0.2.0/tests/trackers/s3m/test_timing.py +27 -0
  435. trackmod-0.2.0/tests/trackers/s3m/test_volume.py +42 -0
  436. trackmod-0.2.0/tests/trackers/st/conftest.py +62 -0
  437. trackmod-0.2.0/tests/trackers/st/test_detection.py +99 -0
  438. trackmod-0.2.0/tests/trackers/st/test_effects.py +47 -0
  439. trackmod-0.2.0/tests/trackers/st/test_layout.py +30 -0
  440. trackmod-0.2.0/tests/trackers/st/test_limits.py +58 -0
  441. trackmod-0.2.0/tests/trackers/st/test_module.py +137 -0
  442. trackmod-0.2.0/tests/trackers/test_detection.py +33 -0
  443. trackmod-0.2.0/tests/trackers/xm/conftest.py +124 -0
  444. trackmod-0.2.0/tests/trackers/xm/test_effects.py +76 -0
  445. trackmod-0.2.0/tests/trackers/xm/test_instrument_file.py +186 -0
  446. trackmod-0.2.0/tests/trackers/xm/test_layout.py +75 -0
  447. trackmod-0.2.0/tests/trackers/xm/test_limits.py +274 -0
  448. trackmod-0.2.0/tests/trackers/xm/test_module.py +446 -0
  449. trackmod-0.2.0/tests/trackers/xm/test_patterns.py +268 -0
  450. trackmod-0.2.0/tests/trackers/xm/test_samples.py +74 -0
  451. trackmod-0.2.0/tests/trackers/xm/test_timing.py +34 -0
  452. trackmod-0.2.0/tests/trackers/xm/test_tuning.py +85 -0
  453. trackmod-0.2.0/tests/wave/test_chunks.py +68 -0
  454. trackmod-0.2.0/tests/wave/test_samples.py +241 -0
@@ -0,0 +1,20 @@
1
+ __pycache__/
2
+ .claude/
3
+ .idea/
4
+ .ipynb_checkpoints/
5
+ .mypy_cache/
6
+ .pytest_cache/
7
+ .venv/
8
+ .venv-build/
9
+ .vscode/
10
+ dist/
11
+
12
+ *.pyc
13
+ *.pyo
14
+ *.coverage
15
+ *.coverage.*
16
+
17
+ *.mod
18
+ *.xm
19
+ *.s3m
20
+ *.it
@@ -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.
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/trackmod.svg)](https://pypi.org/project/trackmod/)
34
+ [![Python](https://img.shields.io/pypi/pyversions/trackmod.svg)](https://pypi.org/project/trackmod/)
35
+ [![License](https://img.shields.io/github/license/JakimPL/TrackMod.svg)](https://github.com/JakimPL/TrackMod/blob/main/LICENSE)
36
+ [![CI](https://github.com/JakimPL/TrackMod/actions/workflows/ci.yml/badge.svg)](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).
@@ -0,0 +1,140 @@
1
+ # TrackMod
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/trackmod.svg)](https://pypi.org/project/trackmod/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/trackmod.svg)](https://pypi.org/project/trackmod/)
5
+ [![License](https://img.shields.io/github/license/JakimPL/TrackMod.svg)](https://github.com/JakimPL/TrackMod/blob/main/LICENSE)
6
+ [![CI](https://github.com/JakimPL/TrackMod/actions/workflows/ci.yml/badge.svg)](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`.