unitracker-player 0.2.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.
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/PKG-INFO +2 -1
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/README.md +1 -0
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/pyproject.toml +1 -1
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/src/unitracker_player/player.py +23 -20
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/src/unitracker_player.egg-info/PKG-INFO +2 -1
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/tests/test_player.py +37 -0
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/LICENSE +0 -0
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/setup.cfg +0 -0
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/src/unitracker_player/__init__.py +0 -0
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/src/unitracker_player/cli.py +0 -0
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/src/unitracker_player/document.py +0 -0
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/src/unitracker_player/expr.py +0 -0
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/src/unitracker_player/modulation.py +0 -0
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/src/unitracker_player.egg-info/SOURCES.txt +0 -0
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/src/unitracker_player.egg-info/dependency_links.txt +0 -0
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/src/unitracker_player.egg-info/entry_points.txt +0 -0
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/src/unitracker_player.egg-info/requires.txt +0 -0
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/src/unitracker_player.egg-info/top_level.txt +0 -0
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/tests/test_cli.py +0 -0
- {unitracker_player-0.2.0 → unitracker_player-0.4.0}/tests/test_document.py +0 -0
- {unitracker_player-0.2.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
|
+
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
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -364,11 +365,13 @@ class _Voice:
|
|
|
364
365
|
|
|
365
366
|
class Player:
|
|
366
367
|
"""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.
|
|
368
|
+
tick, or None once the song has ended. ``regs`` is the register file the last tick wrote,
|
|
369
|
+
each register by its own statement, so a line tracer sees one store per register."""
|
|
368
370
|
|
|
369
371
|
def __init__(self, song: Song):
|
|
370
372
|
self.song = song
|
|
371
373
|
self.tick = 0
|
|
374
|
+
self.regs = [0] * REGISTERS
|
|
372
375
|
self.voices = [_Voice() for _ in range(song.voices)]
|
|
373
376
|
self.cut = self.res = self.route = self.mode = self.vol = 0
|
|
374
377
|
self.cut_prog = self.cut_cmd = None
|
|
@@ -396,6 +399,10 @@ class Player:
|
|
|
396
399
|
return [(t, (v, event, transpose, ins, None, None))]
|
|
397
400
|
*ahead, (last, _) = ins.restart
|
|
398
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])
|
|
399
406
|
for row, before in ahead:
|
|
400
407
|
if t - before >= 0:
|
|
401
408
|
self._lookahead[t - before].append((v, row))
|
|
@@ -436,26 +443,22 @@ class Player:
|
|
|
436
443
|
return self.registers()
|
|
437
444
|
|
|
438
445
|
def registers(self) -> list:
|
|
439
|
-
"""The 25 register values
|
|
440
|
-
regs =
|
|
446
|
+
"""The 25 register values, written to ``regs``."""
|
|
447
|
+
regs = self.regs
|
|
441
448
|
for v, voice in enumerate(self.voices):
|
|
442
|
-
|
|
443
|
-
regs[
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
regs[
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
self.res << 4 | self.route,
|
|
456
|
-
self.mode | self.vol,
|
|
457
|
-
]
|
|
458
|
-
return regs
|
|
449
|
+
at = 7 * v
|
|
450
|
+
regs[at] = voice.freq & 0xFF
|
|
451
|
+
regs[at + 1] = voice.freq >> 8
|
|
452
|
+
regs[at + 2] = voice.pw & 0xFF
|
|
453
|
+
regs[at + 3] = voice.pw >> 8
|
|
454
|
+
regs[at + 4] = ((voice.ctrl ^ voice.mask) & 0xFE) | (voice.ctrl & voice.gate & 1)
|
|
455
|
+
regs[at + 5] = voice.ad
|
|
456
|
+
regs[at + 6] = voice.sr
|
|
457
|
+
regs[21] = self.cut & 7
|
|
458
|
+
regs[22] = self.cut >> 3
|
|
459
|
+
regs[23] = self.res << 4 | self.route
|
|
460
|
+
regs[24] = self.mode | self.vol
|
|
461
|
+
return list(regs)
|
|
459
462
|
|
|
460
463
|
def _start(self, v: int, kind: int, prog: CProgram | None, t: int):
|
|
461
464
|
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.
|
|
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
|
|
@@ -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
|
|
@@ -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))
|
|
@@ -479,6 +505,17 @@ def test_the_player_steps_until_the_song_ends():
|
|
|
479
505
|
assert regs[24] is None and player.step() is None
|
|
480
506
|
|
|
481
507
|
|
|
508
|
+
def test_regs_is_the_register_file_each_tick_rewrites():
|
|
509
|
+
"""`Player.regs` holds the last tick's registers; each step returns a copy of it."""
|
|
510
|
+
player = Player(load(doc()))
|
|
511
|
+
first = player.step()
|
|
512
|
+
assert first == player.regs and first is not player.regs
|
|
513
|
+
player.voices[0].ad = 0x5A
|
|
514
|
+
after = player.registers()
|
|
515
|
+
assert [i for i, (x, y) in enumerate(zip(first, after)) if x != y] == [5]
|
|
516
|
+
assert after[5] == player.regs[5] == 0x5A and first[5] == 9
|
|
517
|
+
|
|
518
|
+
|
|
482
519
|
def _twice(text: str) -> str:
|
|
483
520
|
return text.replace(" ins:\n", " ins:\n i1: {ad: 1}\n", 1)
|
|
484
521
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unitracker_player-0.2.0 → unitracker_player-0.4.0}/src/unitracker_player.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{unitracker_player-0.2.0 → unitracker_player-0.4.0}/src/unitracker_player.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{unitracker_player-0.2.0 → unitracker_player-0.4.0}/src/unitracker_player.egg-info/requires.txt
RENAMED
|
File without changes
|
{unitracker_player-0.2.0 → unitracker_player-0.4.0}/src/unitracker_player.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|