skoolkit 9.2__cp39-cp39-win_amd64.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.
- skoolkit/__init__.py +236 -0
- skoolkit/audio.py +182 -0
- skoolkit/basic.py +346 -0
- skoolkit/bin2sna.py +127 -0
- skoolkit/bin2tap.py +277 -0
- skoolkit/ccmiosimulator.cp39-win_amd64.pyd +0 -0
- skoolkit/cmiosimulator.py +1004 -0
- skoolkit/components.py +60 -0
- skoolkit/config.py +191 -0
- skoolkit/csimulator.cp39-win_amd64.pyd +0 -0
- skoolkit/ctlparser.py +421 -0
- skoolkit/defaults.py +718 -0
- skoolkit/disassembler.py +1092 -0
- skoolkit/graphics.py +428 -0
- skoolkit/image.py +332 -0
- skoolkit/kbtracer.py +364 -0
- skoolkit/loadsample.py +990 -0
- skoolkit/loadtracer.py +648 -0
- skoolkit/opcodes.py +940 -0
- skoolkit/pagingtracer.py +66 -0
- skoolkit/pngwriter.py +504 -0
- skoolkit/refparser.py +190 -0
- skoolkit/resources/128-0.rom +0 -0
- skoolkit/resources/128-1.rom +0 -0
- skoolkit/resources/48.rom +0 -0
- skoolkit/resources/plus2-0.rom +0 -0
- skoolkit/resources/plus2-1.rom +0 -0
- skoolkit/resources/skoolkit-dark.css +67 -0
- skoolkit/resources/skoolkit-green.css +67 -0
- skoolkit/resources/skoolkit-plum.css +67 -0
- skoolkit/resources/skoolkit-wide.css +6 -0
- skoolkit/resources/skoolkit.css +212 -0
- skoolkit/rzxinfo.py +182 -0
- skoolkit/rzxplay.py +527 -0
- skoolkit/simtables.py +281 -0
- skoolkit/simulator.py +2842 -0
- skoolkit/simutils.py +193 -0
- skoolkit/skool2asm.py +143 -0
- skoolkit/skool2bin.py +324 -0
- skoolkit/skool2ctl.py +79 -0
- skoolkit/skool2html.py +409 -0
- skoolkit/skoolasm.py +623 -0
- skoolkit/skoolctl.py +721 -0
- skoolkit/skoolhtml.py +1383 -0
- skoolkit/skoolmacro.py +1541 -0
- skoolkit/skoolparser.py +964 -0
- skoolkit/skoolutils.py +707 -0
- skoolkit/sna2ctl.py +87 -0
- skoolkit/sna2img.py +174 -0
- skoolkit/sna2skool.py +108 -0
- skoolkit/snactl.py +436 -0
- skoolkit/snapinfo.py +498 -0
- skoolkit/snapmod.py +67 -0
- skoolkit/snapshot.py +864 -0
- skoolkit/snaskool.py +617 -0
- skoolkit/tap2sna.py +1142 -0
- skoolkit/tapinfo.py +552 -0
- skoolkit/textutils.py +64 -0
- skoolkit/trace.py +318 -0
- skoolkit/traceutils.py +1933 -0
- skoolkit/z80.py +1278 -0
- skoolkit-9.2.data/scripts/bin2sna.py +26 -0
- skoolkit-9.2.data/scripts/bin2tap.py +26 -0
- skoolkit-9.2.data/scripts/rzxinfo.py +26 -0
- skoolkit-9.2.data/scripts/rzxplay.py +26 -0
- skoolkit-9.2.data/scripts/skool2asm.py +26 -0
- skoolkit-9.2.data/scripts/skool2bin.py +26 -0
- skoolkit-9.2.data/scripts/skool2ctl.py +26 -0
- skoolkit-9.2.data/scripts/skool2html.py +26 -0
- skoolkit-9.2.data/scripts/sna2ctl.py +26 -0
- skoolkit-9.2.data/scripts/sna2img.py +26 -0
- skoolkit-9.2.data/scripts/sna2skool.py +26 -0
- skoolkit-9.2.data/scripts/snapinfo.py +26 -0
- skoolkit-9.2.data/scripts/snapmod.py +26 -0
- skoolkit-9.2.data/scripts/tap2sna.py +26 -0
- skoolkit-9.2.data/scripts/tapinfo.py +26 -0
- skoolkit-9.2.data/scripts/trace.py +26 -0
- skoolkit-9.2.dist-info/COPYING +674 -0
- skoolkit-9.2.dist-info/METADATA +144 -0
- skoolkit-9.2.dist-info/RECORD +82 -0
- skoolkit-9.2.dist-info/WHEEL +5 -0
- skoolkit-9.2.dist-info/top_level.txt +1 -0
skoolkit/__init__.py
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Copyright 2011-2024 Richard Dymond (rjdymond@gmail.com)
|
|
2
|
+
#
|
|
3
|
+
# This file is part of SkoolKit.
|
|
4
|
+
#
|
|
5
|
+
# SkoolKit is free software: you can redistribute it and/or modify it under the
|
|
6
|
+
# terms of the GNU General Public License as published by the Free Software
|
|
7
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
|
8
|
+
# version.
|
|
9
|
+
#
|
|
10
|
+
# SkoolKit is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
11
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
12
|
+
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License along with
|
|
15
|
+
# SkoolKit. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import html
|
|
19
|
+
import sys
|
|
20
|
+
import os
|
|
21
|
+
import posixpath
|
|
22
|
+
import textwrap
|
|
23
|
+
import importlib
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from skoolkit.csimulator import CSimulator
|
|
27
|
+
except ImportError: # pragma: no cover
|
|
28
|
+
CSimulator = None
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
from skoolkit.ccmiosimulator import CCMIOSimulator
|
|
32
|
+
except ImportError: # pragma: no cover
|
|
33
|
+
CCMIOSimulator = None
|
|
34
|
+
|
|
35
|
+
VERSION = '9.2'
|
|
36
|
+
PACKAGE_DIR = os.path.dirname(__file__)
|
|
37
|
+
|
|
38
|
+
BASE_10 = 10
|
|
39
|
+
BASE_16 = 16
|
|
40
|
+
|
|
41
|
+
CASE_LOWER = 1
|
|
42
|
+
CASE_UPPER = 2
|
|
43
|
+
|
|
44
|
+
WRAPPER = textwrap.TextWrapper(break_long_words=False, break_on_hyphens=False)
|
|
45
|
+
|
|
46
|
+
AE_CHARS = frozenset(' !=+-*/<>&|^%$ABCDEFabcdef0123456789()')
|
|
47
|
+
|
|
48
|
+
ROM48 = os.path.join(PACKAGE_DIR, 'resources', '48.rom')
|
|
49
|
+
|
|
50
|
+
ROM128 = (
|
|
51
|
+
os.path.join(PACKAGE_DIR, 'resources', '128-0.rom'),
|
|
52
|
+
os.path.join(PACKAGE_DIR, 'resources', '128-1.rom')
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
ROM_PLUS2 = (
|
|
56
|
+
os.path.join(PACKAGE_DIR, 'resources', 'plus2-0.rom'),
|
|
57
|
+
os.path.join(PACKAGE_DIR, 'resources', 'plus2-1.rom')
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def error(msg):
|
|
61
|
+
sys.stderr.write('ERROR: {0}\n'.format(msg))
|
|
62
|
+
sys.exit(1)
|
|
63
|
+
|
|
64
|
+
def warn(msg):
|
|
65
|
+
sys.stderr.write('WARNING: {0}\n'.format(msg))
|
|
66
|
+
|
|
67
|
+
def info(msg):
|
|
68
|
+
sys.stderr.write('{0}\n'.format(msg))
|
|
69
|
+
|
|
70
|
+
def show_package_dir():
|
|
71
|
+
write_line(PACKAGE_DIR)
|
|
72
|
+
sys.exit(0)
|
|
73
|
+
|
|
74
|
+
def write(text):
|
|
75
|
+
sys.stdout.write(text)
|
|
76
|
+
sys.stdout.flush()
|
|
77
|
+
|
|
78
|
+
def write_line(text):
|
|
79
|
+
sys.stdout.write('{0}\n'.format(text))
|
|
80
|
+
|
|
81
|
+
def write_text(text):
|
|
82
|
+
sys.stdout.write(text)
|
|
83
|
+
|
|
84
|
+
def wrap(text, width):
|
|
85
|
+
WRAPPER.width = width
|
|
86
|
+
return WRAPPER.wrap(text)
|
|
87
|
+
|
|
88
|
+
def get_int_param(num_str, accept0x=False):
|
|
89
|
+
try:
|
|
90
|
+
return int(num_str)
|
|
91
|
+
except ValueError:
|
|
92
|
+
pass
|
|
93
|
+
if num_str.startswith('$'):
|
|
94
|
+
return int(num_str[1:], 16)
|
|
95
|
+
if accept0x and num_str.startswith('0x'):
|
|
96
|
+
return int(num_str[2:], 16)
|
|
97
|
+
if num_str.startswith('%'):
|
|
98
|
+
return int(num_str[1:], 2)
|
|
99
|
+
if num_str.startswith('"') and num_str.endswith('"'):
|
|
100
|
+
try:
|
|
101
|
+
if num_str.startswith('"\\'):
|
|
102
|
+
return ord(num_str[2:-1])
|
|
103
|
+
return ord(num_str[1:-1])
|
|
104
|
+
except TypeError:
|
|
105
|
+
pass
|
|
106
|
+
raise ValueError
|
|
107
|
+
|
|
108
|
+
def parse_int(num_str, default=None):
|
|
109
|
+
try:
|
|
110
|
+
return get_int_param(num_str)
|
|
111
|
+
except ValueError:
|
|
112
|
+
return default
|
|
113
|
+
|
|
114
|
+
def evaluate(param, safe=False):
|
|
115
|
+
try:
|
|
116
|
+
return get_int_param(param)
|
|
117
|
+
except ValueError:
|
|
118
|
+
pass
|
|
119
|
+
param = html.unescape(param)
|
|
120
|
+
if safe or set(param) <= AE_CHARS:
|
|
121
|
+
try:
|
|
122
|
+
return int(eval(param.replace('$', '0x').replace('/', '//').replace('&&', ' and ').replace('||', ' or ')))
|
|
123
|
+
except:
|
|
124
|
+
pass
|
|
125
|
+
raise ValueError
|
|
126
|
+
|
|
127
|
+
def get_address_format(hexadecimal=False, lower=False):
|
|
128
|
+
if hexadecimal:
|
|
129
|
+
if lower:
|
|
130
|
+
return '${:04x}'
|
|
131
|
+
return '${:04X}'
|
|
132
|
+
return '{:05d}'
|
|
133
|
+
|
|
134
|
+
def get_object(name_spec, default_path=''):
|
|
135
|
+
path, sep, name = name_spec.rpartition(':')
|
|
136
|
+
if sep:
|
|
137
|
+
sys.path.insert(0, os.path.expanduser(path) or default_path)
|
|
138
|
+
try:
|
|
139
|
+
return importlib.import_module(name)
|
|
140
|
+
except ImportError:
|
|
141
|
+
pass
|
|
142
|
+
mod_name, sep, attr_name = name.rpartition('.')
|
|
143
|
+
try:
|
|
144
|
+
m = importlib.import_module(mod_name or attr_name)
|
|
145
|
+
except ImportError as e:
|
|
146
|
+
raise SkoolKitError("Failed to import object {}: {}".format(name, e.args[0]))
|
|
147
|
+
try:
|
|
148
|
+
return getattr(m, attr_name)
|
|
149
|
+
except AttributeError:
|
|
150
|
+
raise SkoolKitError("No object named '{}' in module '{}'".format(attr_name, mod_name))
|
|
151
|
+
|
|
152
|
+
def find_file(fname, search_dirs=('',)):
|
|
153
|
+
for f in [os.path.join(d, fname) for d in search_dirs]:
|
|
154
|
+
if os.path.isfile(f):
|
|
155
|
+
return f
|
|
156
|
+
|
|
157
|
+
def open_file(fname, mode='r'):
|
|
158
|
+
if fname == '-':
|
|
159
|
+
if 'w' in mode:
|
|
160
|
+
return sys.stdout.buffer
|
|
161
|
+
return sys.stdin
|
|
162
|
+
try:
|
|
163
|
+
if 'b' in mode:
|
|
164
|
+
return open(fname, mode)
|
|
165
|
+
return open(fname, mode, encoding='utf-8')
|
|
166
|
+
except IOError as e:
|
|
167
|
+
if e.errno == 2:
|
|
168
|
+
raise SkoolKitError('{0}: file not found'.format(fname))
|
|
169
|
+
raise
|
|
170
|
+
except TypeError:
|
|
171
|
+
# Assume this is already a file-like object
|
|
172
|
+
return fname
|
|
173
|
+
|
|
174
|
+
def read_bin_file(fname, size=-1):
|
|
175
|
+
if fname == '-':
|
|
176
|
+
return sys.stdin.buffer.read(size)
|
|
177
|
+
try:
|
|
178
|
+
with open(fname, 'rb') as f:
|
|
179
|
+
return f.read(size)
|
|
180
|
+
except IOError as e:
|
|
181
|
+
if e.errno == 2:
|
|
182
|
+
raise SkoolKitError('{0}: file not found'.format(fname))
|
|
183
|
+
raise
|
|
184
|
+
|
|
185
|
+
def format_template(template__, name__, **fields):
|
|
186
|
+
try:
|
|
187
|
+
return template__.format(**fields)
|
|
188
|
+
except ValueError as e:
|
|
189
|
+
raise SkoolKitError('Failed to format {} template: {}'.format(name__, e.args[0]))
|
|
190
|
+
except KeyError as e:
|
|
191
|
+
raise SkoolKitError("Unknown field '{}' in {} template".format(e.args[0], name__))
|
|
192
|
+
|
|
193
|
+
def normpath(*paths):
|
|
194
|
+
return posixpath.normpath(posixpath.join(*[p.replace('\\', '/') for p in paths]))
|
|
195
|
+
|
|
196
|
+
def get_word(data, index):
|
|
197
|
+
return data[index] + 256 * data[index + 1]
|
|
198
|
+
|
|
199
|
+
def get_word3(data, index):
|
|
200
|
+
return get_word(data, index) + 65536 * data[index + 2]
|
|
201
|
+
|
|
202
|
+
def get_dword(data, index):
|
|
203
|
+
return get_word3(data, index) + 16777216 * data[index + 3]
|
|
204
|
+
|
|
205
|
+
def integer(arg):
|
|
206
|
+
try:
|
|
207
|
+
s_arg = arg.strip()
|
|
208
|
+
if s_arg.startswith('0x'):
|
|
209
|
+
return int(s_arg[2:], 16)
|
|
210
|
+
return int(s_arg)
|
|
211
|
+
except ValueError:
|
|
212
|
+
raise argparse.ArgumentTypeError("invalid integer: '{}'".format(arg))
|
|
213
|
+
|
|
214
|
+
def eval_variable(name, value):
|
|
215
|
+
if name.endswith('$'):
|
|
216
|
+
return value
|
|
217
|
+
return evaluate(value)
|
|
218
|
+
|
|
219
|
+
def variable(arg):
|
|
220
|
+
name, sep, value = arg.partition('=')
|
|
221
|
+
if name and sep:
|
|
222
|
+
try:
|
|
223
|
+
return name, eval_variable(name, value)
|
|
224
|
+
except ValueError:
|
|
225
|
+
raise argparse.ArgumentTypeError("invalid arithmetic expression: '{}'".format(arg))
|
|
226
|
+
elif name:
|
|
227
|
+
raise argparse.ArgumentTypeError("missing variable value: '{}'".format(arg))
|
|
228
|
+
else:
|
|
229
|
+
raise argparse.ArgumentTypeError("missing variable name: '{}'".format(arg))
|
|
230
|
+
|
|
231
|
+
class SkoolKitError(Exception):
|
|
232
|
+
pass
|
|
233
|
+
|
|
234
|
+
# API (ControlDirectiveComposer)
|
|
235
|
+
class SkoolParsingError(SkoolKitError):
|
|
236
|
+
"""Raised when an error occurs while parsing a skool file."""
|
skoolkit/audio.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Copyright 2013, 2014, 2022, 2023 Richard Dymond (rjdymond@gmail.com)
|
|
2
|
+
#
|
|
3
|
+
# This file is part of SkoolKit.
|
|
4
|
+
#
|
|
5
|
+
# SkoolKit is free software: you can redistribute it and/or modify it under the
|
|
6
|
+
# terms of the GNU General Public License as published by the Free Software
|
|
7
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
|
8
|
+
# version.
|
|
9
|
+
#
|
|
10
|
+
# SkoolKit is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
11
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
12
|
+
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License along with
|
|
15
|
+
# SkoolKit. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
from math import ceil, floor
|
|
18
|
+
|
|
19
|
+
CLOCK_SPEED = 'ClockSpeed'
|
|
20
|
+
CONTENTION_BEGIN = 'ContentionBegin'
|
|
21
|
+
CONTENTION_END = 'ContentionEnd'
|
|
22
|
+
CONTENTION_FACTOR = 'ContentionFactor'
|
|
23
|
+
FRAME_DURATION = 'FrameDuration'
|
|
24
|
+
INTERRUPT_DELAY = 'InterruptDelay'
|
|
25
|
+
SAMPLE_RATE = 'SampleRate'
|
|
26
|
+
|
|
27
|
+
class AudioWriter:
|
|
28
|
+
def __init__(self, config=None):
|
|
29
|
+
self.options = ({
|
|
30
|
+
CLOCK_SPEED: 3500000,
|
|
31
|
+
CONTENTION_BEGIN: 14334,
|
|
32
|
+
CONTENTION_END: 57248,
|
|
33
|
+
CONTENTION_FACTOR: 51,
|
|
34
|
+
FRAME_DURATION: 69888,
|
|
35
|
+
INTERRUPT_DELAY: 942,
|
|
36
|
+
SAMPLE_RATE: 44100
|
|
37
|
+
}, {
|
|
38
|
+
CLOCK_SPEED: 3546900,
|
|
39
|
+
CONTENTION_BEGIN: 14361,
|
|
40
|
+
CONTENTION_END: 58041,
|
|
41
|
+
CONTENTION_FACTOR: 51,
|
|
42
|
+
FRAME_DURATION: 70908,
|
|
43
|
+
INTERRUPT_DELAY: 1584,
|
|
44
|
+
SAMPLE_RATE: 44100
|
|
45
|
+
})
|
|
46
|
+
if config:
|
|
47
|
+
for k, v in config.items():
|
|
48
|
+
try:
|
|
49
|
+
self.options[0][k] = self.options[1][k] = int(v)
|
|
50
|
+
except ValueError:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
def write_audio(self, audio_file, delays, contention=False, interrupts=False, offset=0, ma_filter=False, is128k=False):
|
|
54
|
+
options = self.options[is128k]
|
|
55
|
+
if contention or interrupts:
|
|
56
|
+
self._add_contention(delays, contention, interrupts, offset, options)
|
|
57
|
+
if ma_filter:
|
|
58
|
+
samples = self._moving_average_filter(delays, options)
|
|
59
|
+
else:
|
|
60
|
+
samples = self._delays_to_samples(delays, options)
|
|
61
|
+
self._write_wav(audio_file, samples, options)
|
|
62
|
+
|
|
63
|
+
def formats(self):
|
|
64
|
+
return ('.wav',)
|
|
65
|
+
|
|
66
|
+
def _add_contention(self, delays, contention, interrupts, cycle, options):
|
|
67
|
+
c_begin, c_end = options[CONTENTION_BEGIN], options[CONTENTION_END]
|
|
68
|
+
c_factor = 1 + options[CONTENTION_FACTOR] / 100
|
|
69
|
+
i_delay = options[INTERRUPT_DELAY]
|
|
70
|
+
f_duration = options[FRAME_DURATION]
|
|
71
|
+
|
|
72
|
+
for i in range(len(delays)):
|
|
73
|
+
d_offset = 0
|
|
74
|
+
while 1:
|
|
75
|
+
if interrupts and cycle == 0:
|
|
76
|
+
cycle = i_delay
|
|
77
|
+
if i:
|
|
78
|
+
delays[i] += i_delay
|
|
79
|
+
d_offset += i_delay
|
|
80
|
+
d_remaining = delays[i] - d_offset
|
|
81
|
+
if contention and cycle < c_end:
|
|
82
|
+
if cycle < c_begin:
|
|
83
|
+
gap = c_begin - cycle
|
|
84
|
+
if d_offset + gap >= delays[i]:
|
|
85
|
+
# Delay ends before next contended interval starts
|
|
86
|
+
cycle += d_remaining
|
|
87
|
+
break
|
|
88
|
+
# Delay crosses into contended interval
|
|
89
|
+
d_offset += gap
|
|
90
|
+
cycle = c_begin
|
|
91
|
+
else:
|
|
92
|
+
c_period = c_end - cycle
|
|
93
|
+
d_rem_contended = int(d_remaining * c_factor)
|
|
94
|
+
if d_rem_contended < c_period:
|
|
95
|
+
# Delay ends within contended interval
|
|
96
|
+
delays[i] = d_offset + d_rem_contended
|
|
97
|
+
cycle += d_rem_contended
|
|
98
|
+
break
|
|
99
|
+
# Delay crosses into uncontended interval
|
|
100
|
+
cd_cycles = int(c_period / c_factor)
|
|
101
|
+
delays[i] += c_period - cd_cycles
|
|
102
|
+
d_offset += cd_cycles
|
|
103
|
+
cycle = c_end
|
|
104
|
+
else:
|
|
105
|
+
if d_remaining < f_duration - cycle:
|
|
106
|
+
# Delay ends before frame boundary
|
|
107
|
+
cycle += d_remaining
|
|
108
|
+
break
|
|
109
|
+
# Delay crosses frame boundary
|
|
110
|
+
d_offset += f_duration - cycle
|
|
111
|
+
cycle = 0
|
|
112
|
+
|
|
113
|
+
def _moving_average_filter(self, delays, options):
|
|
114
|
+
sample_delay = options[CLOCK_SPEED] / options[SAMPLE_RATE]
|
|
115
|
+
s0, s1 = 0, sample_delay
|
|
116
|
+
t, t1 = 0, ceil(s1)
|
|
117
|
+
bit = bits = 0
|
|
118
|
+
samples = []
|
|
119
|
+
for d in delays:
|
|
120
|
+
while True:
|
|
121
|
+
if t + d < t1:
|
|
122
|
+
if bit:
|
|
123
|
+
bits += d
|
|
124
|
+
t += d
|
|
125
|
+
break
|
|
126
|
+
i = t1 - t
|
|
127
|
+
if bit:
|
|
128
|
+
bits += i
|
|
129
|
+
d -= i
|
|
130
|
+
samples.append((floor(0xFFFF * bits / (t1 - s0)) - 0x8000) & 0xFFFF)
|
|
131
|
+
s0 = t = t1
|
|
132
|
+
s1 += sample_delay
|
|
133
|
+
t1 = ceil(s1)
|
|
134
|
+
bits = 0
|
|
135
|
+
bit = 1 - bit
|
|
136
|
+
return samples
|
|
137
|
+
|
|
138
|
+
def _delays_to_samples(self, delays, options):
|
|
139
|
+
sample_delay = options[CLOCK_SPEED] / options[SAMPLE_RATE]
|
|
140
|
+
samples = []
|
|
141
|
+
direction = 1
|
|
142
|
+
i = 0
|
|
143
|
+
d = delays[0]
|
|
144
|
+
t = 0
|
|
145
|
+
while 1:
|
|
146
|
+
while t >= d:
|
|
147
|
+
i += 1
|
|
148
|
+
if i >= len(delays):
|
|
149
|
+
break
|
|
150
|
+
d += delays[i]
|
|
151
|
+
direction *= -1
|
|
152
|
+
if i >= len(delays):
|
|
153
|
+
break
|
|
154
|
+
if direction > 0:
|
|
155
|
+
samples.append(32767)
|
|
156
|
+
else:
|
|
157
|
+
samples.append(32768)
|
|
158
|
+
t += sample_delay
|
|
159
|
+
return samples
|
|
160
|
+
|
|
161
|
+
def _to_int32(self, num):
|
|
162
|
+
return (num & 255, (num >> 8) & 255, (num >> 16) & 255, num >> 24)
|
|
163
|
+
|
|
164
|
+
def _write_wav(self, audio_file, samples, options):
|
|
165
|
+
sample_rate = options[SAMPLE_RATE]
|
|
166
|
+
data_length = 2 * len(samples)
|
|
167
|
+
header = bytearray()
|
|
168
|
+
header.extend(b'RIFF')
|
|
169
|
+
header.extend(self._to_int32(36 + data_length))
|
|
170
|
+
header.extend(b'WAVEfmt ')
|
|
171
|
+
header.extend(self._to_int32(16)) # length of fmt chunk
|
|
172
|
+
header.extend((1, 0)) # format (1=PCM)
|
|
173
|
+
header.extend((1, 0)) # channels
|
|
174
|
+
header.extend(self._to_int32(sample_rate)) # sample rate
|
|
175
|
+
header.extend(self._to_int32(sample_rate * 2)) # byte rate
|
|
176
|
+
header.extend((2, 0)) # bytes per sample
|
|
177
|
+
header.extend((16, 0)) # bits per sample
|
|
178
|
+
header.extend(b'data')
|
|
179
|
+
header.extend(self._to_int32(data_length)) # length of data chunk
|
|
180
|
+
audio_file.write(header)
|
|
181
|
+
for sample in samples:
|
|
182
|
+
audio_file.write(bytes((sample & 255, sample // 256)))
|