unitracker-player 0.3.0__tar.gz → 0.4.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 (21) hide show
  1. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/PKG-INFO +1 -1
  2. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/pyproject.toml +1 -1
  3. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/src/unitracker_player/player.py +5 -0
  4. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/src/unitracker_player.egg-info/PKG-INFO +1 -1
  5. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/tests/test_player.py +26 -0
  6. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/LICENSE +0 -0
  7. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/README.md +0 -0
  8. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/setup.cfg +0 -0
  9. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/src/unitracker_player/__init__.py +0 -0
  10. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/src/unitracker_player/cli.py +0 -0
  11. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/src/unitracker_player/document.py +0 -0
  12. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/src/unitracker_player/expr.py +0 -0
  13. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/src/unitracker_player/modulation.py +0 -0
  14. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/src/unitracker_player.egg-info/SOURCES.txt +0 -0
  15. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/src/unitracker_player.egg-info/dependency_links.txt +0 -0
  16. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/src/unitracker_player.egg-info/entry_points.txt +0 -0
  17. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/src/unitracker_player.egg-info/requires.txt +0 -0
  18. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/src/unitracker_player.egg-info/top_level.txt +0 -0
  19. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/tests/test_cli.py +0 -0
  20. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/tests/test_document.py +0 -0
  21. {unitracker_player-0.3.0 → unitracker_player-0.4.0}/tests/test_modulation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unitracker-player
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Plays canonical unitracker documents to SID register state per tick
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 = "unitracker-player"
7
- version = "0.3.0"
7
+ version = "0.4.0"
8
8
  description = "Plays canonical unitracker documents to SID register state per tick"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -16,6 +16,7 @@ from unitracker_player.modulation import SHAPES
16
16
  VERSION = "1.20"
17
17
  MAX_VOICES = 3
18
18
  REGISTERS = 25
19
+ ENVELOPE = ("ad", "sr")
19
20
  CONTROL, PULSE, FILTER = range(3)
20
21
  FREQ_MASK, PW_MASK, CUT_MASK = 0xFFFF, 0xFFF, 0x7FF
21
22
 
@@ -398,6 +399,10 @@ class Player:
398
399
  return [(t, (v, event, transpose, ins, None, None))]
399
400
  *ahead, (last, _) = ins.restart
400
401
  if isinstance(event.note, int):
402
+ if ahead:
403
+ wait, hold, ops = ahead[-1][0]
404
+ envelope = tuple(op for op in event.ops if op[0] in ENVELOPE)
405
+ ahead[-1] = ((wait, hold, ops + envelope), ahead[-1][1])
401
406
  for row, before in ahead:
402
407
  if t - before >= 0:
403
408
  self._lookahead[t - before].append((v, row))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unitracker-player
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Plays canonical unitracker documents to SID register state per tick
5
5
  Author: Josh Bailey
6
6
  License: Apache License
@@ -202,6 +202,32 @@ def test_a_note_less_event_splits_at_its_restart_lookahead(phrase, address, expe
202
202
  assert reg(play(doc(phrase, LEAD % HR3)), address) == expected
203
203
 
204
204
 
205
+ FIRST_TICK = "[{hold: 2, gate: 0, ad: 15, sr: 0}, {ctrl: 9, gate: 1, ad: 9, sr: 240}, {}, end]"
206
+
207
+
208
+ @pytest.mark.parametrize(
209
+ "phrase, restart, address, expected",
210
+ [
211
+ ("[C4.i1, D4, -*2]", FIRST_TICK, 5, [9] * 3 + [15] * 2 + [9] * 19),
212
+ ("[C4.i1, D4!ad:10, -*2]", FIRST_TICK, 5, [9] * 3 + [15] * 2 + [10] * 19),
213
+ ("[C4.i1, D4!sr:155, -*2]", FIRST_TICK, 6, [240] * 3 + [0] * 2 + [155] * 19),
214
+ ("[C4.i1, D4!sr:155, -*2]", FIRST_TICK, 5, [9] * 3 + [15] * 2 + [9] * 19),
215
+ ("[C4.i1, D4!ad:10!legato, -*2]", FIRST_TICK, 5, [9] * 6 + [10] * 18),
216
+ ("[C4.i1!ad:10, -*3]", FIRST_TICK, 5, [10] * 24),
217
+ ("[C4.i1, D4!ad:10, -*2]", HR, 5, [9] * 5 + [10] * 19),
218
+ ("[C4.i1, D4!ad:10, -*2]", "[{ctrl: 9}, end]", 5, [9] * 6 + [10] * 18),
219
+ ],
220
+ )
221
+ def test_a_notes_envelope_commands_apply_with_its_restarts_first_tick_row(
222
+ phrase, restart, address, expected
223
+ ):
224
+ """A note's !ad and !sr apply after its restart's last lookahead row, on that row's tick,
225
+ winning over the row's own ad and sr, and again on the note's tick after its instrument;
226
+ its other commands, a dropped row, a restart of one row and !legato leave them on its
227
+ tick."""
228
+ assert reg(play(doc(phrase, LEAD % restart)), address) == expected
229
+
230
+
205
231
  def test_a_note_triggers_on_its_step_after_its_restart_lookahead():
206
232
  """The frequency and the instrument's envelope take the note on its step."""
207
233
  frames = play(doc("[C4.i1, D4, -*2]", LEAD % HR))