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.
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/PKG-INFO +1 -1
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/pyproject.toml +1 -1
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/player.py +15 -1
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker.egg-info/PKG-INFO +1 -1
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_player.py +18 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/LICENSE +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/README.md +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/setup.cfg +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/__init__.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/audio.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/cli.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/constants.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/convert.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/errors.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/legacy.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/model.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/ninja.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/reader.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/reglog.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker/writer.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker.egg-info/SOURCES.txt +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker.egg-info/dependency_links.txt +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker.egg-info/entry_points.txt +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker.egg-info/requires.txt +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/src/pygoattracker.egg-info/top_level.txt +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_audio.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_cli.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_convert.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_coverage.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_examples.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_legacy.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_lint.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_model.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_ninja.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_reader.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_reglog.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_roundtrip.py +0 -0
- {pygoattracker-0.1.2 → pygoattracker-0.1.3}/tests/test_writer.py +0 -0
|
@@ -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
|
|
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."""
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|