unitracker-player 0.2.0__tar.gz → 0.3.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.2.0 → unitracker_player-0.3.0}/PKG-INFO +2 -1
  2. {unitracker_player-0.2.0 → unitracker_player-0.3.0}/README.md +1 -0
  3. {unitracker_player-0.2.0 → unitracker_player-0.3.0}/pyproject.toml +1 -1
  4. {unitracker_player-0.2.0 → unitracker_player-0.3.0}/src/unitracker_player/player.py +18 -20
  5. {unitracker_player-0.2.0 → unitracker_player-0.3.0}/src/unitracker_player.egg-info/PKG-INFO +2 -1
  6. {unitracker_player-0.2.0 → unitracker_player-0.3.0}/tests/test_player.py +11 -0
  7. {unitracker_player-0.2.0 → unitracker_player-0.3.0}/LICENSE +0 -0
  8. {unitracker_player-0.2.0 → unitracker_player-0.3.0}/setup.cfg +0 -0
  9. {unitracker_player-0.2.0 → unitracker_player-0.3.0}/src/unitracker_player/__init__.py +0 -0
  10. {unitracker_player-0.2.0 → unitracker_player-0.3.0}/src/unitracker_player/cli.py +0 -0
  11. {unitracker_player-0.2.0 → unitracker_player-0.3.0}/src/unitracker_player/document.py +0 -0
  12. {unitracker_player-0.2.0 → unitracker_player-0.3.0}/src/unitracker_player/expr.py +0 -0
  13. {unitracker_player-0.2.0 → unitracker_player-0.3.0}/src/unitracker_player/modulation.py +0 -0
  14. {unitracker_player-0.2.0 → unitracker_player-0.3.0}/src/unitracker_player.egg-info/SOURCES.txt +0 -0
  15. {unitracker_player-0.2.0 → unitracker_player-0.3.0}/src/unitracker_player.egg-info/dependency_links.txt +0 -0
  16. {unitracker_player-0.2.0 → unitracker_player-0.3.0}/src/unitracker_player.egg-info/entry_points.txt +0 -0
  17. {unitracker_player-0.2.0 → unitracker_player-0.3.0}/src/unitracker_player.egg-info/requires.txt +0 -0
  18. {unitracker_player-0.2.0 → unitracker_player-0.3.0}/src/unitracker_player.egg-info/top_level.txt +0 -0
  19. {unitracker_player-0.2.0 → unitracker_player-0.3.0}/tests/test_cli.py +0 -0
  20. {unitracker_player-0.2.0 → unitracker_player-0.3.0}/tests/test_document.py +0 -0
  21. {unitracker_player-0.2.0 → unitracker_player-0.3.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.2.0
3
+ Version: 0.3.0
4
4
  Summary: Plays canonical unitracker documents to SID register state per tick
5
5
  Author: Josh Bailey
6
6
  License: Apache License
@@ -247,6 +247,7 @@ frames = play(text, ticks=3000) # a looping song needs ticks
247
247
  song = load(text) # parse and compile once; play(song, ...) reuses it
248
248
  player = Player(song)
249
249
  regs = player.step() # the 25 registers after one tick; None once the song ends
250
+ player.regs # the register file the last tick wrote, one store per register
250
251
  ```
251
252
 
252
253
  `read(text)` returns the document as plain dataclasses. A text outside the grammar raises
@@ -22,6 +22,7 @@ frames = play(text, ticks=3000) # a looping song needs ticks
22
22
  song = load(text) # parse and compile once; play(song, ...) reuses it
23
23
  player = Player(song)
24
24
  regs = player.step() # the 25 registers after one tick; None once the song ends
25
+ player.regs # the register file the last tick wrote, one store per register
25
26
  ```
26
27
 
27
28
  `read(text)` returns the document as plain dataclasses. A text outside the grammar raises
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "unitracker-player"
7
- version = "0.2.0"
7
+ version = "0.3.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"
@@ -364,11 +364,13 @@ class _Voice:
364
364
 
365
365
  class Player:
366
366
  """Plays a Song one tick at a time; ``step()`` returns the registers at the end of the
367
- tick, or None once the song has ended."""
367
+ tick, or None once the song has ended. ``regs`` is the register file the last tick wrote,
368
+ each register by its own statement, so a line tracer sees one store per register."""
368
369
 
369
370
  def __init__(self, song: Song):
370
371
  self.song = song
371
372
  self.tick = 0
373
+ self.regs = [0] * REGISTERS
372
374
  self.voices = [_Voice() for _ in range(song.voices)]
373
375
  self.cut = self.res = self.route = self.mode = self.vol = 0
374
376
  self.cut_prog = self.cut_cmd = None
@@ -436,26 +438,22 @@ class Player:
436
438
  return self.registers()
437
439
 
438
440
  def registers(self) -> list:
439
- """The 25 register values."""
440
- regs = [0] * REGISTERS
441
+ """The 25 register values, written to ``regs``."""
442
+ regs = self.regs
441
443
  for v, voice in enumerate(self.voices):
442
- ctrl = ((voice.ctrl ^ voice.mask) & 0xFE) | (voice.ctrl & voice.gate & 1)
443
- regs[7 * v : 7 * v + 7] = [
444
- voice.freq & 0xFF,
445
- voice.freq >> 8,
446
- voice.pw & 0xFF,
447
- voice.pw >> 8,
448
- ctrl,
449
- voice.ad,
450
- voice.sr,
451
- ]
452
- regs[21:25] = [
453
- self.cut & 7,
454
- self.cut >> 3,
455
- self.res << 4 | self.route,
456
- self.mode | self.vol,
457
- ]
458
- return regs
444
+ at = 7 * v
445
+ regs[at] = voice.freq & 0xFF
446
+ regs[at + 1] = voice.freq >> 8
447
+ regs[at + 2] = voice.pw & 0xFF
448
+ regs[at + 3] = voice.pw >> 8
449
+ regs[at + 4] = ((voice.ctrl ^ voice.mask) & 0xFE) | (voice.ctrl & voice.gate & 1)
450
+ regs[at + 5] = voice.ad
451
+ regs[at + 6] = voice.sr
452
+ regs[21] = self.cut & 7
453
+ regs[22] = self.cut >> 3
454
+ regs[23] = self.res << 4 | self.route
455
+ regs[24] = self.mode | self.vol
456
+ return list(regs)
459
457
 
460
458
  def _start(self, v: int, kind: int, prog: CProgram | None, t: int):
461
459
  running = prog is not None and bool(prog.rows)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unitracker-player
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Plays canonical unitracker documents to SID register state per tick
5
5
  Author: Josh Bailey
6
6
  License: Apache License
@@ -247,6 +247,7 @@ frames = play(text, ticks=3000) # a looping song needs ticks
247
247
  song = load(text) # parse and compile once; play(song, ...) reuses it
248
248
  player = Player(song)
249
249
  regs = player.step() # the 25 registers after one tick; None once the song ends
250
+ player.regs # the register file the last tick wrote, one store per register
250
251
  ```
251
252
 
252
253
  `read(text)` returns the document as plain dataclasses. A text outside the grammar raises
@@ -479,6 +479,17 @@ def test_the_player_steps_until_the_song_ends():
479
479
  assert regs[24] is None and player.step() is None
480
480
 
481
481
 
482
+ def test_regs_is_the_register_file_each_tick_rewrites():
483
+ """`Player.regs` holds the last tick's registers; each step returns a copy of it."""
484
+ player = Player(load(doc()))
485
+ first = player.step()
486
+ assert first == player.regs and first is not player.regs
487
+ player.voices[0].ad = 0x5A
488
+ after = player.registers()
489
+ assert [i for i, (x, y) in enumerate(zip(first, after)) if x != y] == [5]
490
+ assert after[5] == player.regs[5] == 0x5A and first[5] == 9
491
+
492
+
482
493
  def _twice(text: str) -> str:
483
494
  return text.replace(" ins:\n", " ins:\n i1: {ad: 1}\n", 1)
484
495