pygoattracker 0.1.2__tar.gz → 0.1.3__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 (38) hide show
  1. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/PKG-INFO +1 -1
  2. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/pyproject.toml +1 -1
  3. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/player.py +15 -1
  4. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker.egg-info/PKG-INFO +1 -1
  5. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_player.py +18 -0
  6. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/LICENSE +0 -0
  7. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/README.md +0 -0
  8. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/setup.cfg +0 -0
  9. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/__init__.py +0 -0
  10. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/audio.py +0 -0
  11. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/cli.py +0 -0
  12. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/constants.py +0 -0
  13. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/convert.py +0 -0
  14. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/errors.py +0 -0
  15. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/legacy.py +0 -0
  16. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/model.py +0 -0
  17. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/ninja.py +0 -0
  18. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/reader.py +0 -0
  19. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/reglog.py +0 -0
  20. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/writer.py +0 -0
  21. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker.egg-info/SOURCES.txt +0 -0
  22. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker.egg-info/dependency_links.txt +0 -0
  23. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker.egg-info/entry_points.txt +0 -0
  24. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker.egg-info/requires.txt +0 -0
  25. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker.egg-info/top_level.txt +0 -0
  26. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_audio.py +0 -0
  27. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_cli.py +0 -0
  28. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_convert.py +0 -0
  29. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_coverage.py +0 -0
  30. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_examples.py +0 -0
  31. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_legacy.py +0 -0
  32. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_lint.py +0 -0
  33. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_model.py +0 -0
  34. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_ninja.py +0 -0
  35. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_reader.py +0 -0
  36. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_reglog.py +0 -0
  37. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_roundtrip.py +0 -0
  38. {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_writer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygoattracker
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Pure-Python reader, writer, and player for GoatTracker 2 (.SNG) songs
5
5
  Author: Josh Bailey
6
6
  License: Apache License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pygoattracker"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  description = "Pure-Python reader, writer, and player for GoatTracker 2 (.SNG) songs"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -89,9 +89,23 @@ class Player:
89
89
  adparam: int = constants.DEFAULT_ADPARAM,
90
90
  optimize_pulse: bool = True,
91
91
  optimize_realtime: bool = True,
92
+ freq_table=None,
92
93
  ):
93
94
  if not 0 <= subtune < len(song.subtunes):
94
95
  raise GoatTrackerError(f"no such subtune: {subtune}")
96
+ # The editor playroutine (gplay.c) reads a fixed note-frequency table
97
+ # zero-padded to 128 entries. The PACKED player (gt2reloc/player.s) lays
98
+ # the table out as ``freqtbllo[firstnote..lastnote]`` followed
99
+ # immediately by ``freqtblhi[...]`` with NO padding, then the songtable;
100
+ # an out-of-range note (a wavetable relative step past the last note)
101
+ # therefore overruns into the adjacent relocated memory and yields an
102
+ # image-specific frequency. ``freq_table`` lets a caller (the packed-SID
103
+ # decompiler) supply that exact 128-entry table read from the image so
104
+ # the render reproduces the packed player's overrun frequencies. When
105
+ # None the editor's zero-padded ``constants.FREQ_TABLE`` is used.
106
+ self._freq_table = (
107
+ constants.FREQ_TABLE if freq_table is None else tuple(freq_table)
108
+ )
95
109
  self.adparam = adparam & 0xFFFF
96
110
  self.optimize_pulse = optimize_pulse
97
111
  self.optimize_realtime = optimize_realtime
@@ -201,7 +215,7 @@ class Player:
201
215
  return self._rtable[table][(ptr - 1) & 0xFF]
202
216
 
203
217
  def _freq(self, note: int) -> int:
204
- return constants.FREQ_TABLE[note & 0x7F]
218
+ return self._freq_table[note & 0x7F]
205
219
 
206
220
  def _speed_value(self, idx: int, chan: _Channel) -> int:
207
221
  """16-bit speedtable value; high bit selects realtime calculation."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygoattracker
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Pure-Python reader, writer, and player for GoatTracker 2 (.SNG) songs
5
5
  Author: Josh Bailey
6
6
  License: Apache License
@@ -89,6 +89,24 @@ def test_single_note_timeline(song):
89
89
  assert (constants.CONTROL_REG, 0x09) in frames[55]
90
90
 
91
91
 
92
+ def test_freq_table_override(song):
93
+ """A per-instance ``freq_table`` overrides the editor's fixed note table so
94
+ the packed-SID decompiler can reproduce the packed player's overrun
95
+ frequencies (it lays the table out unpadded, so out-of-range notes read
96
+ adjacent image bytes). Default (None) keeps the editor table."""
97
+ # The default render emits C-4 at the standard table frequency.
98
+ base = play(Player(song), 60)
99
+ assert (constants.FREQ_LO_REG, C4_FREQ & 0xFF) in base[8]
100
+ assert (constants.FREQ_HI_REG, C4_FREQ >> 8) in base[8]
101
+ # Override note 48 (C-4) to an arbitrary image-specific value; the render
102
+ # now emits that frequency for the same note, nothing else changes.
103
+ table = list(constants.FREQ_TABLE)
104
+ table[48] = 0xBEEF
105
+ frames = play(Player(song, freq_table=table), 60)
106
+ assert (constants.FREQ_LO_REG, 0xEF) in frames[8]
107
+ assert (constants.FREQ_HI_REG, 0xBE) in frames[8]
108
+
109
+
92
110
  def test_keyoff_keyon(song):
93
111
  song.patterns[0].rows[4] = Row(note=constants.KEYOFF)
94
112
  song.patterns[0].rows[5] = Row(note=constants.KEYON)
File without changes
File without changes
File without changes