pysidwizard 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,74 @@
1
+ """Pure-Python reader, writer, and player for SID-Wizard SWM modules.
2
+
3
+ SID-Wizard is a Commodore 64 tracker by Hermit (Mihaly Horvath). This
4
+ library implements its SWM module format (documented in
5
+ ``native/sources/SWM-spec.src`` of the SID-Wizard source distribution)
6
+ from first principles — no native dependencies, no extension modules.
7
+
8
+ Three public surfaces:
9
+
10
+ * :func:`read_swm` / :func:`parse_swm` — parse an SWM file or byte
11
+ string into a :class:`SWMFile`.
12
+ * :func:`write_swm` / :func:`build_swm` — serialise an :class:`SWMFile`
13
+ back to disk or bytes; round-trips byte-exactly.
14
+ * :class:`SWMPlayer` (in :mod:`pysidwizard.player`) — per-frame
15
+ emulation of SID-Wizard's 6502 player IRQ. Output matches a real
16
+ SID-Wizard running inside ``asid-vice`` byte-for-byte, every frame ×
17
+ every SID register — verified by the integration test suite on
18
+ every PR.
19
+ """
20
+
21
+ from .constants import Waveform, attack_decay, straight_tempo, sustain_release
22
+ from .errors import SWMError, SWMFormatError
23
+ from .model import (
24
+ End,
25
+ Instrument,
26
+ Loop,
27
+ Pattern,
28
+ PlayPattern,
29
+ RawSequenceByte,
30
+ Row,
31
+ SequenceCommand,
32
+ SWMFile,
33
+ TempoOverride,
34
+ Transpose,
35
+ decode_sequence,
36
+ encode_sequence,
37
+ pack_pattern,
38
+ unpack_pattern,
39
+ )
40
+ from .player import SWMPlayer, iter_writes, render_wav, write_csv
41
+ from .reader import parse_swm, read_swm
42
+ from .writer import build_swm, write_swm
43
+
44
+ __all__ = [
45
+ "End",
46
+ "Instrument",
47
+ "Loop",
48
+ "Pattern",
49
+ "PlayPattern",
50
+ "RawSequenceByte",
51
+ "Row",
52
+ "SWMError",
53
+ "SWMFile",
54
+ "SWMFormatError",
55
+ "SWMPlayer",
56
+ "SequenceCommand",
57
+ "TempoOverride",
58
+ "Transpose",
59
+ "Waveform",
60
+ "attack_decay",
61
+ "build_swm",
62
+ "decode_sequence",
63
+ "encode_sequence",
64
+ "iter_writes",
65
+ "pack_pattern",
66
+ "parse_swm",
67
+ "read_swm",
68
+ "render_wav",
69
+ "straight_tempo",
70
+ "sustain_release",
71
+ "unpack_pattern",
72
+ "write_csv",
73
+ "write_swm",
74
+ ]
@@ -0,0 +1,163 @@
1
+ """SWM file-format constants.
2
+
3
+ The numeric constants below mirror those in the upstream SID-Wizard source
4
+ file ``native/sources/SWM-spec.src``. They are reproduced here so the library
5
+ can be used without the C source distribution.
6
+ """
7
+
8
+ from enum import IntFlag
9
+
10
+ # Magic identifiers
11
+ SWM_MAGIC = b"SWM1"
12
+ SWS_MAGIC = b"SWMS"
13
+
14
+ # Header layout (offsets are relative to the SWM data start, i.e. *after* any
15
+ # 2-byte PRG load address that may prefix the file on disk).
16
+ TUNE_HEADER_SIZE = 0x40 # 64 bytes
17
+ MAGIC_POS = 0
18
+ FRAMESPEED_POS = 0x04
19
+ HIGHLIGHT_POS = 0x05
20
+ AUTO_POS = 0x06 # obsolete in SWM1
21
+ CONFIG_BITS_POS = 0x07 # obsolete in SWM1
22
+ MUTE_POS = 0x08 # 3 bytes: 0x08..0x0A
23
+ DEFAULT_PATTERN_LEN_POS = 0x0B
24
+ SEQUENCE_AMOUNT_POS = 0x0C
25
+ PATTERN_AMOUNT_POS = 0x0D
26
+ INSTRUMENT_AMOUNT_POS = 0x0E
27
+ CHORD_LENGTH_POS = 0x0F
28
+ TEMPO_LENGTH_POS = 0x10
29
+ COLOR_THEME_POS = 0x11 # obsolete in SWM1
30
+ KEYBOARD_TYPE_POS = 0x12 # obsolete in SWM1
31
+ DRIVER_TYPE_POS = 0x13
32
+ TUNING_TYPE_POS = 0x14
33
+ RESERVED_POS = 0x15 # bytes 0x15..0x17
34
+ AUTHOR_POS = 0x18
35
+ AUTHOR_LEN = TUNE_HEADER_SIZE - AUTHOR_POS # 40 bytes
36
+
37
+ # Hardware limits (used for validation, not enforced strictly in parsing).
38
+ SID_CHANNELS = 3
39
+ INSTRUMENT_NAME_LEN = 8
40
+ MAX_PATTERN_LEN = 249 # 0xF9
41
+ MAX_INSTRUMENT_SIZE = 128 # 0x80
42
+ MAX_SEQUENCE_LEN = 126 # 0x7E
43
+ MAX_PATTERN_AMOUNT = 100
44
+ MAX_INSTRUMENT_AMOUNT = 37
45
+
46
+ # Default PRG load address used by SID-Wizard's SWM export.
47
+ DEFAULT_LOAD_ADDRESS = 0x1FF8
48
+
49
+ # Pattern NOP-packing range. Each packed byte ``PACKED_MIN+k`` expands to
50
+ # ``k+2`` consecutive zero bytes when preceded by a zero or another packed
51
+ # byte in the source stream.
52
+ PACKED_MIN = 0x70
53
+ PACKED_MAX = 0x77
54
+
55
+ # Pattern note-column effect-value boundaries (informational).
56
+ NOTE_MAX = 0x5F
57
+ VIBRATO_FX = 0x60
58
+ PORTAMENTO_FX = 0x78
59
+ SYNC_ON_FX = 0x79
60
+ SYNC_OFF_FX = 0x7A
61
+ RING_ON_FX = 0x7B
62
+ RING_OFF_FX = 0x7C
63
+ GATE_ON_FX = 0x7D
64
+ GATE_OFF_FX = 0x7E
65
+
66
+ # Universal terminator used in instrument WF / PW / filter tables. On disk,
67
+ # the *final* filter-table terminator is replaced with the instrument's
68
+ # size byte; the writer handles that translation automatically.
69
+ TABLE_END = 0xFF
70
+
71
+ # Pattern row flag. The note and instrument columns of a row both use
72
+ # bit 7 to signal "the next column byte follows me". A row that consists
73
+ # of just a note (no instrument or fx) leaves the bit clear.
74
+ PATTERN_NEXT_COLUMN_FLAG = 0x80
75
+
76
+ # SWM note-column reference value. SID-Wizard's converter treats SWM
77
+ # note 49 (0x31) as the equivalent of MIDI middle C.
78
+ SWM_C5_NOTE = 49
79
+
80
+ # Sequence (orderlist) commands. Anything ``< 0x80`` in a sequence is a
81
+ # pattern reference; the values below are top-bit-set commands recognised
82
+ # by the player and by SID-Wizard's exporters.
83
+ SEQUENCE_END = 0xFE # exit subtune without looping
84
+ SEQUENCE_END_WITH_LOOP = 0xFF # exit, followed by a 1-byte loop position
85
+ SEQUENCE_TRANSPOSE_BASE = 0x90 # 0x90..0x9F sets a -16..+15 semitone shift
86
+ SEQUENCE_TEMPO_BASE = 0xB0 # 0xB0..0xFD overrides the subtune tempo
87
+
88
+ # Subtune funktempo: bit 7 of a tempo byte means "use the low seven bits
89
+ # straight, do not average with the partner byte".
90
+ TEMPO_STRAIGHT_FLAG = 0x80
91
+
92
+ # Instrument layout. These offsets index the bytes *before* the 8-byte
93
+ # instrument name on disk. ``INST_WF_TABLE_POS`` doubles as the size of
94
+ # the fixed instrument header; the variable-length WF / PW / Filter
95
+ # tables start there.
96
+ INST_HEADER_SIZE = 0x10
97
+ INST_CONTROL_POS = 0x00
98
+ INST_HR_AD_POS = 0x01 # hard-restart attack/decay
99
+ INST_HR_SR_POS = 0x02 # hard-restart sustain/release
100
+ INST_AD_POS = 0x03 # note-start attack/decay
101
+ INST_SR_POS = 0x04 # note-start sustain/release
102
+ INST_VIBRATO_POS = 0x05 # vibrato freq (low nibble) + amp (high)
103
+ INST_VIBRATO_DELAY_POS = 0x06
104
+ INST_ARP_SPEED_POS = 0x07
105
+ INST_DEFAULT_CHORD_POS = 0x08
106
+ INST_OCTAVE_POS = 0x09 # signed 2's-complement semitone shift
107
+ INST_PW_TABLE_PTR_POS = 0x0A # PW-table offset, relative to instbase
108
+ INST_FILTER_TABLE_PTR_POS = 0x0B
109
+ INST_GATEOFF_WF_POS = 0x0C
110
+ INST_GATEOFF_PW_POS = 0x0D
111
+ INST_GATEOFF_FILT_POS = 0x0E
112
+ INST_FIRST_WAVEFORM_POS = 0x0F # 1st-frame waveform (see :class:`Waveform`)
113
+ INST_WF_TABLE_POS = INST_HEADER_SIZE
114
+
115
+
116
+ class Waveform(IntFlag):
117
+ """SID waveform bits as encoded by SID-Wizard.
118
+
119
+ Each flag corresponds to one of the SID's four waveform generators.
120
+ Combine with ``|`` to mix waveforms (e.g. ``Waveform.TRIANGLE |
121
+ Waveform.PULSE`` selects a tied triangle+pulse output). A zero value
122
+ selects silence.
123
+ """
124
+
125
+ TRIANGLE = 0x01
126
+ SAWTOOTH = 0x02
127
+ PULSE = 0x04
128
+ NOISE = 0x08
129
+
130
+
131
+ def attack_decay(attack: int, decay: int) -> int:
132
+ """Pack an Attack/Decay nibble pair into a single ADSR-register byte.
133
+
134
+ Both nibbles are in the SID's 0..15 range. Used to assemble the
135
+ :data:`INST_AD_POS` and :data:`INST_HR_AD_POS` instrument bytes.
136
+ """
137
+ if not (0 <= attack <= 0xF and 0 <= decay <= 0xF):
138
+ raise ValueError("attack and decay must each be 0..15")
139
+ return (attack << 4) | decay
140
+
141
+
142
+ def sustain_release(sustain: int, release: int) -> int:
143
+ """Pack a Sustain/Release nibble pair into a single ADSR-register byte.
144
+
145
+ Both nibbles are in the SID's 0..15 range. Used to assemble the
146
+ :data:`INST_SR_POS` and :data:`INST_HR_SR_POS` instrument bytes.
147
+ """
148
+ if not (0 <= sustain <= 0xF and 0 <= release <= 0xF):
149
+ raise ValueError("sustain and release must each be 0..15")
150
+ return (sustain << 4) | release
151
+
152
+
153
+ def straight_tempo(frames_per_row: int) -> int:
154
+ """Encode a "straight" (non-funky) subtune-tempo byte.
155
+
156
+ ``frames_per_row`` is the player's row delay in PAL frames (1..127);
157
+ the returned byte sets :data:`TEMPO_STRAIGHT_FLAG` so the runtime uses
158
+ it directly instead of averaging with its partner byte in a funktempo
159
+ pair.
160
+ """
161
+ if not (1 <= frames_per_row <= 0x7F):
162
+ raise ValueError("frames_per_row must be 1..127")
163
+ return TEMPO_STRAIGHT_FLAG | frames_per_row
pysidwizard/errors.py ADDED
@@ -0,0 +1,9 @@
1
+ """Exceptions raised by :mod:`pysidwizard`."""
2
+
3
+
4
+ class SWMError(Exception):
5
+ """Base class for SWM-related errors."""
6
+
7
+
8
+ class SWMFormatError(SWMError, ValueError):
9
+ """Raised when SWM data is malformed or violates documented limits."""