Skip to content

Commit

Permalink
Add support to trace.py for writing a PNG file
Browse files Browse the repository at this point in the history
  • Loading branch information
skoolkid committed Aug 27, 2024
1 parent 445ce6b commit ac6e6a6
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 6 deletions.
11 changes: 9 additions & 2 deletions skoolkit/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
CCMIOSimulator, get_int_param, integer, read_bin_file)
from skoolkit.audio import CLOCK_SPEED, AudioWriter
from skoolkit.cmiosimulator import CMIOSimulator
from skoolkit.components import get_image_writer
from skoolkit.config import get_config, show_config, update_options
from skoolkit.graphics import Frame, scr_udgs
from skoolkit.pagingtracer import Memory, PagingTracer
from skoolkit.simulator import Simulator
from skoolkit.simutils import PC, T, from_snapshot, get_state
Expand Down Expand Up @@ -275,14 +277,19 @@ def run(snafile, options, config):
lines = textwrap.wrap(simplify(delays, options.depth), 78)
print('Delays:\n {}'.format('\n '.join(lines)))
if options.dump:
if options.dump.lower().endswith('.wav'):
ext = options.dump.lower()[-4:]
if ext == '.wav':
delays = tracer.get_delays()
if delays:
audio_writer = AudioWriter({CLOCK_SPEED: cpu_freq})
with open(options.dump, 'wb') as f:
audio_writer.write_audio(f, delays, ma_filter=True)
else:
raise SkoolKitError('No audio detected')
elif ext == '.png':
frame = Frame(scr_udgs(simulator.memory, 0, 0, 32, 24), 2)
with open(options.dump, 'wb') as f:
get_image_writer().write_image([frame], f)
else:
ram, registers, state, machine = get_state(simulator)
write_snapshot(options.dump, ram, registers, state, machine)
Expand All @@ -294,7 +301,7 @@ def main(args):
usage='trace.py [options] FILE [OUTFILE]',
description="Trace Z80 machine code execution. "
"FILE may be a binary (raw memory) file, a SNA, SZX or Z80 snapshot, or '48', '128' or '+2' for no snapshot. "
"If 'OUTFILE' is given, an SZX/Z80 snapshot or WAV file is written after execution has completed.",
"If 'OUTFILE' is given, an SZX/Z80 snapshot, WAV file or PNG file is written after execution has completed.",
add_help=False
)
parser.add_argument('snafile', help=argparse.SUPPRESS, nargs='?')
Expand Down
2 changes: 2 additions & 0 deletions sphinx/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Changelog
configuration parameter values)
* Added support to :ref:`skool2bin.py` for padding the output with zeroes (as
specified by the ``PadLeft`` and ``PadRight`` configuration parameters)
* Added support to :ref:`trace.py` for writing a PNG file after code execution
has completed
* Added support to the :ref:`FOREACH` macro for the ``POKEname`` special
variable
* Fixed how the 'ADC A,*', 'SBC A,*', 'ADC HL,rr' and 'SBC HL,rr' instructions
Expand Down
7 changes: 5 additions & 2 deletions sphinx/source/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1994,8 +1994,8 @@ To list the options supported by `trace.py`, run it with no arguments::

Trace Z80 machine code execution. FILE may be a binary (raw memory) file, a
SNA, SZX or Z80 snapshot, or '48', '128' or '+2' for no snapshot. If 'OUTFILE'
is given, an SZX/Z80 snapshot or WAV file is written after execution has
completed.
is given, an SZX/Z80 snapshot, WAV file or PNG file is written after execution
has completed.

Options:
--audio Show audio delays.
Expand Down Expand Up @@ -2108,6 +2108,9 @@ Configuration parameters may also be set on the command line by using the
+---------+-------------------------------------------------------------------+
| Version | Changes |
+=========+===================================================================+
| 9.4 | Added support for writing a PNG file after execution has |
| | completed |
+---------+-------------------------------------------------------------------+
| 9.3 | Added the ``--state`` option; added support for writing a WAV |
| | file after execution has completed; added support for the ``m`` |
| | (memory) replacement field in the ``TraceLine*`` configuration |
Expand Down
4 changes: 2 additions & 2 deletions sphinx/source/man/trace.py.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ DESCRIPTION
``trace.py`` simulates the execution of machine code in a 48K, 128K or +2 SNA,
SZX or Z80 snapshot, or a binary (raw memory) file. If FILE is '48', '128' or
'+2', no snapshot is loaded, and the RAM is left blank (all zeroes). If
'OUTFILE' is given, an SZX/Z80 snapshot or WAV file is written after execution
has completed.
'OUTFILE' is given, an SZX/Z80 snapshot, WAV file or PNG file is written after
execution has completed.

OPTIONS
=======
Expand Down
53 changes: 53 additions & 0 deletions tests/test_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ def write_audio(self, audio_file, delays, ma_filter=None):
self.delays = delays
self.ma_filter = ma_filter

class MockImageWriter:
def __init__(self, config=None, palette=None):
global image_writer
image_writer = self
self.config = config
self.palette = palette

def write_image(self, frames, img_file):
self.frames = frames
self.fname = img_file.name

def mock_run(*args):
global run_args
run_args = args
Expand Down Expand Up @@ -2525,6 +2536,48 @@ def test_128k_bank_2(self):
self.assertEqual(s_memory[0xC000], 2)
self.assertEqual(s_banks[0][0], 0)

@patch.object(trace, 'get_image_writer', MockImageWriter)
def test_write_png(self):
data = (
0x06, 0x08, # $8000 LD B,$08
0x21, 0x00, 0x40, # $8002 LD HL,$4000
0x74, # $8005 LD (HL),H
0x24, # $8006 INC H
0x10, 0xFC, # $8007 DJNZ $8005
0x26, 0x58, # $8009 LD H,$58
0x36, 0x04, # $800B LD (HL),$04
)
infile = self.write_bin_file(data, suffix='.bin')
outfile = 'out.png'
start = 32768
stop = start + len(data)
output, error = self.run_trace(f'-o {start} -S {stop} {infile} {outfile}')
exp_output = f"""
Stopped at ${stop:04X}
Wrote {outfile}
"""
self.assertEqual(dedent(exp_output).strip(), output.rstrip())
self.assertIsNone(image_writer.config)
self.assertIsNone(image_writer.palette)
self.assertEqual(len(image_writer.frames), 1)

frame = image_writer.frames[0]
self.assertEqual(frame.scale, 2)
self.assertEqual(frame.mask, 0)
self.assertEqual(frame.x, 0)
self.assertEqual(frame.y, 0)
self.assertEqual(frame.width, 512)
self.assertEqual(frame.height, 384)
self.assertEqual(frame.tindex, 0)
self.assertEqual(frame.alpha, -1)

udgs = frame.udgs
self.assertEqual(len(udgs), 24)
self.assertEqual(len(udgs[0]), 32)
self.assertEqual([64, 65, 66, 67, 68, 69, 70, 71], list(udgs[0][0].data))
self.assertEqual(udgs[0][0].attr, 4)
self.assertEqual(image_writer.fname, outfile)

@patch.object(trace, 'AudioWriter', MockAudioWriter)
def test_write_wav_48k(self):
data = (
Expand Down

0 comments on commit ac6e6a6

Please sign in to comment.