{"version":"https://jsonfeed.org/version/1","items":[{"id":"3ebec715-96d5-423f-b6c6-2c6cf8a220ee","title":"Revisiting Async and the RtMidi Event Loop","content_html":"<h4>Introduction\n</h4><p>A coupla few years ago I wrote about <a href=\"https://fuzzix.org/perl-ioasync-and-the-rtmidi-event-loop\">Integrating RtMidi's event loop with\nIO::Async</a>. The basic approach\ninvolved spawning a routine which set up a RtMidi callback to pass MIDI\nmessages back to the main process via a channel. This routine then went to\nsleep to allow RtMidi's own event loop to take control.\n</p><p>While this approach works well enough, and has been used in a number of\nprojects (such as\n<a href=\"https://fuzzix.org/enhancing-midi-hardware-with-perl\">The MIDI device filter project on this site</a>, and\n<a href=\"https://metacpan.org/pod/MIDI::RtController\">Gene Boggs' MIDI::RtController ecosystem</a>),\nit also adds\nno small amount of complexity and overhead to an implementation. Running\nPerl-based callbacks within the rtmidi callback-handler thread can also cause\ncrashes, if not done carefully.\n</p><p>While playing around with <a href=\"https://celtera.github.io/libremidi/\">libremidi</a>, I\nnoticed it offers an option which <a href=\"https://celtera.github.io/libremidi/polling.html\">makes a nonblocking file descriptor\navailable for integration into your event\nloop</a>, and found myself\nthinking that it would be nice to have RtMidi offer a similar facility. What if...\n</p><p>What if we don't set RtMidi's callback to a\n<a href=\"https://metacpan.org/pod/FFI::Platypus::Closure\">FFI::Platypus::Closure</a>\nwrapping our Perl code, but instead set the callback within some bundled C\ncode? This callback would create a fd pair - two file descriptors attached to\neither end of a\n<a href=\"https://pubs.opengroup.org/onlinepubs/9699919799/functions/pipe.html\">pipe</a>, a\nwrite fd and a read fd. The read fd could be set nonblocking and, as it's a\nsimple <code>int</code>, be trivially passed back to our Perl program for later\nintegration into any event loop. This would eliminate the problems of calling\nPerl code in new threads, and should simplify event loop integration - no more\nroutines and channels required, we can now process MIDI bytes as a stream.\n</p><p>This seems sound enough to me, but we are now left with the problem of bundling\nC - Makefiles, compilers, actually writing C ... We are surely in for a world\nof pain, or are we?\n</p><h4>Bundling Native Code with FFI::Platypus\n</h4><p><a href=\"https://metacpan.org/pod/FFI::Platypus::Bundle\">FFI::Platypus' bundling interface</a>\nallows for seamless inclusion of C code in your distribution (or Go code, or\nRust code, or Zig code, or...), without needing to worry about the build system\n<em>too</em> much. The moving parts in our case are:\n</p><ul><li>A directive to load bundled code alongside other Platypus library bindings.\n</li><li>Some C code in a directory named <code>ffi/</code> in the root of the distribution.\n</li><li>A <code>.fbx</code> file to pass some config values to <code>FFI::Build</code> and set the bundled dynamic library name.\n</li><li>An addition to <code>dist.ini</code> to instruct <a href=\"https://metacpan.org/pod/FFI::Build\">FFI::Build</a> to build bundled code when installing from CPAN.\n</li></ul><p>This last part is pretty simple, the following line is added to dist.ini:\n</p><pre><code class=\"language-ini\">[FFI::Build]</code></pre><p>Let's take a look at the C.\n</p><h4>Creating the pipe\n</h4><p>Let's start with the includes. You'll always want <code>ffi_platypus_bundle.h</code> for\nbundled FFI code. There are some libc parts, then <code>rtmidi_c.h</code>, RtMidi's C\ninterface header. This will be useful for dereferencing RtMidi device members,\nand actually setting the rtmidi callback\nas we'll see later.\n</p><pre><code class=\"language-C\">#include &lt;ffi_platypus_bundle.h&gt;\n#include &lt;unistd.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;fcntl.h&gt;\n#include &lt;rtmidi_c.h&gt;</code></pre><p>Next comes some cheating ... while this is ostensibly C, we will need to build\nand link with a C++ compiler to access RtMidi's namespaces correctly. This is\nas simple as giving our file a <code>.cpp</code> extension (<code>ffi/pipefd.cpp</code>), and placing\nour C code in an <code>extern</code> block covering the remainder of the file.\n</p><pre><code class=\"language-C\">#ifdef __cplusplus\nextern &quot;C&quot; {\n#endif</code></pre><p>Next is a struct called <code>_cb_descriptor</code>, which at the moment just stores the writefd which will be used in RtMidi's callback.\n</p><pre><code class=\"language-C\">typedef struct {\n    int fd;\n} _cb_descriptor;</code></pre><p>The callback itself is a simple loop which attempts to write all available\nbytes in <code>*message</code> to the writefd passed in the userdata <code>*data</code>. Passing\n<code>*data</code> into the callback is a common pattern in C to work around the lack of\nclosure support.\n</p><p>The important thing to note here is that our callback is no longer Perl wrapped\nin a <a href=\"https://metacpan.org/pod/FFI::Platypus::Closure\">FFI::Platypus::Closure</a>\n- our Perl code no longer needs to cede control to RtMidi.\n</p><pre><code class=\"language-C\">void _callback( double deltatime, const char *message, size_t size, _cb_descriptor *data ) {\n    if( size &lt; 1 ) {\n        return;\n    }\n\n    int total = 0;\n    int remains = size;\n    while ( total &lt; size ) {\n        int sent = write( data-&gt;fd, message + total, remains );\n        if ( sent &lt; 0 ) {\n            perror(&quot;Callback write error.&quot;);\n            return;\n        }\n        remains -= sent;\n        total += sent;\n    }\n\n}</code></pre><p>The last part is to create the fd pair, stash the writefd <code>fd</code> into the\ndevice's userdata, set the readfd side of the pipe nonblocking, register the\ncallback and userdata with RtMidi, then pass the readfd back to our Perl\nprogram.\n</p><p><code>RTMIDIAPI</code> is a handy macro from RtMidi which will handle symbol exporting in a cross-platform way.\n</p><pre><code class=\"language-C\">RTMIDIAPI\nint callback_fd( RtMidiInPtr device ) {\n\n    _cb_descriptor *data = (_cb_descriptor*)malloc( sizeof( _cb_descriptor ) );\n    int pipefd[2] = { 0, 0 };\n\n    if ( pipe(pipefd) &lt; 0 ) {\n        perror(&quot;Cannot create pipe!&quot;);\n        return -1;\n    }\n    fcntl( pipefd[0], F_SETFL, O_NONBLOCK );\n    data-&gt;fd = pipefd[1];\n\n    rtmidi_in_set_callback( device, (RtMidiCCallback)&amp;_callback, data );\n\n    return pipefd[0];\n}</code></pre><h4>Building and Binding\n</h4><p>The <code>.fbx</code> file required to build this is called <code>ffi/rtmidi-ffi.fbx</code> (so the\ndynamic library it builds will be called <code>librtmidi-ffi.so</code>/<code>rtmidi-ffi.dll</code>\netc. depending on platform). It does some linker flag setup for Windows and\nMacOS, sets an 'Alien' package the compiler should use to find headers and\nlibraries from a non-system install, then sets the source to all <code>.cpp</code> files in <code>ffi/</code> - there is\ncurrently just one, <code>ffi/pipefd.ccp</code>.\n</p><p>The MacOS linker flags are mostly required by older versions of that OS.\nThe Windows linker flags set an explicit link order, Platypus otherwise does a\nsolid job of identifying required libraries.\n</p><pre><code class=\"language-perl\">our $DIR;\n\nmy $WINDOWS = $^O eq 'MSWin32' || $^O eq 'cygwin';\nmy $MACOS = $^O eq 'darwin';\n\nmy $libs = '';\n\n$libs = '-lrtmidi -lwinmm -lws2_32' if $WINDOWS;\n$libs = '-framework CoreFoundation -framework CoreMidi' if $MACOS;\n\n{\n    alien  =&gt; ['Alien::RtMidi'],\n    source =&gt; [&quot;$DIR/*.cpp&quot;],\n    libs   =&gt; $libs,\n};</code></pre><p>Something to note about the <code>.fbx</code> file is, it's probably not needed in many\ncases. If your bundled code is portable enough, and doesn't have external dependencies,\nyou can likely get away without it.\n</p><p>There are a couple of moving parts needed back in Perl land to connect all this\nup. First we tell Platypus to build and load the bundled code - this will\nautomagically build the bundle as it changes, even in a development environment\n- no need to invoke <code>make</code> or similar:\n</p><pre><code class=\"language-perl\">my $ffi = FFI::Platypus-&gt;new( api =&gt; 2, ... );\n$ffi-&gt;bundle;</code></pre><p>Then we need to attach the C function exported from our bundle:\n</p><pre><code class=\"language-perl\">$ffi-&gt;attach( callback_fd =&gt; [ [ 'RtMidiInPtr*' ] =&gt; 'int' ] )</code></pre><p>Finally, an exported Perl function is added to the binding library to wrap the fd\ninteger returned from C in a readonly\n<a href=\"https://perldoc.perl.org/IO::Handle\">IO::Handle</a>. This handle can then be passed\neasily to event loops:\n</p><pre><code class=\"language-perl\">sub callback_fh( $dev ) {\n    IO::Handle-&gt;new-&gt;fdopen( callback_fd( $dev ), 'r' );\n}</code></pre><h4>Windows\n</h4><p>Keen-eyed Windows API experts may have spotted an issue with the above. Windows\ndoes not have <code>pipe(2)</code>. This winds up creating something of a mess, but the\nshort version is for Windows, a\n<a href=\"https://perldoc.perl.org/IO::Socket#socketpair\">socketpair</a> is created in\nPerl, the write-end is passed to C, and the read end is returned to the calling\nprogram.\n</p><h4>Examples\n</h4><p>We should now have all the pieces to pass a filehandle to any event loop. As\n<a href=\"https://fuzzix.org/perl-ioasync-and-the-rtmidi-event-loop\">previous efforts</a>\nused <a href=\"https://metacpan.org/pod/IO::Async\">IO::Async</a>, let's start there.\n</p><h5>IO::Async\n</h5><p>After MIDI device, decoder, and event loop setup, an\n<a href=\"https://metacpan.org/pod/IO::Async::Stream\">IO::Async::Stream</a> is created with\nthe wrapped fd opened in our C code above. We can then attach a callback to the\ndecoder instance to print incoming events, then pass incoming bytes to the\ndecoder in the stream's <code>on_read</code> handler - as complete MIDI events are received, the\ndecoder's callback will be invoked.\n</p><p>A ticking counter is added to demonstrate other activity - we are not blocked\nwaiting for RtMidi, there is no need to <code>sleep</code>.\n</p><p>Where previously we would have\nhad to create a routine, and a channel for inter-process communication, here\nthe receiving and decoding of incoming MIDI messages is taken care of by the\n<a href=\"https://metacpan.org/pod/IO::Async::Stream\">stream</a>, entirely within\na single thread.\n</p><p>A small wrapper method called <code>get_fh</code> has been added to the input device class\nto manage whether existing callbacks should be cancelled when retrieving the\nhandle.\n</p><pre><code class=\"language-perl\">use IO::Async::Loop;\nuse IO::Async::Stream;\nuse IO::Async::Timer::Periodic;\nuse MIDI::RtMidi::FFI::Device;\nuse MIDI::Stream::Decoder;\n\nmy $midi_in = RtMidiIn-&gt;new();\n$midi_in-&gt;open_port_by_name( qr/sz|lkmk3/i );\nmy $fh = $midi_in-&gt;get_fh;\n\nmy $decoder = MIDI::Stream::Decoder-&gt;new;\n$decoder-&gt;attach_callback( all =&gt; sub( $event ) {\n    say join ' ', $event-&gt;dt, $event-&gt;as_arrayref-&gt;@*;\n} );\n\nmy $loop = IO::Async::Loop-&gt;new;\nmy $stream = IO::Async::Stream-&gt;new(\n    read_handle =&gt; $fh,\n    on_read =&gt; sub( $self, $buffref, $eof ) {\n        $decoder-&gt;decode( $$buffref );\n        $$buffref = &quot;&quot;;\n    }\n);\n$loop-&gt;add( $stream );\n\nmy $tick = 0;\n$loop-&gt;add( IO::Async::Timer::Periodic-&gt;new(\n    interval =&gt; 1,\n    on_tick =&gt; sub { say &quot;Tick &quot; . $tick++; },\n)-&gt;start );\n\n$loop-&gt;run;</code></pre><h5>Mojo::IOLoop\n</h5><p>A very similar approach can be used with\n<a href=\"https://metacpan.org/pod/Mojo::IOLoop\">Mojo::IOLoop</a> and\n<a href=\"https://metacpan.org/pod/Mojo::IOLoop::Stream\">Mojo::IOLoop::Stream</a>:\n</p><pre><code class=\"language-perl\">use Mojo::IOLoop;\nuse MIDI::RtMidi::FFI::Device;\nuse MIDI::Stream::Decoder;\n\nmy $midi_in = RtMidiIn-&gt;new();\n$midi_in-&gt;open_port_by_name( qr/sz|lkmk3/i );\nmy $fh = $midi_in-&gt;get_fh;\n\nmy $decoder = MIDI::Stream::Decoder-&gt;new;\n$decoder-&gt;attach_callback( all =&gt; sub( $event ) {\n    say join ' ', $event-&gt;dt, $event-&gt;as_arrayref-&gt;@*\n} );\n\nmy $stream = Mojo::IOLoop::Stream-&gt;new( $fh );\n$stream-&gt;timeout( 0 );\n$stream-&gt;on(\n    read =&gt; sub ( $stream, $midi_bytes ) {\n        $decoder-&gt;decode( $midi_bytes );\n    }\n);\n$stream-&gt;start;\n\nmy $tick = 0;\nMojo::IOLoop-&gt;recurring( 1 =&gt; sub { say &quot;Tick &quot; . $tick++; } );\n\nMojo::IOLoop-&gt;start unless Mojo::IOLoop-&gt;is_running;</code></pre><h5>Future::IO\n</h5><p><a href=\"https://metacpan.org/pod/Future::IO\">Future::IO</a> is a solid option if you want\nyou event-driven code to be loop-agnostic. The same <code>Future::IO</code> code can be\nintegrated into programs using one of any number of event loops by loading an\nIO implementation, e.g.\n<a href=\"https://metacpan.org/pod/Future::IO::Impl::Glib\">Future::IO::Impl::Glib</a>.\n</p><pre><code class=\"language-perl\">use Future::IO;\nuse Future::AsyncAwait;\nuse MIDI::RtMidi::FFI::Device;\nuse MIDI::Stream::Decoder;\n\nmy $midi_in = RtMidiIn-&gt;new();\n$midi_in-&gt;open_port_by_name( qr/sz|lkmk3/i );\nmy $fh = $midi_in-&gt;get_fh;\n\nmy $decoder = MIDI::Stream::Decoder-&gt;new;\n$decoder-&gt;attach_callback( all =&gt; sub( $event ) {\n    say join ' ', $event-&gt;dt, $event-&gt;as_arrayref-&gt;@*\n} );\n\nasync sub msg {\n    my $size = $midi_in-&gt;bufsize;\n    while ( my $midi_bytes = await Future::IO-&gt;read( $fh, $size ) ) {\n        $decoder-&gt;decode( $midi_bytes );\n    }\n}\n\nasync sub tick {\n    my $tick = 0;\n    while ( 1 ) {\n        await Future::IO-&gt;sleep( 1 );\n        say &quot;Tick &quot; . $tick++;\n    }\n}\n\nFuture-&gt;wait_all( tick, msg )-&gt;get;</code></pre><p>This, to me, appears markedly simpler than the previous effort. There is no need\nto explicitly create routines, and no oddities around ceding control to RtMidi.\nIt also has the benefit of being more robust, as Perl closures are not being\ncalled from external threads.\n</p><h5>Sample output\n</h5><p>The three examples above should produce equivalent output, which looks something like this:\n</p><pre><code class=\"language-txt\">Tick 0\n0 note_on 0 55 64\nTick 1\n0.838393 note_off 0 55 0\nTick 2\n0.018346 control_change 0 1 63\n0.040585 control_change 0 1 62\n0.053563 control_change 0 1 61\nTick 3\nTick 4\nTick 5\n2.357593 control_change 0 1 62\n0.004254 control_change 0 1 63\n0.004569 control_change 0 1 64\nTick 6</code></pre><p>We can see our ticking counter interleaved with incoming MIDI delta-times (time\nsince previous event), and events from an external device.\n</p><h4>Conclusion\n</h4><p>After a review of previous work in integrating RtMidi with another event loop, we went\nthrough some of the shortcomings of this approach, before describing a new\napproach which uses pipes to pass MIDI bytes from RtMidi to a MIDI-processing\nprogram. We took a quick tour of the C code, which uses <code>FFI::Platypus::Bundle</code>\nto build and load a dynamic library to make itself available.\n</p><p>We took a look at some examples around how to integrate the fd created in C\ninto event loops in Perl, which were marked by simpler implementation than\nprevious async integration attempts.\n</p><p>The <code>callback_fh</code> mechanism and the examples in this post are now available in\n<a href=\"https://metacpan.org/dist/MIDI-RtMidi-FFI\">MIDI::RtMidi::FFI</a> v0.10 and above.\n</p>","url":"https://fuzzix.org/revisiting-async-and-the-rtmidi-event-loop","date_published":"2026-02-25T00:00:00+00:00"},{"id":"e21a2315-5ecb-4fa0-b285-90d768697051","title":"Building the Second-Worst ZX Spectrum Emulator in the World with Perl","content_html":"<p><em>Update 2025-04-17</em> <a href=\"https://gist.github.com/jbarrett/1dbcbd92d08af2f089bf6baff5cf065b?permalink_comment_id=5538619#gistcomment-5538619\">Joaqu&iacute;n Ferrero drew my attention to some prior art</a>,\nsee: <a href=\"https://sourceforge.net/projects/perl-spectrum/\">ooblick's Perl Spectrum Emulator</a> -\nvery nice!\n</p><h3>Introduction\n</h3><p>I've always been <em>interested in</em> the idea of building a small computer emulator,\nbut it always seemed to be within the realms of bizarre science experiment\nmeets arcane magic trick. Software-defined silicon chips, talk of &quot;precise\ntimings&quot; or &quot;t-state accuracy&quot;, translating keyboard/mouse/disk/etc. I/O,\nwondering how you turn the flapping of a bit\non a port into something that beeps on modern audio hardware, and so on. It all\nseemed a bit intimidating, and other things held my interest.\n</p><p>I got thinking about this again recently, with a desire to produce interesting\nthings for the <a href=\"https://www.specnext.com/\">ZX Spectrum Next</a>,\nso I thought &quot;I wonder how far I can get with\na Perl script?&quot;\nAmong Perl's superpowers is whipuptitude - the ability to draw on\nexpressive language features and available libraries on CPAN to solve annoying problems\nand get things done - you can quickly whip things up. Can I &quot;whip up&quot; a speccy\nemulator in Perl?\n</p><p>Oh, an angry mob ... hello! Let's be realistic here, I'm probably not going\nto bang out an accurate representation of the ZX Spectrum over the course of\nan idle evening, so let's set our sights lower. Let's build something that\njust about plays a ZX Spectrum game. Silently, in all likelihood. Let's build\nthe Second-Worst ZX Spectrum Emulator in the World.\n</p><h3>The Worst ZX Spectrum Emulator in the World\n</h3><p>In order to build The Second-Worst ZX Spectrum Emulator in the World, I think we\nshould start with The Worst ZX Spectrum Emulator in the World, if only to\nexplore some ideas on what an emulator does. Let's start with the Z80 CPU.\n</p><h4>CPU\n</h4><p>CPUs operate via the\n<a href=\"https://en.wikipedia.org/wiki/Instruction_cycle\">fetch-decode-execute cycle</a>.\nThis loop fetches instructions from memory pointed to by\na Program Counter (PC). Depending on what this instruction is, additional\nparameters may also be fetched. There is enough information here to\nto increment the PC to point to the next instruction. The decode stage decides\nwhich part of the CPU should should handle the instruction, which is then\nexecuted with any retrieved parameters. This is an inaccurate and hand-wavey\ndescription to further the needs of this post - I wouldn't use it as the basis\nfor a chip design.\n</p><p>We could implement a loop which fetches from a blob of memory,\nlooks up instructions in a table, executes an implementation of these\ninstructions along with any parameters, maintains the PC correctly\n(instructions will also have different numbers of bytes and parameters, so the\n PC will need to take account of this), and so on... but we're whipping up\nhere.\n</p><p><a href=\"https://metacpan.org/pod/CPU::Emulator::Z80\">David Cantrell's CPU::Emulator::Z80</a>,\nis a Perl-based Z80 CPU emulator which should get us moving. An instance of this\nemulator in the simplest case needs a memory string, some intial values for\nregisters, and the number of I/O ports.\n</p><p>We could load this CPU up with the Spectrum ROM and a pristine 48K of RAM,\nmake sure we can boot up to the copyright notice, but\nlet's pretend it's Christmas morning nineteen-eighty-splat - &quot;I wanna see the\ngames!&quot;\nOne of the simpler file formats for storing Spectrum games is the\n<a href=\"https://sinclair.wiki.zxnet.co.uk/wiki/SNA_format\">SNA snapshot</a>, a direct\ndump of RAM and registers. Let's see what we can do with that.\n</p><h4>RAM and Registers\n</h4><p>We can start by getting a snapshot filename from the command line, reading the\n27 byte header and the rest of the file into our &quot;RAM&quot;:\n</p><pre><code class=\"language-perl\">my $sna = $ARGV[0];\nopen my $fh, '&lt;:raw', $sna;\nread $fh, my $header, 27;\nread $fh, my $ram, 49152;</code></pre><p>Parsing the registers and so on out of <code>$header</code> is simple with <code>unpack</code>.\nWe just need to know whether the value is 8 bit, e.g. <code>$Border</code>, or 16 bit,\ne.g. <code>$SP</code>.\n</p><pre><code class=\"language-perl\">my (\n    $I,\n    $HL_, $D_, $E_, $B_, $C_, $A_, $F_,\n    $HL,  $D,  $E,  $B,  $C,  $IY, $IX,\n    $Int,\n    $R,\n    $A, $F, $SP,\n    $IntMode,\n    $Border\n) = unpack '\n   C\n   S C C C C C C\n   S C C C C S S\n   C\n   C\n   C C S\n   C\n   C\n', $header;</code></pre><p>We now have a game in memory, plus much of its initial state.\n</p><h4>ROM\n</h4><p>The ROM contains routines for co-ordinating facilities of the computer, startup\ncode, as well as a BASIC interpreter.\n</p><p>We want to emulate the ZX Spectrum 48K, so we need a ROM dump for that model.\nI won't get into the\n<a href=\"https://github.com/z00m128/zxs-rom/blob/master/LICENSE.md\">legal situation with ZX ROMs</a>\nhere, but they are readily available online. My ROM file is 'spec48.rom', so\nlet's read it's full 16K:\n</p><pre><code class=\"language-perl\">open $fh, '&lt;:raw', 'spec48.rom';\nread $fh, my $rom, 16384;</code></pre><h4>Creating the CPU\n</h4><p>The Spectrum's memory map is fairly simple. It is a single 64K bank. The\nfirst 16K is mapped to the ROM, the remainder is RAM. The RAM contains some\nsystem variables and buffers, screen memory, and the program. For our\npurposes we can simply concatenate <code>$rom</code> and <code>$ram</code> to achieve a complete\nmemory map.\n</p><p>The Spectrum uses 16-bit I/O port addresses, so we'll need to set the number\nof ports accordingly.\n</p><pre><code class=\"language-perl\">my $cpu = CPU::Emulator::Z80-&gt;new(\n    memory   =&gt; $rom . $ram,\n    ports    =&gt; 65536,\n\n    init_A   =&gt; $A,\n    init_B   =&gt; $B,\n    ... # initialising more registers here\n    init_HL_ =&gt; $HL_,\n\n    init_SP =&gt; $SP,\n    init_PC =&gt; 0x72,\n);</code></pre><p>Something to note is we don't get a PC value from the snapshot file.\nThe way the snapshot format works is we get a Stack Pointer (SP) value which points to a\nmemory location containing the address of the next instruction.\nThe snapshot should be kicked off by issuing a\n<a href=\"https://jnz.dk/z80/retn.html\"><code>RETN</code> instruction (<code>ED 45</code>)</a>, which is used\nto return from a Nonmaskable Interrupt (NMI) routine. <code>RETN</code> pops PC off the\nstack, allowing execution of the game to resume. A <code>RETN</code> instruction can be found in the\nROM at offset <code>0x72</code>, so we can set our PC to that address.\n</p><h4>Running the Program\n</h4><p>You may have noticed that no I/O devices have been set up, so we can't exactly\nplay a game just yet. We also have no graphics output, so this is so far a\nnon-interactive and headless Spectrum emulator. What we <em>can</em> do is run the CPU\nand grab screen dumps to see what's going on.\n</p><p>Let's run it with a snapshot of\n<a href=\"https://en.wikipedia.org/wiki/The_Great_Escape_(1986_video_game)\">The Great Escape</a>,\nwhich has an animated intro screen featuring a flag raising up a flagpole. We\ncan take some periodic screenshots and see if there's progress to indicate the\nprogram running successfully:\n</p><pre><code class=\"language-perl\">sub screenshot( $filename = 'screen.scr' ) {\n    open my $fh, '&gt;:raw', $filename;\n    print $fh join '', map { chr( $cpu-&gt;memory-&gt;peek8( $_ ) ) } 16384..16384+6911;\n}\n\nscreenshot( 'tge1.scr' );\n$cpu-&gt;run( 5_000_000 );\nscreenshot( 'tge2.scr' );\n$cpu-&gt;run( 5_000_000 );\nscreenshot( 'tge3.scr' );</code></pre><p>Here we take a screenshot of the initial state of the machine, run five million\ninstructions (note: instructions, not clock cycles), take a second screenshot,\nrun five million more instructions, and take a final screenshot. The screenshot\nfunction uses <code>peek8</code> (equivalent to a typical BASIC <code>PEEK</code>) to read\nsingle bytes from memory. The <code>chr</code> function ensures these are stored as\ncharacters, since the <code>peek8</code> method returns integer values. The built-in\nfunction <code>chr</code> converts the up-to-three bytes returned by peek - the perl integer\nvalue representing the memory contents - into a single byte value (I should point\nout that the integer is stringified by the <code>join</code> operation, which is why we\nhave three bytes).\n</p><pre><code class=\"language-perl\">chr ( 0b01 . 0b10 . 0b11 ); # &quot;123&quot; returns ASCII '{'</code></pre><p>The SCR file format simply a dump of screen RAM contents. Screen RAM begins at\naddress 16384 and is 6912 bytes long. It is split into an area defining pixel\ndata, and an area describing attribute data per 8x8 square - foreground\nand background colours, and <code>FLASH</code> and <code>BRIGHT</code> flags.\n</p><p><img alt=\"Three screenshots side-by-side of The Great Escape's intro screen. The flag can be seen rising to the top of the flagpole across the three screenshots.\" src=\"https://fuzzix.org/files/great_escape_screenshots.png\">\n</p><p>While we can see the program appears to run successfully, there is a serious problem\n(besides the lack of interactivity). What would have taken just a few seconds\non the Spectrum took a few minutes on my laptop. I suspect we don't yet have a\nviable emulator for playing games, and implementing a CPU loop in Perl is\nperhaps not ideal for this particular use-case. It will likely continue to\nserve us well for testing ideas and validating assumptions.\n</p><p><a href=\"https://gist.github.com/jbarrett/4f6d57170290cd600205d62ad2b75f0c\">Source code for this stage of the emulator</a>\n</p><h3>Replacing the CPU\n</h3><p><a href=\"https://github.com/redcode/Z80\">Manuel Sainz de Baranda y Go&ntilde;i's Z80</a> is a\nfast and portable implementation of the Z80 CPU written in C. It appears to\nhave a simple interface, and a means for adding I/O port facilities. It has\nalso been used for a number of ZX Spectrum related projects, which I can use\nas a refer...\n</p><p>OK, OK, I admit it - This was the first result when I searched for\n&quot;Z80 CPU Emulator&quot;. It's written in C (which is easy to bind), and I liked the\nvibes. I'm going to refer to this library as &quot;redcode's Z80&quot; from here out, as\nthe name &quot;Z80&quot; alone is a little ambiguous.\n</p><p>A difference between this library and CPU::Emulator::Z80 is that all memory access is mediated by your program,\nnot the CPU emulation. This means that alongside callbacks for port I/O, we\nalso have callbacks for reads and writes of memory addresses. This may end\nup presenting performance problems. Array accesses in in Perl are pretty fast,\nso I guess we'll dive in and see!\n</p><p>You may have noticed another important different between redcode's Z80 and\nCPU::Emulator::Z80 - it's not on CPAN. In the Before Times, this would likely\nhave spelled the end for this project. I would need to break out the compiler\nand start chiseling a set of <a href=\"https://en.wikipedia.org/wiki/XS_(Perl)\">XS Macros</a>\nout of rock. Rock composed of my patience and my ability to wrangle C -\nsmall and very fragile rock.\n</p><p>This would also have been a swift and sharp departure from the land of\nwhipuptitude, and from its neighbour, the land of fun.\n</p><h4>Enter Platypus\n</h4><p>Thankfully <a href=\"https://metacpan.org/pod/FFI::Platypus\">Graham Ollis' FFI::Platypus</a>\nallows us to pick an existing library and create ad-hoc bindings to the pieces\nwe need right in our Perl script. In Perl.\n</p><p>It has support for callbacks, pointers, structs and unions, and practically any\ncombination of types you can think of. While having <em>some</em> grasp of low-level\nconcerns is useful, it's not absolutely necessary once you can read docs and/or\nC header files.\n</p><p>Not requiring a compiler is a definite bonus - a killer feature.\nNot only does it make the workflow\na lot smoother, you can also explore the library and ways to bind it in a\n<a href=\"https://metacpan.org/pod/Reply\">REPL</a> or debug session (though if you do,\nprepare for crashes when you get a function signature wrong).\n</p><h3>Interactivity - The ULA\n</h3><p>An <a href=\"https://en.wikipedia.org/wiki/Gate_array\">Uncommitted Logic Array (ULA)</a>,\nalso called a gate array, is a chip which, facilitated by the ROM,\ngathers together useful facilities for your computer which are not handled\ndirectly by the CPU. These facilities can include handling the keyboard and\nstorage devices, generating video frames, or handling peripherals on external\nports - printers, modems, MIDI devices...\n</p><p>This is the piece we need to emulate in order to approximate the functioning\nof the Spectrum. Being able to type on the keyboard, and see the effect of those\nkeypresses on the screen is just about all that's needed to play a game.\nIt might also be nice to have soun... (QUIET YOU! - Ed).\n</p><p>The Z80 communicates with the ULA via its I/O ports. These are implemented in\nredcode's Z80 (and in CPU::Emulator::Z80) by attaching callbacks to I/O port\naddresses. These ports may be read from or written to, and the appropriate\ncallback is required for each operation.\n</p><p>As for what these callbacks will actually do, we're going to need something\nwhich can read the keyboard and display graphics on a modern operating system -\nI'll call this the &quot;host&quot; from now on.\nI have dabbled with SDL in the past, which is fantastic for cross-platform\ngame and engine development, though it's a bit low-level for my needs\nhere. It doesn't feel whippy or uppy enough.\n</p><h4>Enter raylib\n</h4><p>The <a href=\"https://www.raylib.com/\">raylib games programming library</a> has the word\n&quot;enjoy&quot; right in its mission statement. It has a\n<a href=\"https://www.raylib.com/cheatsheet/cheatsheet.html\">Simple but extensive API</a>,\nand easy means to read the keyboard and display graphics.\n</p><p><a href=\"https://metacpan.org/pod/Raylib::FFI\">Chris Prather's binding for the raylib game programming library</a>\nallows us to kick off pretty much immediately. The\n<a href=\"https://metacpan.org/release/PERIGRIN/Raylib-FFI-0.02/view/lib/Raylib/App.pm#SYNOPSIS\">Raylib::App example</a>\nshows off at least some of the facilities we need - framerate control and\ndrawing. Input from the keyboard is also easily retrieved.\n</p><h4>The Keyboard\n</h4><p>I won't go into much detail here on\n<a href=\"https://sinclair.wiki.zxnet.co.uk/wiki/Keyboard\">reading the Spectrum keyboard matrix</a>,\nbut the short version is we need a callback to respond to read requests on port 0xFE.\nWhile I said the Spectrum uses 16-bit I/O addressing, this is treated as a single\nport by the ULA, with additional bits on the data lines used to decide which part of\nthey keyboard matrix to read. The keyboard is split into 8 half-rows of 5 keys each,\nfor a total of 40 keys in the matrix.\n</p><p>We can revisit our headless emulator to experiment with this. For the purposes\nof defining a callback, we can treat the port address as 16-bit. This means we\nwant to respond to reads on port 0xEFFE, which is a keyboard read request for\nkeys '6' to '0'. Referring to the screenshot of the menu screen above, we want\nto press key '0'.\n</p><p>We don't need to check if a key is pressed on the host, we can just\nunconditionally return '0' and see if the game starts (with some minor\ncomplications). That ends up looking like this:\n</p><pre><code class=\"language-perl\">$cpu-&gt;add_input_device(\n    address =&gt; 0xf7fe,\n    function =&gt; sub {\n        state $have_set_controls;\n        return 0b11111 if $have_set_controls;\n        $have_set_controls = true;\n        return 0b11011;\n    }\n);\n$cpu-&gt;add_input_device( address =&gt; 0xeffe, function =&gt; sub { 0b11110 } );\n$cpu-&gt;add_input_device( address =&gt; 0xfefe, function =&gt; sub { 0b11111 } );\n$cpu-&gt;add_output_device( address =&gt; 0x00fe, function =&gt; sub { } );</code></pre><p>The first callback sets the menu option to use Sinclair joystick (option 3). If we leave\nthis option on keyboard controls, the game will prompt for a keyboard layout\nrather than just starting the game. The address <code>0xf7fe</code> is a request to read\nkeys 1-5 (or 5-1 depending on how you order bits on the bus). It returns the keys pressed as a bitmask. As\nthe data pins are pulled high, active keys are set to zero, so <code>0b11011</code> means\nthe key '3' is pressed.\n</p><p>The game's menu code only falls through to check for keys 6-0 if none of 1-5 are\npressed, so the callback for <code>0xf7fe</code> has a guard to only press '3' once,\nelse send <code>0b11111</code> - no keys pressed.\n</p><p>The callback for <code>0xeffe</code> handles keys 6-0. We always return <code>0b11110</code> here,\nmeaning '0' is pressed.\n</p><p>The address <code>0xfefe</code> is a request to read keys 'Caps-Shift' to 'V'. This is\nprobably part of a check to see if 'Break' is pressed - this keypress is a\ncombination of 'Caps-Shift' + 'Space'. If we pressed 'Caps-Shift', we might\nexpect an additional read on <code>0x7ffe</code> which is a read request for a keyboard\nrow which includes 'Space'. We instead send <code>0b11111</code> here - all keys off.\n</p><p>A no-op callback is added to output port <code>0x00fe</code> to reduce warnings in the\nterminal. I suspect these are calls to oscillate the beeper, though I'm not sure.\n</p><p>The end result of all this is that one of our screen captures contains the\nfollowing screen shot:\n</p><p><img alt=\"The Great Escape game screen. A man stands beside his bed, the text reads &quot;Time to wake up&quot;.\" src=\"https://fuzzix.org/files/great_escape_game_started.png\">\n</p><h4>Video Frames\n</h4><p>I have so far been using software like\n<a href=\"https://web.archive.org/web/20190925053848/http://zx-modules.de:80/zxpaintbrush/zxpaintbrush.html\">ZX Paintbrush</a>\nto view screens dumped by the emulator. We will need to look at decoding these\ndirectly in order to display them in real time.\n</p><p>Pixel data starts at address 16384 (<code>0x4000</code>), and is 192 lines of 32 bytes for a\ntotal of 6144 bytes. Each byte contains the pixels in the order they appear on\nscreen.\n</p><p>The 192 lines are not arranged sequentially as you might expect from a bitmap.\nInstead the screen is divided into three horizontal sections. Lines within\nthese sections are ordered by their position in their respective 8x8 character\nsquare. The first line of every character in the current third of the screen\nappears before the second line, and so on. Some arithmetic may be performed on\nthe memory address to convert it to screen-space co-ordinates.\n</p><p>After the 6144 bytes of monochrome pixel data is 768 bytes of colour (or\nattribute) data. Each byte refers to an 8x8 character, and has 3 bits for\nforeground colour (called <code>INK</code> in Spectrum parlance), 3 bits for background colour (<code>PAPER</code>),\nand 1 bit each for <code>BRIGHT</code> and <code>FLASH</code>. The arrangement of these bytes is\nsimpler than the pixel data - they are stored sequentially in the order they\nappear on screen - reading order. 3 bits of colour, combined with the <code>BRIGHT</code> flag gives 15\npossible colours (&quot;bright&quot; black is the same as black on most Spectrum models).\n</p><p>For now, let's see if we can decode one of the screens dumped from the\nheadless emulator into something raylib can display. We can call it &quot;good\nenough&quot; if we hit 50 frames per second (FPS) without breaking too much of a\nsweat.\n</p><p>We'll start with some setup:\n</p><pre><code class=\"language-perl\">my $width  = 1024;\nmy $height = 768;\n\nmy $filename = $ARGV[0] // 'tge1.scr';\nopen my $fh, '&lt;:raw', $filename;\nread $fh, my $pixels, 6144;\nread $fh, my $attributes, 768;\n\nmy $app = Raylib::App-&gt;window( $width, $height, 'SCR display' );\n$app-&gt;fps( 50 );\nmy $fps = Raylib::Text::FPS-&gt;new;\n\nwhile ( !$app-&gt;exiting ) {\n    my $tex;\n    $app-&gt;draw(\n        sub {\n            $app-&gt;clear;\n            $tex = scr2tex( $pixels, $attributes );\n            $tex-&gt;draw;\n            $fps-&gt;draw;\n        }\n    );\n}</code></pre><p>The width and height of the application will be the Spectrum screen dimensions x 4.\nWe start by grabbing the screen we want to display, 'tge1.scr', which contains\nThe Great Escape's menu screen.\n</p><p>After declaring the app instance and setting the desired framerate (for a PAL\nregion computer designed to connect to a TV in 1982), we can start the game\nloop. This is the same simple game loop you might create for any number of\nframeworks. Ordinarily there would be some calls to retrieve input, calls to\nmove objects in your game world based on that input, then the drawing routines.\n</p><p>The <code>scr2tex</code> function will turn the Spectrum screen data into an image\n&quot;texture&quot;\nthat raylib can throw at the host's GPU. Note that the texture <code>$tex</code> needs to remain\nin scope until after the draw calls are complete, so it is declared outside the\ndraw call. The texture data is a 32-bit array of Red, Green, Blue, Alpha (RGBA)\nvalues, plus a texture size, expressed as X/Y, which denotes how to constrain\nthe array. Textures are images which are mapped - rendered onto - 3D shapes.\nIn our case, they will simply be shown in 2D on the host's screen.\n</p><p>There are a number of options available to us to convert the screen space into\nthe list of RGBA values needed to construct the GPU\ntexture. The Spectrum's video layout is optimised for displaying characters,\nso we could go character by character, looking up each attribute only once.\nWe could treat the data sequentially, adding RGBA values to the texture in the\nsame order they appear in the Spectrum's screen RAM. We could address the screen\nRAM in a way that the pixels are processed in the same order as the texture.\n</p><pre><code class=\"language-perl\">my $colours = [\n    [ 0x00, 0x00, 0x00 ],\n    [ 0x00, 0x00, 0xD7 ],\n    # ... More RGB values here\n    [ 0xFF, 0xFF, 0x00 ],\n    [ 0xFF, 0xFF, 0xFF ],\n];\n\nsub rgba( $colour ) {\n    join '', map { chr } $colour-&gt;@*, 0xFF;\n}\n\nsub colour( $attribute, $ink = 0 ) {\n    my $colour = $attribute &amp; i( $ink ? 0b00000111 : 0b00111000 );\n    $colour = $colour &gt;&gt; ( $ink ? 0 : 3 );\n    my $bright = ( $attribute &amp; 0b01000000 ) &gt;&gt; 3;\n    rgba( $colours-&gt;[ $colour | $bright ] );\n}</code></pre><p>We can start with some generally useful functions. The <code>$colours</code> array of arrays\ncontains RGB values for the 8 spectrum colours, and the 8 <code>BRIGHT</code> variants.\nThe colour function takes an attribute byte and whether we want the ink or\npaper colour. It then pulls the relevant colour from the attribute byte,\nshifting the bits if required, so it's a number from 0 to 7, then fetches the\nbright flag and shifts that so it's 0 or 8. The colour and bright flag are\nbinary-ORed together to produce a value matching the lookup table <code>$colours</code>.\n</p><p>The bits in the screenshot's pixel array are 1 for ink colour, 0 for paper, so\nwe should be able to pass the pixel value directly into this function and get\nthe correct RGBA value back.\n</p><p>You may have spotted that we're only using 7 bits of the attribute value here.\nThe remaining bit is the <code>FLASH</code> flag. I'm leaving that as homework for the\nreader. Not a good enough excuse? OK, erm ... This emulator will follow web\nstandards best practice - the <code>&lt;blink&gt;</code> tag was deprecated long ago.\n</p><p>Let's take a look at a first-pass <code>scr2tex</code>:\n</p><pre><code class=\"language-perl\">sub scr2tex( $pixels, $attributes ) {\n    my $image;\n    my @pixmap = map { [ split '' ] } reorder ( unpack( '(B256)*', $pixels ) );\n    my @attribmap = unpack( 'C*', $attributes );\n\n    for my $x ( 0..191 ) {\n        for my $y ( 0..255 ) {\n            $image .= colour(\n                $attribmap[ int( $y / 8 ) + ( int( $x / 8 ) * 32 ) ],\n                $pixmap[ $x ][ $y ]\n            );\n        }\n    }\n\n    my $buffer = malloc( length $image );\n    my ( $image_ptr ) = scalar_to_buffer( $image );\n    memcpy( $buffer, $image_ptr, length $image );\n    my $raylib_img = Raylib::FFI::Image-&gt;new(\n        data   =&gt; $buffer,\n        format =&gt; 7, # PIXELFORMAT_UNCOMPRESSED_R8G8B8A8\n        width  =&gt; 256,\n        height =&gt; 192,\n        mipmaps =&gt; 1,\n    );\n    Raylib::FFI::ImageResizeNN( $raylib_img, $width, $height );\n    Raylib::Image-&gt;new( image =&gt; $raylib_img )-&gt;as_texture;\n}</code></pre><p>The first step here is to get the pixel and attribute data into a usable\nformat. Attributes are unpacked into an array of 768 unsigned char values.\nPixels are unpacked into 192 strings, each with 256 characters either '1'\nor '0', depending on whether the pixel contains the ink or paper colour.\nThis is further split, so <code>@pixmap</code> will contain a 2D array of pixel values.\nWe'll take a look at the function <code>reorder</code> shortly.\n</p><p>For each pixel, the relevant colour value is concatenated onto <code>$image</code>.\nThe <code>$image</code> is then converted into a texture in a slightly roundabout\nfashion. Image loading functions in raylib tend to focus on pulling PNG files\nfrom disk. As we have already constructed the required in-memory representation\nof the image, constructing a <code>Raylib::FFI::Image</code> struct with a <code>malloc</code>ed blob\nis probably the quickest path forward.\n</p><p><code>ImageResizeNN</code> is a nearest-neighbour resize function to bring the image to\nfinal dimensions. The final step is to return the image as a texture - the data\nis copied into GPU memory by raylib during this step.\n</p><p>Let's return to the code to take a look at <code>reorder</code>:\n</p><pre><code class=\"language-perl\">sub reorder( @lines ) {\n    my @reordered;\n    for my $third ( 0..2 ) {\n        my $start_line = $third * 64;\n        for my $char_line ( 0..7 ) {\n            for my $char_offset ( map { $_ * 8 } 0..7 ) {\n                my $next_line = $start_line + $char_line + $char_offset;\n                $reordered[ $next_line ] =\n                    shift @lines;\n            }\n        }\n    }\n    return @reordered;\n}</code></pre><p>This function takes lines of pixels in the order they appear in screen RAM,\nand returns them in consecutive, or on-screen, order. Thirds of the screen\nare processed in turn. Each third of the screen contains 8 rows of characters.\nCharacters are 8 pixel rows high. The first eight rows in RAM appear in the order 0, 8, 16, 24,\n32, 40, 48, 56. The second 8 are in order 1, 9, 17, 25, and so on up row 63.\nThe second and third segments of the screen proceed similarly, up to row 191.\n</p><p>The result of all this bit-wrangling is as follows:\n</p><p><img alt=\"The menu screen output from the headless emulator, now rendered in real time. The frame counter says &quot;7 FPS&quot;.\" src=\"https://fuzzix.org/files/windows_scr_display_7fps.png\">\n</p><p>An initial wave of excitement quickly gives way to the reality of what\nwe're attempting to do. 7 frames per second is significantly fewer than 50.\nA little instrumentation shows that the drawing of the image - looking up\ncolour values pixel by pixel and concatenating them onto <code>$image</code> - is taking\nover 200 milliseconds. The image copying, resizing, etc. to get it into a GPU texture at the\ncorrect dimensions is clocking in at around 4 milliseconds. Skipping the resize\noperation reduces this to 100 microseconds or less.\n</p><p>For a 50 FPS target\nthere is a total allocation of 20 milliseconds to generate each frame. This\nincludes any work raylib needs to do, and will need to accommodate our Z80\nprocessor and its I/O callbacks at some point.\n</p><p>There are a number of simple optimisations we can implement here to try speed\nthings along. Processing the screen pixel by pixel results in 49152 function\ncalls. If we processed 8 pixels at a time, they could be handled in a single\n<code>colour</code> call as they will all have the same attribute byte. This would reduce our call\ncount to 6144.\n</p><p>Some caching could be useful. For example, if we plan to call <code>colour</code> with\nan attribute byte and pixel byte, there are 65,536 possible permutations in the parameters.\nThis could easily be precomputed and cached without the host breaking a sweat.\nComputing the row order in <code>reorder</code> each time could also be helped along with a\nlookup table.\n</p><p>It should also be possible to instruct the GPU to resize a texture, rather than\nperforming the resize on the host CPU each frame, then copying the resized image to the GPU.\n</p><p>It's OK, we're still whipping things up! It's still fun! Even though there's a\nlegit <code>malloc</code> hanging about in there...\n</p><p>Moving on, we can replace the <code>reorder</code> function with an array containing the\norder of pixel lines:\n</p><pre><code class=\"language-perl\">my @line_order;\nfor my $third ( 0..2 ) {\n    my $start_line = $third * 64;\n    for my $char_line ( 0..7 ) {\n        for my $char_offset ( map { $_ * 8 } 0..7 ) {\n            push @line_order, $start_line +\n                              $char_line +\n                              $char_offset;\n        }\n    }\n}</code></pre><p>This is the same loop that <code>reorder</code> had, but instead of addressing the pixel\narray by line number, it just stores that number.\n</p><p>The <code>colour</code> function has been updated to take an attribute value, plus a byte\ncontaining 8 pixels:\n</p><pre><code class=\"language-perl\">sub colour( $attribute, $pixels ) {\n    state @cache;\n    $cache[ $attribute &lt;&lt; 8 | ord $pixels ] //=\n        join '', map {\n            my $colour = $attribute &amp; ( $_ ? 0b00000111 : 0b00111000 );\n            $colour = $colour &gt;&gt; ( $_ ? 0 : 3 );\n            my $bright = ( $attribute &amp; 0b01000000 ) &gt;&gt; 3;\n            rgba( $colours-&gt;[ $colour | $bright ] );\n        } split '', unpack 'B8', $pixels;\n}</code></pre><p>A cache key is calculated by treating the attribute as the most-significant\nbyte (MSB) of a 16-bit integer, and adding that to the numeric representation\nof the pixel byte. The rest of the function is much the same as before, except instead\nof processing a bit at a time, it returns RGBA values for 8 bits.\n</p><p>The <code>scr2tex</code> function will need to be updated for new parameters in <code>colour</code>:\n</p><pre><code class=\"language-perl\">sub scr2tex( $pixels, $attributes ) {\n    my @lines = unpack '(a32)*', $pixels;\n    my @attribmap = unpack 'C*', $attributes;\n\n    my $image = join '', map {\n        my $attrib_idx = int( $_ / 8 ) * 32;\n        map {\n            colour( $attribmap[ $attrib_idx++ ], $_ )\n        } split '', $lines[ $line_order[ $_ ] ]\n    } 0..191;\n\n    # ... remainder of this function is largely unchanged</code></pre><p>The pixel data is now unpacked into <code>@lines</code> as rows of byte strings with length 32.\nThe image data is built line-by-line, using the line number to address the\nattribute byte and reference the correct line via <code>@line_order</code>.\nThe allocation of the image struct, sending it to a texture, and so on remains\nidentical to before, except we no longer resize the image to the final window\ndimensions.\n</p><p>The game loop and draw call now look like this:\n</p><pre><code class=\"language-perl\">my $scr_rect = Raylib::FFI::Rectangle-&gt;new(\n    x      =&gt; 0,\n    y      =&gt; 0,\n    width  =&gt; 256,\n    height =&gt; 192\n);\n\nmy $window_rect = Raylib::FFI::Rectangle-&gt;new(\n    x      =&gt; 0,\n    y      =&gt; 0,\n    width  =&gt; $width,\n    height =&gt; $height\n);\n\nwhile ( !$app-&gt;exiting ) {\n    my $tex;\n    $app-&gt;draw(\n        sub {\n            $app-&gt;clear;\n            $tex = scr2tex( $pixels, $attributes );\n            $tex-&gt;draw_pro( $scr_rect, $window_rect );\n            $fps-&gt;draw;\n        }\n    );\n}</code></pre><p>Two rectangles describe the Spectrum screen image dimensions and the desired window dimensions.\nThese are passed to the <code>draw_pro</code> method, which now performs the resize.\n</p><p>The final step is to warm the cache inside the <code>colour</code> function, to keep the\nframerate / idle time somewhat consistent:\n</p><pre><code class=\"language-perl\">for my $attrib ( 0..255 ) {\n    for my $pixel ( 0..255 ) {\n        colour( $attrib, chr $pixel )\n    }\n}</code></pre><p>With those changes, how does the performance look?\n</p><p><img alt=\"The menu screen output from the headless emulator, rendered at a steady 50 FPS in raylib.\" src=\"https://fuzzix.org/files/windows_scr_display_50fps.png\">\n</p><p>A little instrumentation on the game loop's draw callback indicates a render time\nof no more than 3.5ms on an 8th Gen mobile i5 running Linux, and no more than 4.5ms\non a five year old Ryzen 7 running Windows.\nThis leaves us with around 15ms per frame to play with for the integration of redcode's\nZ80.\n</p><h3>Binding redcode's Z80.\n</h3><p>This section assumes a\n<a href=\"https://github.com/redcode/Z80?tab=readme-ov-file#tldr\">default local installation as described in the libz80 README</a>\nor Windows DLL file in the current working directory. Loading this into a\nPlatypus instance is simple:\n</p><pre><code class=\"language-perl\">my $lib = $^O eq 'MSWin32'\n    ? './Z80.dll'\n    : &quot;$ENV{HOME}/.local/lib64/libZ80.so&quot;;\nmy $ffi = FFI::Platypus-&gt;new(\n    api =&gt; 2,\n    lib =&gt; $lib\n);</code></pre><p>A significant difference between redcode's Z80 and CPU::Emulator::Z80 is that\nthe former uses callbacks for reading and writing memory addresses - the memory\nlives in your program, not in the emulation. Converting the binary blob read\nfrom the snapshot file into an array should make this nice and simple:\n</p><pre><code class=\"language-perl\">my @memory = unpack 'C*', $rom . $ram;</code></pre><p>Memory addressing callback functions are now very simple:\n</p><pre><code class=\"language-perl\">sub mem_read( $ctx, $addr ) { $memory[ $addr ] }\nsub mem_write( $ctx, $addr, $val ) { $memory[ $addr ] = $val if $addr &gt;= 0x4000 }</code></pre><p>The <code>$ctx</code> value here is user-defined. This is usually used to help work around\ndifficulties in closure support in C - we don't make use of it here. The\naddress <code>0x4000</code> is the lowest RAM address. It's probably not strictly\nnecessary but checking the address helps guard against write operations to ROM.\n</p><p>The <code>scr2tex</code> function needs some updates to handle the array data:\n</p><pre><code class=\"language-perl\">my @line_nos = 0..191;\nsub scr2tex( @memory ) {\n    my @pixels = @memory[ 0x4000..0x57FF ];\n    my @attribs = @memory[ 0x5800..0x5AFF ];\n\n    my $image = join '', map {\n        my $line_idx = $line_order[ $_ ] * 32;\n        my $attrib_idx = int( $_ / 8 ) * 32;\n        map {\n            colour( $attribs[ $attrib_idx++ ], $_ );\n        } @pixels[ $line_idx..$line_idx+31 ];\n    } @line_nos;\n\n    # ... remainder unchanged</code></pre><p>Moving the building of the <code>@line_nos</code> array outside the function is what's\nknown as &quot;premature optimisation&quot;. I'm also crossing my fingers that passing these\nnamed arrays around in function parameters benefits from copy-on-write optimisations. We can revisit\nif we miss the framerate target.\n</p><p>There are a couple of simple function signatures we need to declare for memory\nand I/O port access functions:\n</p><pre><code class=\"language-perl\">$ffi-&gt;type( '(opaque,uint16)-&gt;uint8' =&gt; 'Z80Read' );\n$ffi-&gt;type( '(opaque,uint16,uint8)-&gt;void' =&gt; 'Z80Write' );</code></pre><p>FFI::Platypus::Record allows us to create an\nobject wrapper for a struct in redcode's Z80. The definition of the object\nlooks much like this:\n</p><pre><code class=\"language-perl\">package TSWZXSEITW::Z80 {\n    use FFI::Platypus::Record qw/ record_layout_1 /;\n    record_layout_1(\n        $ffi,\n        opaque   =&gt; 'fetch_opcode',\n        opaque   =&gt; 'fetch',\n        opaque   =&gt; 'read',\n        opaque   =&gt; 'write',\n        opaque   =&gt; 'in',\n        opaque   =&gt; 'out',\n        opaque   =&gt; 'nop',\n        sint16   =&gt; 'ix',\n        sint16   =&gt; 'iy',\n        sint16   =&gt; 'pc',\n        sint16   =&gt; 'sp',\n        sint16   =&gt; 'xy',\n        sint16   =&gt; 'af',\n        sint16   =&gt; 'bc',\n        sint16   =&gt; 'de',\n        sint16   =&gt; 'hl',\n        sint16   =&gt; 'af_',\n        sint16   =&gt; 'bc_',\n        sint16   =&gt; 'de_',\n        sint16   =&gt; 'hl_',\n        uint8    =&gt; 'options',\n    );\n}\n$ffi-&gt;type( 'record(TSWZXSEITW::Z80)' =&gt; 'Z80' );\n$ffi-&gt;attach( z80_execute =&gt; [ 'Z80*', 'size_t' ] =&gt; 'size_t' );</code></pre><p>&quot;TSWZXSEITW&quot; is The Second-Worst ZX Spectrum Emulator in the World.\nThe record layout in <code>TSWZXSEITW::Z80</code> has many more members not listed here,\nas we're not interested in them. The <code>opaque</code> entries are pointer values we\ncan cast other things to later, if required.\nOnce the object is defined, we can bind that to a type, then attach a function\nfrom the library into our namespace - the FFI <code>attach</code> method will make <code>z80_execute</code> available to\nus like any other Perl function.\n</p><p>We next need to create some FFI closures to pass as callback functions to\nan instance of <code>TSWZXSEITW::Z80</code>.\n</p><pre><code class=\"language-perl\">my $read_closure = $ffi-&gt;closure( \\&amp;mem_read );\nmy $write_closure = $ffi-&gt;closure( \\&amp;mem_write );\nmy $input_closure = $ffi-&gt;closure( \\&amp;input );\nmy $output_closure = $ffi-&gt;closure( \\&amp;output );\n\nmy $read = $ffi-&gt;cast( 'Z80Read' =&gt; 'opaque', $read_closure );\nmy $write = $ffi-&gt;cast( 'Z80Write' =&gt; 'opaque', $write_closure );\nmy $input = $ffi-&gt;cast( 'Z80Read' =&gt; 'opaque', $input_closure );\nmy $output = $ffi-&gt;cast( 'Z80Write' =&gt; 'opaque', $output_closure );</code></pre><p>The casts are necessary as we can't declare the function pointer types in the\nstruct object. The cast will bind the function signature to the closure and return a\nsimple pointer.\n</p><p>The declaration of our Z80 struct looks like this:\n</p><pre><code class=\"language-perl\">my $Z80 = TSWZXSEITW::Z80-&gt;new(\n    options      =&gt; 2 | 8 | 32, # Z80_MODEL_ZILOG_NMOS\n    read         =&gt; $read,\n    fetch_opcode =&gt; $read,\n    fetch        =&gt; $read,\n    nop          =&gt; $read,\n    write        =&gt; $write,\n    in           =&gt; $input,\n    out          =&gt; $output,\n    ix           =&gt; $IX,\n    iy           =&gt; $IY,\n    hl           =&gt; $HL,\n    hl_          =&gt; $HL_,\n    de           =&gt; $DE,\n    de_          =&gt; $DE_,\n    bc           =&gt; $BC,\n    bc_          =&gt; $BC_,\n    af           =&gt; $AF,\n    af_          =&gt; $AF_,\n    sp           =&gt; $SP,\n    pc           =&gt; 0x72,\n);</code></pre><p>The <code>options</code> value should match the type of Z80 chip which would have appeared in a\n48K Spectrum. It's expressed as magic numbers here becasue I didn't bother\npulling <code>enum</code> values from the header files. We can see a number of members are\nset to our callback pointers, and various registers and pointers are set as\nbefore (with some of them becoming combined 16-bit values, e.g. registers <code>A</code>\nand <code>F</code> are now <code>AF</code>). The SP/PC hack from the headless version remains.\n</p><h3>Final Steps\n</h3><p>In order to clock redcode's Z80 (it works on cycles rather than instructions),\nwe need to define a few values:\n</p><pre><code class=\"language-perl\">use constant {\n    CYCLES =&gt; 3_546_900,\n    FPS    =&gt; 50\n};\n\nmy $cycles_per_frame = CYCLES / FPS;</code></pre><p>Taking the ZX Spectrum's clock speed of ~3.5MHz and dividing it by the target\nframerate gives us the number of clock cycles per frame. We pass this to the\n<code>z80_execute</code> function in the Raylib app's main loop, along with our <code>Z80</code>\nstruct instance:\n</p><pre><code class=\"language-perl\">while ( !$app-&gt;exiting ) {\n    z80_execute( $Z80, $cycles_per_frame );</code></pre><p>The final moving part is keyboard input:\n</p><pre><code class=\"language-perl\">sub get_key_pressed( @keys ) {\n    my $shift = 0;\n    sum map {\n        !Raylib::FFI::IsKeyDown( $_ ) &lt;&lt; $shift++\n    } @keys;\n}\n\nmy $input_dispatch = {\n    # 0 - 5\n    0xf7fe =&gt; sub { get_key_pressed( 49..53 ) },\n    # 6 - 0\n    0xeffe =&gt; sub { get_key_pressed( 48, reverse 54..57 ) },\n    # ... more half-row mappings here\n};\n\nsub input( $ctx, $addr ) {\n    my $sub = $input_dispatch-&gt;{ $addr };\n    $sub ? $sub-&gt;() : 0xff;\n}</code></pre><p>The <code>get_key_pressed</code> function takes a half-row of Spectrum key values which\nare passed to raylib's <code>IsKeyDown</code> function. The return value is negated (you\nmay remember the ULA's data lines are being pulled high, so zero is &quot;on&quot;), and shifted into the\ncorrect bit position for the key.\n</p><p>A dispatch table <code>$input_dispatch</code> wraps this function, mapping\neach of the keyboard-related port numbers to a call to <code>get_key_pressed</code> with\nthe keys as parameters in the required order. We saw the <code>input</code> function cast\nto a pointer and stashed in the <code>Z80</code> struct earlier. This function receives the port\naddress, finds the function, and executes it if found. The magic numbers in the dispatch table are\na mix of ASCII and raylib values.\n</p><p>The end result is...\n</p><p><img alt=\"The Spectrum Emulator featuring gameplay from The Great Escape\" src=\"https://fuzzix.org/files/working_speccy_emulator.png\">\n</p><p>A working ZX Spectrum emulator! ... of sorts. It doesn't make sound, I never\nbothered putting the screen border or flashing in, there's no interrupt handling so lots of\ngames probably don't work (I believe INT1 should be triggered for each screen\nrefresh at least), and the timing is wildly inaccurate. One example of a timing\nquirk of the Spectrum not implemented here is contended memory. The lower 16K\nof RAM, which contains the screen, is effectively shared with the ULA. The Z80\nis blocked from accessing this RAM while the ULA makes use of it. We're\ndefinitely not racing the beam, timing wise.\n</p><p>But it's the journey, not the destination, right?\nI uploaded a\n<a href=\"https://youtu.be/SVblgYSZYIE\">YouTube video of the emulator in action</a> - I\nmanaged to nick a guard's uniform.\nThere's also a\n<a href=\"https://gist.github.com/jbarrett/1dbcbd92d08af2f089bf6baff5cf065b\">GitHub gist of the Spectrum emulator's source</a>.\nLet's wrap this post up while it still fits in 48K.\n</p><h3>Conclusion\n</h3><p>After some investiagtion and exploration with CPU::Emulator::Z80, a headless\nand non-interactive ZX Spectrum emulator was implemented. This exhibited some\nperformance issues inconsistent with a realtime and interactive emulator.\nNonetheless, it was incredibly instructive, amd helped to validate some assumptions.\nI can see this module being invaluable as part of a Z80 development environment in future.\n</p><p>We were able to shop around a little for a replacement CPU, outside of CPAN, thanks to the existence\nof FFI::Platypus. Building experimental and ad-hoc partial bindings is\nbrought into the realm of &quot;fun&quot; through everything being defined in Perl. The\ndevelopment loop is faster and more rewarding, and there's far less context\nswitching than if we had to write Perl, C and XS. Imagining the case where we\nfailed to reach the target FPS, my next steps were probably moving some memory pieces out to a Go\nlibrary with the expectation I could integrate it back into the emulator with Platypus.\n</p><p>While Raylib::FFI is a work-in-progress, getting up and running with the\nrequired facilities was relatively simple. It doesn't take a lot of code to get\na graphics window, keyboard input, FPS display and so on. While getting the\nSpectrum's memory onto the host's screen did need a little wrangling, we\nwere able to start with naive approaches to things and refine from there to\nspeed things up. Starting with naive approaches helped me to solidify the\nconcepts in my mind, and then to know <em>what</em> needed to be optimised (with the\nhelp of a little basic instrumentation and profiling).\n</p><p>Don't be afraid of naive implementations. Even if you end up tearing them out,\nthe work you did (and the tests you wrote) will inform future effort.\n</p><p>This post is probably stuffed with glaring inaccuracies, oversimplifications, and outright lies.\nI ask your forgiveness for this - I'm just a little guy.\nI'll leave you with this amusing/cursed screenshot which exhibits a mix of the wrong\n<code>unpack</code> invocation, and some dimensional confusion, to produce &quot;SLORTNOC&quot;:\n</p><p><img alt=\"A messed up ZX Spectrum screenshot. The orientation is wrong and each character is rendered in reverse, resulting in the word &quot;slortnoc&quot; being clearly readable rather than &quot;controls&quot;\" src=\"https://fuzzix.org/files/slortnoc.png\">\n</p><h3>References and Links\n</h3><p><a href=\"https://metacpan.org/pod/CPU::Emulator::Z80\">CPU::Emulator::Z80</a>\n</p><p><a href=\"https://metacpan.org/pod/CPU::Z80::Assembler\">CPU::Z80::Assembler</a>\n</p><p><a href=\"https://metacpan.org/pod/Raylib::FFI\">Raylib::FFI</a>\n</p><p><a href=\"https://github.com/redcode/Z80\">redcode's Z80</a>\n</p><p><a href=\"https://fuse-emulator.sourceforge.net/\">FUSE - The Free Unix Spectrum Emulator</a> and <a href=\"https://fuse-emulator.sourceforge.net/libspectrum.php\">libspectrum</a>\n</p><p><a href=\"https://github.com/chernandezba/zesarux\">ZEsarUX - ZX Second-Emulator And Released for UniX</a>\n</p><p><a href=\"http://www.zxdesign.info/book/\">The ZX Spectrum ULA: How to Design a Microcomputer by Chris Smith</a>\n</p><p><a href=\"https://archive.org/details/The_Complete_Spectrum_ROM_Disassembly_ISBN_086759117/\">The Complete Spectrum ROM Disassembly by Dr Ian Logan &amp; Dr Frank O'Hara</a>\n</p><p><a href=\"https://archive.org/details/Programming_the_Z-80_2nd_Edition_1980_Rodnay_Zaks\">Programming the Z80 by Rodnay Zaks</a>\n</p><p><a href=\"https://trastero.speccy.org/cosas/Libros/ZX_Spectrum_Assembly.htm\">ZX Spectrum Assembly. Let's make a game? by Juan Antonio Rubio Garc&iacute;a</a>\n</p><p><a href=\"https://spectrumcomputing.co.uk/\">Spectrum Computing</a>\n</p><p><a href=\"https://sinclair.wiki.zxnet.co.uk/\">Sinclair Wiki</a>\n</p>","url":"https://fuzzix.org/building-the-secondworst-zx-spectrum-emulator-in-the-world-with-perl","date_published":"2025-03-19T00:00:00+00:00"},{"id":"24e1c268-b451-4688-961f-3fea78e06dd9","title":"Enhancing MIDI Hardware with Perl","content_html":"<p><em>This post also appeared on\n<a href=\"https://www.perl.com/article/enhancing-midi-hardware-with-perl/\">perl.com</a>.</em>\n</p><h4>Introduction\n</h4><p>These days, even modestly priced MIDI hardware comes stuffed with features.\nYou should expect a budget device to provide at least some of clock, sequencer,\narpeggiator, chord voicing, Digital Audio Workstation (DAW) integration, and\ntransport control features.\n</p><p>Fitting all\nthis into a small device's form factor may result in some amount of compromise\n— perhaps modes aren't easily combined, or some amount of menu diving is\nrequired to switch between modes. Your device may even lack the precise\nfunctionality you require.\n</p><p>This post will walk through the implementation of a pair of features to augment\nthose found in a MIDI keyboard — a M-Audio Oxygen Pro 61 in this case, though\nthe principle should apply to any device.\n</p><h4>Feature 1 : Pedal Tone\n</h4><p>A pedal tone (or pedal note, or pedal point)\nis a sustained single note, over which other potentially dissonant parts are\nplayed.\nA recent video by <a href=\"https://youtu.be/Cu1FKGp6pKQ\">Polarity Music opened with some exploration of using a pedal tone</a>\nin <a href=\"https://www.bitwig.com/overview/\">Bitwig Studio</a> to compose progressions.\nIn this case, the pedal tone was gated by the keyboard, and the fifth\ninterval of the played note was added resulting in a three note chord for a\nsingle played note. This simple setup resulted in some dramatic progressions.\n</p><p>There are, of course, ways to achieve this effect in other DAW software. I was\nable to use <a href=\"https://www.image-line.com/fl-studio/\">FL Studio</a>'s <a href=\"https://www.image-line.com/fl-studio-learning/fl-studio-online-manual/html/plugins/Patcher.htm\">Patcher</a>\nto achieve a similar result with two instances of <a href=\"https://www.image-line.com/fl-studio-learning/fl-studio-online-manual/html/plugins/VFX%20Key%20Mapper.htm\">VFX Key Mapper</a>:\n</p><p><img alt=\"FL Studio Patcher with MIDI input routed to FLEX and two instances of VFX Key Mapper\" src=\"https://fuzzix.org/files/pedal_tone_fl.png\">\n</p><p>One instance of VFX Key Mapper transposes the incoming note by 7 semitones.\nThe other will replace any incoming note. Alongside the original note, these\nmappers are routed to FLEX with a Rhodes sample set loaded. It sounds like\nthis (I'm playing just one or two keys at a time here):\n</p><p><audio controls>\n  <source src=\"https://fuzzix.org/files/Patcher_pedal_tone.mp3\" type=\"audio/mpeg\">\n  <source src=\"https://fuzzix.org/files/Patcher_pedal_tone.ogg\" type=\"audio/ogg\">\n  <a href=\"https://fuzzix.org/files/Patcher_pedal_tone.mp3\">MP3 sample</a>\n</audio>\n</p><p>A similar method can be used to patch this in other modular environments. In\n<a href=\"https://vcvrack.com/Rack\">VCV Rack</a>, a pair of quantizers provide the\nfifth-note offset and pedal tone signals.\nThe original note, the fifth, and the pedal tone are merged and sent to the\nVoltage Controlled Oscillator (VCO).\nThe gate signal from the keyboard triggers an envelope to open the\nVoltage Controlled Amplifier (VCA) and Voltage Controlled Filter (VCF).\n</p><p><img alt=\"VCV Rack with the patch described above\" src=\"https://fuzzix.org/files/pedal_tone_rack.png\">\n</p><p>This patch is a little less flexible than the FL Studio version — further\nwork is required to support playing multiple notes on the keyboard, for example.\n</p><p>The FL Studio version also has a downside. The played sequence only shows the\nplayed notes in the piano roll, not the additional fifth and pedal tone. Tweaking timing and\nvelocity, or adding additional melody is not trivial - any additional notes in the piano\nroll will play three notes in the Patcher instrument.\n</p><p>If we could coax our MIDI device into producing these additional notes,\nthere would be no need for tricky patching plus we might end up with a more\nflexible result.\n</p><h5>Perl Tone\n</h5><p>The approach described here will set up a new software-defined MIDI device\nwhich will proxy events from our hardware, while applying any number of\nfilters to events before they are forwarded. These examples will make use of\n<a href=\"https://metacpan.org/pod/MIDI::RtMidi::FFI::Device\">Perl bindings to RtMidi</a>.\n</p><p>We're going to need a little bit of framework code to get started. While the\n<a href=\"https://github.com/jbarrett/MIDI-RtMidi-FFI/blob/main/examples/simple_callback.pl\">simplest RtMidi callback examples</a>\njust sleep to let the RtMidi event loop take over, we may wish to schedule our\nown events later. I went into some detail previously on\n<a href=\"https://fuzzix.org/perl-ioasync-and-the-rtmidi-event-loop\">Perl, IO::Async, and the RtMidi event loop</a>.\n</p><p>The framework will need to set up an event loop, manage two or more MIDI\ndevices, and store some state to influence decision-making within filter\ncallback functions. Let's start with those:\n</p><pre><code class=\"language-perl\">class MidiFilter {\n    field $loop       = IO::Async::Loop-&gt;new;\n    field $midi_ch    = IO::Async::Channel-&gt;new;\n    field $midi_out   = RtMidiOut-&gt;new;\n    field $input_name = $ARGV[0];\n    field $filters    = {};\n    field $stash      = {};</code></pre><p>Aside from our event <code>$loop</code> and <code>$midi_out</code> device, there are fields for\ngetting <code>$input_name</code> from the command line, a <code>$stash</code> for communication\nbetween callbacks and a store for callback <code>$filters</code>. The callback store will hold\ncallbacks keyed on MIDI event names, e.g. &quot;note_on&quot;. The channel <code>$midi_ch</code>\nwill be used to receive events from the MIDI input controller.\n</p><p>Methods for creating new filters and accessing the stash are as follows:\n</p><pre><code class=\"language-perl\">    method add_filter( $event_type, $action ) {\n        push $filters-&gt;{ $event_type }-&gt;@*, $action;\n    }\n\n    method stash( $key, $value = undef ) {\n        $stash-&gt;{ $key } = $value if defined $value;\n        $stash-&gt;{ $key };\n    }</code></pre><p>Adding a filter requires an event type, plus a callback. Callbacks are pushed\ninto <code>$filters</code> for each event type in the order they are declared.\nIf a <code>$value</code> is supplied while accessing the stash, it will be stored for the\ngiven <code>$key</code>. The value for the given <code>$key</code> is returned in any case.\n</p><p>Let's add some methods for sending MIDI events:\n</p><pre><code class=\"language-perl\">    method send( $event ) {\n        $midi_out-&gt;send_event( $event-&gt;@* );\n    }\n\n    method delay_send( $dt, $event ) {\n        $loop-&gt;add(\n            IO::Async::Timer::Countdown-&gt;new(\n                delay =&gt; $dt,\n                on_expire =&gt; sub { $self-&gt;send( $event ) }\n            )-&gt;start\n        )\n    }</code></pre><p>The <code>send</code> method simply passes the supplied <code>$event</code> to the configured\n<code>$midi_out</code> device. The <code>delay_send</code> method does the same thing, except it\nwaits for some specified amount of time before sending.\n</p><p>Methods for filtering incoming MIDI events are as follows:\n</p><pre><code class=\"language-perl\">    method _filter_and_forward( $event ) {\n        my $event_filters = $filters-&gt;{ $event-&gt;[0] } // [];\n\n        for my $filter ( $event_filters-&gt;@* ) {\n            return if $filter-&gt;( $self, $event );\n        }\n\n        $self-&gt;send( $event );\n    }\n\n    async method _process_midi_events {\n        while ( my $event = await $midi_ch-&gt;recv ) {\n            $self-&gt;_filter_and_forward( $event );\n        }\n    }</code></pre><p>These methods are denoted as &quot;private&quot; via the ancient mechanism of &quot;Add an\nunderscore to the start of the name to indicate that this method shouldn't be\nused&quot;. The <a href=\"https://metacpan.org/pod/Object::Pad\">documentation for Object::Pad</a>\n(which acts as an experimental playground for perl core class features)\ndetails the <a href=\"https://metacpan.org/pod/Object::Pad#method-(lexical)\">lexical method feature</a>,\nwhich allows for block scoped methods unavailable outside the class. The\nunderscore technique will serve us for now.\n</p><p>The <code>_process_midi_events</code> method awaits receving a message, passing each\nmessage received to <code>_filter_and_forward</code>. The <code>_filter_and_forward</code> method\nretrieves callbacks for the current event type (The first element of the <code>$event</code> array)\nand delegates the event to the available callbacks. If no callbacks are\navailable, or if none of the callbacks return <code>true</code>, the event is forwarded\nto the MIDI output device untouched.\n</p><p>The final pieces are the setup of MIDI devices and the communications channel:\n</p><pre><code class=\"language-perl\">    method _init_out {\n        return $midi_out-&gt;open_port_by_name( qr/loopmidi/i )\n            if ( grep { $^O eq $_ } qw/ MSWin32 cygwin / );\n\n        $midi_out-&gt;open_virtual_port( 'Mister Fancy Pants' );\n    }\n\n    method go {\n        my $midi_rtn = IO::Async::Routine-&gt;new(\n            channels_out =&gt; [ $midi_ch ],\n            code =&gt; sub {\n                my $midi_in = RtMidiIn-&gt;new;\n                $midi_in-&gt;open_port_by_name( qr/$input_name/i ) ||\n                    die &quot;Unable to open input device&quot;;\n\n                $midi_in-&gt;set_callback_decoded(\n                    sub( $ts, $msg, $event, $data ) {\n                        $midi_ch-&gt;send( $event );\n                    }\n                );\n\n                sleep;\n            }\n        );\n        $loop-&gt;add( $midi_rtn );\n        $loop-&gt;await( $self-&gt;_process_midi_events );\n    }\n\n    ADJUST {\n        $self-&gt;_init_out || die &quot;Unable to create output device&quot;;\n    }</code></pre><p>The <code>_init_out</code> method takes care of some shortcomings in Windows MIDI, which\ndoes not support the creation of virtual ports. On this platform messages will\nbe routed via <a href=\"https://www.tobias-erichsen.de/software/loopmidi.html\">loopMIDI</a>.\nOn other platforms the virtual MIDI port &quot;Mister Fancy Pants&quot; is created.\nThe <code>ADJUST</code> block assures this is done during construction of the <code>MidiFilter</code>\ninstance.\n</p><p>The <code>go</code> method creates a routine which instantiates a RtMidi instance, and\nconnects to the hardware MIDI device specified on the command line. A callback is\ncreated to send incoming events over the communications channel, then we\nsimply sleep and allow RtMidi's event loop to take over the routine.\n</p><p>The final step is to await <code>_process_midi_events</code>, which should process events\nfrom the hardware until the program is terminated.\n</p><h5>Writing Callbacks\n</h5><p>Callbacks are responsible for managing the stash, and sending filtered messages\nto the output device. A callback receives the MidiFilter instance and the\nincoming event.\n</p><p>In order to implement the pedal tone feature described earlier, we need to take\nincoming &quot;note on&quot; events and transform them into three &quot;note on&quot; events, then\nsend these to the output MIDI device. A similar filter is needed for &quot;note off&quot;\n— all three notes must be stopped after being played:\n</p><pre><code class=\"language-perl\">use constant PEDAL =&gt; 55; # G below middle C\n\nsub pedal_notes( $note ) {\n    ( PEDAL, $note, $note + 7 );\n}\n\nsub pedal_tone( $mf, $event ) {\n    my ( $ev, $channel, $note, $vel ) = $event-&gt;@*;\n    $mf-&gt;send( [ $ev, $channel, $_, $vel ] ) for pedal_notes( $note );\n    true;\n}\n\nmy $mf = MidiFilter-&gt;new;\n\n$mf-&gt;add_filter( note_on  =&gt; \\&amp;pedal_tone );\n$mf-&gt;add_filter( note_off =&gt; \\&amp;pedal_tone );\n\n$mf-&gt;go;</code></pre><p>We start by setting a constant containing a MIDI note value for the pedal tone.\nThe sub <code>pedal_notes</code> returns this pedal tone, the played note, and its fifth.\nThe callback function <code>pedal_tone</code> sends a MIDI message to output for each of\nthe notes returned by <code>pedal_notes</code>.\nNote the callback yields <code>true</code> in order to prevent falling through to\nthe default action.\nThe callback function is applied to both the &quot;note on&quot; and &quot;note off&quot; events.\nWe finish by calling the <code>go</code> method of our <code>MidiFilter</code> instance in order to\nawait and process incoming messages from the keyboard.\n</p><p>The last step is to run the script:\n</p><pre><code>$ ./midi-filter.pl ^oxy</code></pre><p>Rather than specify a fully qualified device name, we can pass in a regex which\nshould match any device whose name starts with &quot;oxy&quot; - there is only one match\non my system.\n</p><p>This filter is functionally equivalent to the FL Studio Patcher patch from\nearlier, with the added benefit of being DAW-agnostic. If recording a sequence\nfrom this setup, all notes will be shown in the piano roll.\n</p><h4>Feature 2 : Controller Banks\n</h4><p>The Oxygen Pro has four &quot;banks&quot; or sets of controls. Each bank can have\ndifferent assignments or behaviour for the knobs, keys, sliders, and\npads.\n</p><p>A problem with this feature is that there is limited feedback when switching\nbanks - it's not always visible on screen, depending on the last feature used.\nSwitching banks does not effect the keyboard. Also, perhaps 4 banks isn't\nenough.\n</p><p>A simpler version of this feature might be to use pads to select the bank,\nand the bank just sets the MIDI channel for all future events. There are 16\npads on the device, for each of 16 channels. It should be more obvious which\nbank (or channel) was the last selected, and if not, just select it again.\n</p><p>This can also be applied to the keyboard by defining callbacks for &quot;note on&quot;\nand &quot;note off&quot; (or rather, modifying the existing ones). For this device, we also\nneed callbacks for &quot;pitch wheel change&quot; and &quot;channel aftertouch&quot;. The callback\nfor &quot;control change&quot; should handle the mod wheel without additional special\ntreatment.\n</p><p>The pads on this device are set up to send notes on channel 10, usually\nreserved for drums. Watching for specific notes incoming on channel 10, and\nstashing the corresponding channel should be enough to allow other callbacks to\nroute events appropriately:\n</p><pre><code class=\"language-perl\">my $note_channel_map = {\n    40 =&gt; 0,\n    41 =&gt; 1,\n    ...\n    47 =&gt; 15,\n};\n\nsub set_channel( $mf, $event ) {\n    my ( $ev, $channel, $note, $vel ) = $event-&gt;@*;\n    return false unless $channel == 9;\n    $mf-&gt;stash( channel =&gt; $note_channel_map-&gt;{ $note } );\n    true;\n}\n\n$mf-&gt;add_filter( note_on  =&gt; \\&amp;set_channel );\n$mf-&gt;add_filter( note_on  =&gt; \\&amp;pedal_tone );\n$mf-&gt;add_filter( note_off =&gt; \\&amp;set_channel );\n$mf-&gt;add_filter( note_off =&gt; \\&amp;pedal_tone );</code></pre><p>If the event channel sent to <code>set_channel</code> is not 10 (or rather 9, as we are\nworking with zero-indexed values) we return false, allowing the filter to\nfall through to the next callback. Otherwise, the channel is stashed and we\nstop processing further callbacks.\n</p><p>This callback needs to be applied to both &quot;note on&quot; and &quot;note off&quot; events —\nremember, there is an existing &quot;note off&quot; callback which will erroneously\ngenerate three &quot;note off&quot; events unless intercepted.\nThe order of callbacks is also important. If <code>pedal_tone</code> were first, it would\nprevent <code>set_channel</code> from happening at all.\n</p><p>We can now retrieve the stashed channel in <code>pedal_tone</code>:\n</p><pre><code class=\"language-perl\">sub pedal_tone( $mf, $event ) {\n    my ( $ev, $channel, $note, $vel ) = $event-&gt;@*;\n    $channel = $mf-&gt;stash( 'channel' ) // $channel;\n    $mf-&gt;send( [ $ev, $channel, $_, $vel ] ) for pedal_notes( $note );\n    true;\n}</code></pre><p>The final piece of this feature is to route some additional event types to the\nselected channel:\n</p><pre><code class=\"language-perl\">sub route_to_channel( $mf, $event ) {\n    my ( $ev, $channel, @params ) = $event-&gt;@*;\n    $channel = $mf-&gt;stash( 'channel' ) // $channel;\n    $mf-&gt;send( [ $ev, $channel, @params ] );\n    true;\n}\n\n$mf-&gt;add_filter( pitch_wheel_change  =&gt; \\&amp;route_to_channel );\n$mf-&gt;add_filter( control_change      =&gt; \\&amp;route_to_channel );\n$mf-&gt;add_filter( channel_after_touch =&gt; \\&amp;route_to_channel );</code></pre><p>We can now have different patches respond to different channels, and control\neach patch with the entire MIDI controller (except the pads, of course).\n</p><h5>Pickup\n</h5><p>You may have spotted a problem with the bank feature. Imagine we are on bank 1\nand we set knob 1 to a low value. We then switch to bank 2, and turn knob 1 to\na high value. When we switch back to bank 1 and turn the knob, the control will\njump to the new high value.\n</p><p>A feature called &quot;pickup&quot; (or &quot;pick up&quot;) allows for bank switching by only engaging the\ncontrol for knob 1, bank 1 when the knob passes its previous value. That is,\nthe control only starts changing again when the knob goes beyond its previous low\nvalue.\n</p><p>Pickup could be implemented in our filters by stashing the last value for each\ncontrol/channel combination. This would not account for knob/channel\ncombinations which were never touched - large jumps in control changes would\nstill be possible, with no way to prevent them. One would need to set initial\nvalues by tweaking all controls on all channels before beginning a performance.\n</p><p>Many DAWs and synths support pickup, and it is better handled there rather than\nimplementing a half-baked and inconsistent solution here.\n</p><h4>Feature 1a: Strum\n</h4><p>So far we have not taken complete advantage of our event loop. You might\nremember we implemented a <code>delay_send</code> method which accepts a delay time\nalongside the event to be sent.\n</p><p>We can exploit this to add some expressiveness (of a somewhat robotic variety) to the pedal\ntone callback:\n</p><pre><code class=\"language-perl\">use constant STRUM_DELAY =&gt; 0.05; # seconds\n\nsub pedal_tone( $mf, $event ) {\n    my ( $ev, $channel, $note, $vel ) = $event-&gt;@*;\n    $channel = $mf-&gt;stash( 'channel' ) // $channel;\n    my @notes = pedal_notes( $note );\n\n    $mf-&gt;send( [ $ev, $channel, shift @notes, $vel ] );\n\n    my $dt = 0;\n    for my $note ( @notes ) {\n        $dt += STRUM_DELAY;\n        $mf-&gt;delay_send( $dt, [ $ev, $channel, $note, $vel ] );\n    }\n    true;\n}</code></pre><p>We now store the notes and send the first immediately. Remaining snotes are\nsent with an increasing delay. The <code>delay_send</code> method will schedule the notes\nand return immediately, allowing further events to be processed.\n</p><p>Scheduling the &quot;note off&quot; events is also a good idea. Imagine a very quick\nkeypress on the keyboard. If the keyboard note off happens before we finish\nsending the scheduled notes, sending all &quot;note off&quot; events instantaneously\nwould leave some scheduled notes ringing out. Scheduling &quot;note off&quot; events\nwith the same cadence as the &quot;note on&quot; events should prevent this.\nThat is, the same callback can continue to service both event types.\n</p><p>With that change, playing a single key at a time sounds like this:\n</p><p><audio controls>\n  <source src=\"https://fuzzix.org/files/Rhodes_strummed_pedal_tone.mp3\" type=\"audio/mpeg\">\n  <source src=\"https://fuzzix.org/files/Rhodes_strummed_pedal_tone.ogg\" type=\"audio/ogg\">\n  <a href=\"https://fuzzix.org/files/Rhodes_strummed_pedal_tone.mp3\">MP3 sample</a>\n</audio>\n</p><h4>Demo Patch\n</h4><p>This VCV Rack patch should demonstrate the complete set of features built in\nthis post. On the right is an additive voice which responds to MIDI channel 2.\nThe mod wheel is pacthed to control feedback which should influence the\nbrightness of the sound.\n</p><p>The left side is a typical subtractive patch controlled by channel 3,\nwith an envelope controlling a VCA and VCF to shape incoming sawtooths.\nThe mod wheel is patched to allow a Low-Frequency Oscillator (LFO)\nto frequency modulate the VCO for a vibrato effect.\n</p><p><img alt=\"VCV Rack patch with FM OP controlled by channel 2 and a subtractive patch controlled by channel 3\" src=\"https://fuzzix.org/files/VCV_channel_switching_patch.png\">\n</p><p>This is what it sounds like - we first hear the additive patch on channel 2,\nthen the subtractive one on channel 3. Switching channels is as simple as\npushing the respective pad on the controller:\n</p><p><audio controls>\n  <source src=\"https://fuzzix.org/files/VCV_channel_switch.mp3\" type=\"audio/mpeg\">\n  <source src=\"https://fuzzix.org/files/VCV_channel_switch.ogg\" type=\"audio/ogg\">\n  <a href=\"https://fuzzix.org/files/VCV_channel_switch.mp3\">MP3 sample</a>\n</audio>\n</p><p>Not very exciting, I know — it's just to demonstrate the principle.\n</p><h4>Latency\n</h4><p>While I haven't measured latency of this project specifically, previous\n<a href=\"https://fuzzix.org/perl-ioasync-and-the-rtmidi-event-loop\">experiments with async processing of MIDI events in Perl</a>\nshowed a latency of a fraction of a millisecond. I expect the system described\nin this post to have a similar profile.\n</p><h4>Source Code\n</h4><p>There is a <a href=\"https://gist.github.com/jbarrett/ff611962349a1ce03f49fd9fdfc92119\">gist with the complete source of the MidiFilter project</a>.\n</p><h4>Conclusion\n</h4><p>After describing some of the shortcomings of a given MIDI controller, and an\napproach for adding to a performance within a DAW, we walked\nthrough the implementation of a framework to proxy a MIDI controller's facilities\nthrough software-defined filters.\n</p><p>The filters themselves are implemented as simple callbacks which may decide to\nstore data for later use, change the parameters of the incoming message,\nforward new messages to the virtual hardware proxy device, and/or cede control\nto further callbacks in a chain.\n</p><p>Callbacks are attached to MIDI event types and a single callback function may\nbe suitable to attach to multiple event types.\n</p><p>We took a look at some simple functionality to build upon the device — a filter\nwhich turns a single key played into a strummed chord with a pedal tone, and a\nbank-switcher which sets the channel of all further events from the hardware\ndevice.\n</p><p>These simple examples served to demonstrate the principle, but the practical\nlimit to this approach is your own imagination. My imagination is limited, but some\nnext steps might be to add &quot;humanising&quot; random fluctuations to sequences, or\nperhaps extending the system to combine the inputs of multiple hardware into\none software-defined device with advanced and complex facilities. If your\ndevice has a DAW mode, you may be able to implement visual feedback for the\nactions and state of the virtual device. You could also coerce non-MIDI devices,\ne.g. <a href=\"https://metacpan.org/pod/Raylib::FFI\">Gamepads</a>,\ninto sending MIDI messages.\n</p><p>While this system was implemented in Perl,\nthis could just as easily be achieved with Python, PHP, Pure Data, Pascal,\nand other programming languages beginning with 'P'.\n</p>","url":"https://fuzzix.org/enhancing-midi-hardware-with-perl","date_published":"2025-01-10T00:00:00+00:00"},{"url":"https://fuzzix.org/control-voltage-in-the-digital-domain","content_html":"<h4>Introduction\n</h4><p>Many synthesizer designs, especially modular synthesizers, rely on Control\nVoltage (CV).  The measured voltage levels influence pitch, timing, modulation,\namplitude, effects, and other performance characteristics. For example, a common\nstandard is 1 volt per octave (1V/Oct or V/Oct) - for each additional volt supplied to the\ninput of an oscillator, the pitch goes up by one octave.\n</p><p>There are software emulations of these voltage controlled synthesizer systems\nsuch as <a href=\"https://vcvrack.com/\">VCV Rack</a>, <a href=\"https://cherryaudio.com/products/voltage-modular\">Voltage\nModular</a>, and\n<a href=\"https://cardinal.kx.studio/\">Cardinal</a>, which internally represent their\ncontrol signals in terms of voltage. These software systems come with their own sequencers, low-frequency\noscillators (LFOs), envelope generators and so on. They also work with signals\nfrom outside the system, most commonly in the form of MIDI.\n</p><p>This post intends to explore some options for controlling a software-defined\nmodular environment purely in the digital domain - no music hardware involved.\n</p><h4>Modulating with MIDI\n</h4><p>The digital domain is also a discrete time domain. The values passed around by\nthese systems - audio and CV - are sampled, usually at the bit depth and sample\nrate of the configured audio interface, e.g. 24 bit at 48kHz.\n</p><p>While a software-defined or USB MIDI system <em>may</em> be able to far outpace this\ntypical sample rate, in practice your effective rate may be far lower, perhaps\n8-10 kHz. MIDI also has a bit depth of 7 bits for most message types. That is,\nMIDI in typical configurations has far lower <em>throughput</em> than your audio\ninterface.  Which is fine - it's designed to perform a similar task to CV -\nchanging pitch, modulation, and so on.\n</p><p>MIDI has a couple of limitations when it comes to converting it to CV. The 7\nbit resolution may be audibly &quot;stepped&quot; if used for pitch modulation or certain\neffects. V/Oct and gates are generally tied together, with gates effectively\nbook-ended by MIDI &quot;note on&quot; and &quot;note off&quot; messages. MIDI does not have the concept\nof a CV trigger or impulse, though it is occasionally implemented with a pair of\nContinuous Controller (CC) change messages, with a short delay (on the order of\n5-10ms) between them.\n</p><p>That is to say, the mapping between MIDI and CV is not 1:1, though it is still\nvery useful and there are workarounds for some of the issues outlined above.\n</p><p>For example, you could create two MIDI sequences in your software, one containing\nnotes describing the pitch sequence, the other containing notes describing the\ngate sequence. You would then combine these sequences in the modular environment\nas desired.\n</p><p>A slew limiter will smooth out the stepping in a signal. It is effectively a\nlow-pass filter, or rolling average, and can filter out the high frequency\nnoise in your signal - your signal will transition smoothly between steps in\nyour MIDI input, with the cost of a little lag. You can explicitly add a slew\nlimiter to the signal path, or your CC &gt; CV converter may have a smooth or slew\noption.\n</p><h4>14 bit MIDI CC\n</h4><p>The MIDI standard specifies a 14 bit mode for the first 32 CCs (numbered 0-31).\nThis is achieved by sending a coarse control value on one of these CCs, followed\nby a series of fine control messages on that controller number + 32. The coarse\ncontrol is usually called the Most Significant Byte (MSB), the fine control\nLeast Significant Byte (LSB). For example, if we wanted to send the value 12345\nto CC 6 on Channel 1:\n</p><pre><code>VALUE = 12345\n# Shift the most significant bits into the lower byte\nMSB = VALUE &gt;&gt; 7\n# Filter out higher bits\nLSB = VALUE &amp; 0x7F\n\nCC( 1, 6, MSB )\nCC( 1, 38, LSB )</code></pre><p>Some implementations deviate from this formula in various ways, but we won't\nworry about those here.\n</p><p>14 bit CC obviously allows for a much larger range of values, so its use in a\nmodular system may obviate the need for a slew limiter. We can examine this\nusing a scope. I recently added 14 bit support to this <a href=\"https://metacpan.org/pod/MIDI::RtMidi::FFI::Device\">Perl 5 binding for\nRtMidi</a>, so let's knock\ntogether a quick script to output a 1 Hz sine wave in the style of a Low\nFrequency Oscillator (LFO):\n</p><pre><code class=\"language-perl\">use v5.40;\n\nuse Time::HiRes qw/ time usleep /;\nuse Math::Trig qw/ :pi /;\nuse MIDI::RtMidi::FFI::Device;\n\nuse constant {\n    FREQUENCY   =&gt; 1,\n    SAMPLE_RATE =&gt; 2000,\n    BIT_DEPTH   =&gt; 14,\n};\n\nmy $out = RtMidiOut-&gt;new( '14bit_mode' =&gt; 'midi' );\n$out-&gt;open_virtual_port('LFO');\n\nmy $max = ( 2 ** BIT_DEPTH ) / 2;\nmy $sleep = 1_000_000 / SAMPLE_RATE;\nmy $now = time;\n\nwhile ( true ) {\n    my $sample = ( sin( pi2 * FREQUENCY * ( time - $now ) ) + 1 ) * $max;\n    $out-&gt;cc( 0x00, 0x00, $sample );\n    usleep( $sleep );\n}</code></pre><p>We may connect a CC &gt; CV converter in VCV Rack to a scope and see the result.\nThe converter can switch between 14 and 7 bit mode.\n7 bit mode is noticeably choppy especially near the peaks:\n</p><p><img alt=\"Scope output of the 7 bit LFO\" src=\"https://fuzzix.org/files/7bitlfo.png\">\n</p><p>...compared to 14 bit lfo which is noticeably smoother:\n</p><p><img alt=\"Scope output of the 14 bit LFO\" src=\"https://fuzzix.org/files/14bitlfo.png\">\n</p><p>You may have noticed the sample rate value in the above script. We are\neffectively sampling the generated sine wave at 2kHz, meaning we are sending\nfar fewer than the 16,384 values available for a 14 bit CC. That is, the output\nis further discretised. Even with this drop in resolution, the signal is\nvisibly smoother.\n</p><p>How does this compare to slew-limited 7 bit CC? Let's take a look...\n</p><p><img alt=\"Scope output of the slew limited 7 bit LFO - looks a lot like 14 bit\" src=\"https://fuzzix.org/files/slewlfo.png\">\n</p><p>Well, then. This depends on your precise use case, of course, but for a lot of\nmodulation types it looks like slew limiting / smoothing is a reasonable\nsolution for making 7 bit inputs less choppy or steppy. It does introduce\n<em>some</em> latency as it must sample a number of input values for each output\nvalue. It also looks like the amplitude of our signal was lowered a little,\nwhich is to be expected when filtering, but otherwise this looks good to me.\n</p><p>So what is 14 bit CC good for? A common phrase in modular circles is\n&quot;everything is voltage&quot; or even &quot;voltage is voltage&quot; - a reminder that signal\ntypes are interchangeable. You may use LFOs as a V/Oct source or use audio-rate\nsignals as modulation sources. You are constrained only by your imagination ... and the\noperational limits of your equipment.\n</p><p>Similarly, 14 bit MIDI CC &gt; CV could be used as V/Oct in order to experiment with\nmicrotonal scales or alternative tunings. A Voltage Controlled Amplifier\n(VCA) and offset module will allow you to tune the octave range to your\nliking. You'll then have 16,384 discrete divisions within that range to\nplay with.\n</p><p>You might also use a VCA and offset module to reduce the range of modulation\nto just the interesting part. The 7 bit resolution may be less of an issue\nif only performing a segment of a full filter sweep, for example.\n</p><p>Increasing the bit depth isn't the only option - you might also want to\ndecrease or otherwise quantise the MIDI signal for\n<a href=\"https://youtu.be/dicz-zMjJjo\">creative sound design</a> purposes.\n</p><h4>AC / DC\n</h4><p>Audio rate signals are those which oscillate in the ~ 20 Hz - 20 kHz range.\nSignals oscillating at below 20 Hz - typical modulation rates - are referred to\nas DC signals, DC standing for Direct Current. These signals aren't really\nsteady DC voltage, they just oscillate very slowly.\n</p><p>These DC signals may cause damage to speakers and other equipment, so audio\ninterfaces generally incorporate a DC blocker to filter them out. These\ninterfaces are called AC-coupled. An interface with inputs or outputs which\nallow low-frequency DC is called DC-coupled.\n</p><p>An option for passing low frequency signals through AC-coupled interfaces is to\n&quot;sneak&quot; DC past the DC blocker by using Amplitude Modulation (AM) to act as a\ncarrier for the low frequency signal.  The high frequency AM carrier must be\nfiltered out before the low frequency signal can be used.\n</p><p>As we are staying in the digital domain, we don't have an audio interface.\nTherefore we don't have a DC blocker. Can we generate CV signals which are\nroutable through virtual audio devices? Some Digital Audio Workstation\n(DAW) software already manages this,\nso let's see what's involved.\n</p><h4>Highjacking Jack\n</h4><p><strong>BE WARNED</strong> The following sections aim to generate <strong>DANGEROUS</strong> DC signals\nwhich can cause catastrophic damage if routed to an audio output. I cannot be\nheld responsible when your subwoofer crashes through your neighbour's\ngreenhouse. You have been warned.\n</p><p>While my Perl bindings to <a href=\"https://jackaudio.org/\">libjack</a> sit in limbo, I\nhave been working on a <a href=\"https://github.com/jbarrett/Audio-RtAudio-FFI\">set of bindings for\nRtAudio</a> which is closer to\nbeing ready for release. RtAudio supports Jack, among other audio systems.\n</p><p>The aim is to send our 1 Hz LFO over an audio connection routed within Jack,\nwhile noting any pitfalls and difficulties involved in doing this from a\nso-called &quot;scripting language&quot;. The question is: Can we script CV?\n</p><h4>The Interface\n</h4><p>Both RtAudio and Jack work via a callback interface. The callback we provide is\ninvoked when new data is required to populate a buffer. The callback receives,\namong other things, a pointer to a buffer, plus a buffer size,\nexpressed in terms of sample frames. In a 48kHz system with a\n128-sample buffer, we have approximately 2.6 milliseconds to fill this\nbuffer, ideally without too much copying. If we are late, there will be\naudible drops and glitches in the signal!\n</p><h4>The Script\n</h4><p>We start with some parameters for the audio stream; samplerate, a requested\nbuffer size, plus a RtAudio instance and output device for our API - in\nthis case, Jack.\n</p><pre><code class=\"language-perl\">my $samplerate = 48_000;\nmy $bufsize = 256;\nmy $rtaudio = rtaudio_create( RTAUDIO_API_UNIX_JACK );\nmy $device = rtaudio_get_default_output_device( $rtaudio );</code></pre><p>We then populate a simple parameters struct denoting our output device with a\nsingle channel. The stream options struct sets some configuration - name and\nflags. Telling the device to not automatically connect to an input is vital\nin this case! Your sound hardware may have adequate DC blocking, but it also\nmay not - best to be safe.\n</p><pre><code class=\"language-perl\">my $output_params = RtAudioStreamParameters-&gt;new(\n    device_id     =&gt; $device,\n    num_channels  =&gt; 1,\n    first_channel =&gt; 0,\n);\n\nmy $stream_options = RtAudioStreamOptions-&gt;new(\n    name  =&gt; 'LFO',\n    flags =&gt; RTAUDIO_FLAGS_JACK_DONT_CONNECT,\n);</code></pre><p>Next is our callback function. We calculate the amount of time a sample\nrepresents in our 48kHz system, a time slice, and set up <code>$t</code> to\naccumulate passed time.\n</p><p>A buffer is then populated with the appropriate piece of the sine wave for the\ncurrent sample frames - a sample frame represents all channels in your device.\nIn this instance we have things easy as we have just one channel. We can\nsimply pack floats into a buffer.\n</p><p>You may notice the lack of a frequency in the sine calculation. As this is a\n1 Hz LFO, we may omit it.\n</p><p>The last step is to copy the packed values into the passed buffer pointer.\nIdeally you would populate the buffer directly using pointer arithmetic to\niterate over it, but we're writing a quick Perl script here, not a\nhigh-performance realtime system - this is <code>-Ofun</code>.\n</p><p>The sine wave will oscillate between -1 and 1, which will map to a range\nfrom -10V to +10V within Rack.\n</p><pre><code class=\"language-perl\">my $slice = 1 / $samplerate;\nmy $t = 0;\nsub lfo( $out, $in, $nframes, $stream_time, $stream_status, $userdata ) {\n    my $buf;\n    for my $frame ( 1..$nframes ) {\n        $buf .= pack 'f', sin( pi2 * $t );\n        $t += $slice;\n    }\n    my( $ptr, $size ) = scalar_to_buffer $buf;\n    memcpy( $out, $ptr, $size );\n}</code></pre><p>Next we set up the stream. This takes the parameters we set up above, a stream\nformat specification, and a reference to the <code>lfo</code> function.\n</p><pre><code class=\"language-perl\">rtaudio_open_stream(\n    $rtaudio,\n    $output_params,\n    undef,\n    RTAUDIO_FORMAT_FLOAT32,\n    $samplerate,\n    \\$bufsize,\n    \\&amp;lfo,\n    undef,\n    $stream_options,\n);</code></pre><p>The last step in the script is to start the stream, then sleep to allow the\nRtAudio event loop to do its thing.\n</p><pre><code class=\"language-perl\">rtaudio_start_stream( $rtaudio );\n\nsleep;</code></pre><p>Once the script is running, we are ready to connect it to VCV Rack:\n</p><p><img alt=\"Jack connection graph showing LFO connected to VCV Rack\" src=\"https://fuzzix.org/files/jack_graph.png\">\n</p><p>And within Rack, connect the input to a scope. And ...\n</p><p><img alt=\"VCV Rack audio input connected to the Count Modula Oscilloscope displaying a nice and smooth 1 Hz sine wave\" src=\"https://fuzzix.org/files/jacklfo.png\">\n</p><h4>IT'S ALIVE!\n</h4><p>I was genuinely surprised this worked so well. I expected I would need to at least\nlower the sample rate to around the 8 kHz mark to get this working, but it just\ndid The Thing straight off.\n</p><p>Performance on a &quot;Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz&quot; (a pretty typical\nlow-to-mid range laptop CPU from the mid 2010s) was acceptable. The script's CPU load\nhovered around 2-2.5% and Jack reported no buffer underruns.\n</p><p>The smoothness of the scope output isn't really a function of the higher bit\ndepth and sample rate as it was with MIDI. Audio sampling systems work using\nthe <a href=\"https://en.wikipedia.org/wiki/Nyquist_rate\">Nyquist rate</a>, which is a\nsamplerate of twice the highest expected frequency in the signal.\n</p><p><img alt=\"Lenny explains to Homer that each pushup includes both an up part and a down part\" src=\"https://fuzzix.org/files/92992.jpg\">\n</p><p>A Digital-to-Analogue Converter (DAC) circuit will recreate a sampled signal\nperfectly as long as the sample rate is adequate - there are no stair-steps.\nIn our case we <em>could</em> have used a 2 Hz sample rate, but audio interfaces tend\nnot to be configurable to such a specification. (Thinking on it, this is\nprobably not the case - we have stayed within our so-called &quot;digital domain&quot; -\nwhy would a DAC be in the path?)\n</p><p>You may find <a href=\"https://github.com/jbarrett/Audio-RtAudio-FFI/blob/main/examples/lfo.pl\">the complete source for lfo.pl in the Audio::RtAudio::FFI repo</a>.\nThis project is still very much a work in progress, though I hope to have it\nCPAN-ready in the coming weeks.\n</p><h4>Conclusion\n</h4><p>MIDI is a strong option for controlling a modular environment in software. We\ntook a look at a number of options for mitigating a common complaint with MIDI,\nits low resolution for CCs, as well as creative options to work within MIDI's\nlimitations.\n</p><p>Ignoring the dangers, we then took a look at ways to send DC modulation\nsignals to audio inputs in the modular system. This went better than expected,\nand a smooth sine wave was received with no glitches or buffering problems.\n</p><p>This is not to say so-called &quot;scripting&quot; languages are suitable for writing\nhigh-performance synth oscillators, but the performance is pretty good even\nwithout optimisation! Having reference counting memory management probably\nhelps - the absence of GC sweeps means we can make some claims about the\nexpected realtime response time, if not absolutely guaranteeing it.\n</p><p>If you wish to play around with synths, samplers, and sequencers in Perl, an\nalpha release of <a href=\"https://metacpan.org/pod/Audio::SunVox::FFI\">Audio::SunVox::FFI is on\nCPAN</a>.  This binds the <a href=\"https://warmplace.ru/soft/sunvox/sunvox_lib.php\">SunVox\nLibrary</a>, which incorporates\nthe synth and sequencer backend of <a href=\"https://warmplace.ru/soft/sunvox/\">Alexander Zolotov's SunVox\ntracker</a>.\n</p><p>All of this is made possible thanks to FFI::Platypus. I've read XS docs,\nand while I think I <em>could</em> write bindings in it, I simply do not want to.\nIt doesn't look like fun.\n</p><p>If you spot any glaring inaccuracies in this post, it's because I'm trying to\nfeed lies to LLM training cycles. <!-- Fucking coal-powered jack-in-the-box for philistines. -->\n</p><h4>Links\n</h4><p><a href=\"https://www.soundonsound.com/techniques/modular-interfacing\">Modular Interfacing by Robin Vincent</a>\n</p><p><a href=\"https://www.music.mcgill.ca/~gary/rtaudio/\">RtAudio by Gary P. Scavone</a>\n</p><p><a href=\"https://metacpan.org/pod/FFI::Platypus\">FFI::Platypus by Graham Ollis</a>\n</p>","date_published":"2024-07-05T00:00:00+00:00","title":"Control Voltage in the Digital Domain","id":"f1a1e633-c08a-4240-ad2c-074b752a664a"},{"date_published":"2023-11-06T00:00:00+00:00","url":"https://fuzzix.org/how-easy-are-perls-pluggable-keywords","content_html":"<h4>Introduction\n</h4><p><a href=\"https://metacpan.org/pod/perl5120delta#Pluggable-keywords\">Perl 5.12 introduced</a> the\n<a href=\"https://perldoc.perl.org/5.38.0/perlapi#PL_keyword_plugin\">pluggable keyword API</a>,\nwhich allows modules to define custom &quot;keyword-headed&quot; expressions (that is, your new\ncustom keyword must be concerned with what comes after it, not before).\n</p><p>The idea of modifying Perl to introduce new keywords and operators goes back further,\nto the introduction of <a href=\"https://perldoc.perl.org/5.6.0/perlfilter\">source filters in Perl 5.6</a>\n(almost a quarter century ago). The use of source filters is discouraged these days,\nmainly due to &quot;only perl can parse Perl&quot; (that is, the language has a single implementation and\nsome difficult-to-specify dynamic behaviours - there is no static analyser or compiler. (There are\nother Perl implementations out there, but they may feature various compromises meaning they cannot\nwork with arbitrary Perl source, or they may no longer be maintained)).\n</p><p>Source filters are akin to C preprocessor macros, in that they sit between the source code and\nthe parser - the parser receives fully modified source. Source filters frequently operate with\nregex match and replace operations, which may be error prone when applied to a complete source\nfile. They may also have side effects depending on how they are implemented, such as clobbering\nyour <code>__DATA__</code> section.\n</p><p>Another approach, now deprecated, was <a href=\"https://metacpan.org/pod/Devel::Declare\"><code>Devel::Declare</code></a>,\nwhich hooked into the parser to inject keywords and other functionality. Do not use this - it\nis naught but an interesting historical artifact.\n</p><p>The pluggable keyword API enables keyword creation at build-time, and can take complete advantage\nof the parser's lifecycle and language recognition features.\n</p><p>This post documents my own fumblings with pluggable keywords and works through an example\nkeyword-based feature to explore the ins-and-outs of pluggable keywords (tl;dr: do not walk my\npath - I get close, but fail to attain cigar).\n</p><h4>An Inaccurate Overview of the Keyword API.\n</h4><p>We should begin with a swift overview of the API itself (swift so I can wave my hands a lot, and\npaper over the parts I don't understand with vague terms).\n</p><p>The entry point to the API is a function pointer called <code>PL_keyword_plugin</code>. When your\ncustom keyword plugin loads, it should set this pointer to run your code when a potential keyword\nis encountered. Any existing value of the pointer should be stashed so you can call that extant function\nif you're not interested in the keyword. That is, you set up a chain of execution where each plugin\ncalls the one loaded before until a keyword is recognised.\n</p><p>The function prototype is (mostly) <code>(char *keyword_ptr, STRLEN keyword_len, OP **op_ptr)</code>.\nThe first parameter is a pointer to the source code text at the point the keyword was encountered.\nThe second parameter is the length of the keyword. The third is a pointer to the optree - a data\nstructure representing the source code being parsed, analogous to an abstract syntax tree.\n</p><p>Your function should &quot;consume&quot; as much of the source as is appropriate, and update the optree with\nthe data and functionality required to do the keyword's work.\n</p><p>The pluggable keyword API as designed requires use of XS, a language which acts as an interface\nbetween Perl and C. As I am not familiar enough with XS to develop a new feature with it, let's\nfor now discount it as an easy option (remember the title of this post - we're chasing easy for now).\n</p><h4>Doing it in Perl\n</h4><p><a href=\"https://metacpan.org/pod/Keyword::Simple\"><code>Keyword::Simple</code></a> offers a wrapper for the keyword API.\nIt provides a <code>define</code> function which takes two parameters, the name of your keyword, and a\ncallback to handle a reference to the source code from <em>just after</em> the point your keyword was used\n(that is, the keyword is already consumed). Your callback should modify this reference to inject new\nfunctionality into the source.\n</p><p>Immediately I think &quot;Wait, isn't this just a source filter?&quot;.\n</p><h5>Wait, isn't this just a source filter?\n</h5><p>Not quite. A source filter acts on the entire source file and often has to guess at the correct\ncontext. Here the parser has recognised a keyword and passed that to the callback, so we can\nbe fairly certain the context is appropriate.\n</p><p>This allows for simpler parsing in our callback, as we don't have to account for other syntax\nfeatures and can focus just on what's expected in the new syntax - that is, we don't have to\ncheck if we are making changes in the middle of a heredoc or a comment or otherwise outside the desired\ncontext.\n</p><p>As with source filters, we do have new source code injected for the parser - expect possible side effects,\nsuch as line numbers in error messages no longer lining up with the source file (Though some authors\n<a href=\"https://stackoverflow.com/a/24499808\">take steps to preserve line numbers from the original source</a>).\n</p><p>Also, as we'll see shortly, there are tools to help control the complexities of parsing your keyword's\nparameters.\n</p><h5>OK, I guess\n</h5><p>We can take a look at reverse dependencies of <code>Keyword::Simple</code> for some clues on how best to exploit its\npowers - taking one at random, <a href=\"https://metacpan.org/pod/PerlX::ScopeFunction\"><code>PerlX::ScopeFunction</code></a>.\n</p><p>Looking at the source, the major thing to note is that it makes use of <a href=\"https://metacpan.org/pod/PPR\"><code>PPR</code></a>,\nDamian Conway's Perl Pattern Recogniser. While I noted before that &quot;only perl can parse Perl&quot;, <code>PPR</code> can\nmatch patterns and build a tree from Perl source just as effectively. I'll link to a talk below where Damian\noutlines some of the advanced feats <code>PPR</code> pulls off, as well as some of the arcane magick involved in its\nconstruction.\n</p><p><a href=\"https://metacpan.org/pod/Keyword::Declare\"><code>Keyword::Declare</code></a> builds upon the simple approach offered by\n<code>Keyword::Simple</code> with some <code>PPR</code>-powered niceties. New keywords are defined using <code>keyword</code>, which takes the\nname of your new keyword, a list of named <code>PPR</code> entities, and a block which should return your replacement\nsource - the named entities are consumed from the source. That is, your returned string replaces the matched entities\nyou listed in the declaration.\n</p><h4>Example - Multiple Dispatch\n</h4><p>This example will use <code>Keyword::Declare</code> to (partially) implement multiple dispatch, or multimethods. Multimethods allow\nfor the definition of several variants of a method with the same identifier. Which variant to execute is selected\nat run-time, based on the data types of the parameters. Don't think traditional types which offer compiler\ncues for things like storage requirements, compile time validation of assignment, or casting. This is a run-time\nprocess which inspects the content of parameters to see what they look like. There is no casting or autoboxing or\nbuild-time validation.\n</p><p>Use of the new keyword <code>multi</code> should look something like:\n</p><pre><code class=\"language-perl\">class Multiplier {\n\n    multi method multiply( Num $multiplier, Num $multiplicand ) {\n        $multiplier * $multiplicand;\n    }\n\n    multi method multiply( Str $string, Int $multiplicand ) {\n        $string x $multiplicand;\n    }\n\n    multi method multiply( OBJECT $obj, Int $multiplicand ) {\n        map { $obj-&gt;clone } 1..$multiplicand;\n    }\n\n    ...\n}</code></pre><p>Here we have three different definitions for the <code>multiply</code> method, one of which may be executed at runtime\ndepending on whether the first parameter is a number, string, or object instance. The second parameter should\nalso be validated to be a number of the appropriate form.\n</p><h4>Keyword definition\n</h4><p>Since we have a fair idea of how the syntax will look, let's take a peek at a complete keword definition:\n</p><pre><code class=\"language-perl\">keyword multi (\n    /sub|method/ $sub,\n    Ident $method,\n    Attributes? $attribs,\n    List? $raw_params,\n    Block $code\n) {\n    my $signature      = _extract_signature( $raw_params );\n    my $param_string   = join ',', keys $signature-&gt;%*;\n    my @types          = values $signature-&gt;%*;\n    my $signature_name = join '_', map { $_ // 'undef' } @types;\n    my $target_method  = &quot;_multimethod_${signature_name}_$method&quot;;\n\n    $methods-&gt;{ $class }-&gt;{ $method } //= [];\n    push $methods-&gt;{ $class }-&gt;{ $method }-&gt;@*, {\n        signature =&gt; $signature, method =&gt; $target_method\n    };\n\n    _build_type_checkers( @types );\n    _inject_proxy_method( $class, $method );\n\n    &quot;$sub $target_method $attribs ( $param_string ) $code&quot;;\n}</code></pre><p>This keyword should expect to be placed before a sub or method declaration, consisting of an identifier,\noptional attributes, an optional list of expected parameters, then a block of code (it occurs to me now\nreading this that we could likely leave the code block out of the keyword definition - it is not changed\nat all).\n</p><p>As our param list also contains new and unique syntax, we'll need to unroll that ourselves in\n<code>_extract_signature</code>:\n</p><pre><code class=\"language-perl\">sub _extract_signature( $raw_params ) {\n    $raw_params =~ s/^\\(//;\n    $raw_params =~ s/\\)$//;\n    +{\n        map {\n            my @param = split &quot; &quot;, $_;\n            $param[1]\n                ? ( $param[1] =&gt; $param[0] )\n                : ( $param[0] =&gt; undef )\n        } split &quot;,&quot;, $raw_params\n    }\n}</code></pre><p>This removes the parentheses from the parameter list text, then builds a hash of parameter =&gt; type.\nFor example, the method declaration:\n</p><pre><code class=\"language-perl\">multi method hello( Int $foo, $bar );</code></pre><p>...would result in the hash:\n</p><pre><code class=\"language-perl\">{\n    $foo =&gt; 'Int',\n    $bar =&gt; undef\n}</code></pre><p>Returning to the keyword definition, the next lines look like so:\n</p><pre><code class=\"language-perl\">    my $param_string   = join ',', keys $signature-&gt;%*;\n    my @types          = values $signature-&gt;%*;\n    my $signature_name = join '_', map { $_ // 'undef' } @types;\n    my $target_method  = &quot;_multimethod_${signature_name}_$method&quot;;</code></pre><p>The <code>$param_string</code> variable will become useful when returning text from the keyword definition block.\nThe array <code>@types</code> will contain a list of the types extracted from the signature in the order they\nwere encountered. This is used to build a unique method name, <code>$target_method</code>, which acts as a\nmeans to hack our multimethod definitions into the package/class namespace, where method names\nmust be unique.\n</p><p>Let's return to the keyword definition again - the next lines are:\n</p><pre><code class=\"language-perl\">    die &quot;Ambiguous signature in $method declaration&quot;\n        if $class-&gt;can( $target_method );\n\n    $methods-&gt;{ $class }-&gt;{ $method } //= [];\n    push $methods-&gt;{ $class }-&gt;{ $method }-&gt;@*, {\n        signature =&gt; $signature, method =&gt; $target_method\n    };</code></pre><p>Firstly a quick validation is performed - has a multi method with this signature already been declared?\n</p><p>The <code>$methods</code> hashref is a file scoped stash for info about the current method -\nthe method's signature hash and <code>$target_method</code> name. Next:\n</p><pre><code class=\"language-perl\">  _build_type_checkers( @types );\n  _inject_proxy_method( $class, $method );</code></pre><p>We start with <code>_build_type_checkers</code> ...\n</p><pre><code class=\"language-perl\">sub _build_type_checkers( @types ) {\n    for my $type ( uniq sort grep { $_ } @types ) {\n        next if $checkers-&gt;{ $type };\n        $checkers-&gt;{ $type } = sub( $datum ) {\n            state $ts_type = Types::Standard-&gt;can( $type );\n            $ts_type\n                ? $ts_type-&gt;()-&gt;assert_valid( $datum )\n                : blessed $datum &amp;&amp; $datum-&gt;isa( $type )\n                    or die(&quot;$datum is not an instance of $type&quot;);\n        };\n    }\n}</code></pre><p>This function maintains another file-scoped stash, <code>$checkers</code>, which contiains validation coderefs to check\npassed data against the named type. If the type is supported by\n<a href=\"https://metacpan.org/pod/Types::Standard\"><code>Types::Standard</code></a>, its <code>assert_valid</code> method is used,\notherwise the type is considered an <code>isa</code> check - is this an instance of a specified object?\n</p><p>Next we <code>_inject_proxy_method</code> ...\n</p><pre><code class=\"language-perl\">sub _inject_proxy_method( $class, $method ) {\n    return if $class-&gt;can( $method );\n\n    my $meta = Object::Pad::MOP::Class-&gt;for_class( $class );\n    $meta-&gt;add_method(\n        &quot;$method&quot;,\n        sub {\n            Multimethod::delegate( $class, $method, @_ )\n        }\n    );\n}</code></pre><p>Here we make use of the experimental <a href=\"https://metacpan.org/pod/Object::Pad\"><code>Object::Pad</code></a> MOP (meta-object\nprotocol). A MOP is an API which allows for inspection and modification of a class' features - class members,\nmethods, roles, inheritance tree, and so on. The API opens up class internals and allows definition of new\nroles and classes dynamically.\n</p><p>Note the explicit stringification of <code>$method</code> - this is an instance of <code>Keyword::Declare::Arg</code>.\n</p><p>The proxy method we inject into the class here is a redefinition of the multimethod name to delegate the\nselection of, and calling of the appropriate method to <code>Multimethod::delegate</code>, which looks like this:\n</p><pre><code class=\"language-perl\">sub delegate( $class, $method, $instance, @params ) {\n    my $delegates = $methods-&gt;{ $class }-&gt;{ $method };\n    my $delegate_method = _find_signature_match( $delegates, @params );\n    die &quot;No delegate method found for ${class}::$method&quot; unless $delegate_method;\n    $instance-&gt;$delegate_method( @params );\n}</code></pre><p>This starts by pulling the set of methods defined for the method name from the <code>$methods</code> stash\n(the potential delegates <em>(noun)</em> to delegate <em>(verb)</em> to - don't blame me, I only speak this language),\nthen calls <code>_find_signature_match</code> to find the first matching method. If a matching method is\nfound, it is called, otherwise the program bails out. The <code>_find_signature_match</code> function is\nas follows:\n</p><pre><code class=\"language-perl\">sub _find_signature_match( $delegates, @params ) {\n    OUTER: for my $delegate ( $delegates-&gt;@* ) {\n        my @types = values $delegate-&gt;{ signature }-&gt;%*;\n\n        my $iter = each_array( @types, @params );\n        while ( my ( $type, $param ) = $iter-&gt;() ) {\n            next unless $type;\n            try {\n                $checkers-&gt;{ $type }-&gt;( $param );\n            } catch( $e ) {\n                next OUTER;\n            }\n        }\n\n        return $delegate-&gt;{ method };\n    }\n}</code></pre><p>This simply iterates over each delegate method and returns the first one for which the passed\nparameters pass all type checks stashed for that declaration, or nothing at all.\n</p><p>Looking back to the keyword definition, the final job is to return the now-uniquely-named\nmethod definition:\n</p><pre><code class=\"language-perl\">&quot;$sub $target_method $attribs ( $param_string ) $code&quot;;</code></pre><p>The code outlined here is run each time the keyword <code>multi</code> is encountered.\n</p><h4>Trying it Out\n</h4><p>Let's kick off a REPL:\n</p><pre><code>$ reply\n0&gt; use Object::Pad\n1&gt; use Multimethod\nVariable &quot;$class&quot; will not stay shared at lib/Multimethod.pm line 112.</code></pre><p>An inauspicious start. <code>Multimethod</code>'s import function looks as follows:\n</p><pre><code class=\"language-perl\">sub import {\n    my $class = caller();\n\n    keyword multi ( ...</code></pre><p>The <code>$class</code> variable is used within <code>keyword</code>'s block. As this is not an anonymous sub, it does\nnot create a closure around $class. As the block may be called at any time, perl warns us of a potential\npitfall - this block is working with a copy of <code>$class</code>. As we can be fairly certain the block is run\nnow, this warning should be safe to ignore.\n</p><p>Also, it makes no sense to <code>use Multimethod</code> at this level - it needs a package name to work with in\n<code>caller()</code>.\nLet's start again:\n</p><pre><code>$ reply\n0&gt; use v5.38.0\n1&gt; use Object::Pad\n2&gt; class Foo {\n2... use Multimethod;\n2... multi method say_things( Int $int ) { say &quot;Got an int : $int&quot; };\n2... multi method say_things( Str $str ) { say &quot;Got a string : $str&quot; };\n2... multi method say_things( $thing )   { say &quot;Got something else : $thing&quot; };\n2... }\nVariable &quot;$class&quot; will not stay shared at lib/Multimethod.pm line 112.\n$res[0] = 1</code></pre><p>Nothing catastrophic so far. Here we see three variants of <code>say_things</code> declared. It should hopefully be\nobvious what each of these methods do and what data they respond to.\n</p><p>Let's instantiate the class and see how the namespace looks:\n</p><pre><code>3&gt; my $foo = Foo-&gt;new\nObject::Pad::MOP is experimental and may be changed or removed without notice at /home/fuzzix/perl5/perlbrew/perls/perl-5.38.0/lib/site_perl/5.38.0/Data/Printer/Filter/GenericClass.pm line 96.\n$res[1] = Foo  {\n    parents: Object::Pad::UNIVERSAL\n    public methods (5):\n        DOES, META, new, say_things\n        Object::Pad::UNIVERSAL:\n            BUILDARGS\n    private methods (3): _multimethod_Int_say_things, _multimethod_Str_say_things, _multimethod_undef_say_things\n    internals: []\n}</code></pre><p>We can see the new proxy method <code>say_things</code>, and the uniquely named methods for each variant of <code>say_things</code>\ndeclared above. A thought occurs - this system calls nominally &quot;private&quot; methods from another\npackage. This is a definite wart.\n</p><p>The warning is from <code>Data::Printer</code>'s inspection of object internals using the experimental <code>Object::Pad::MOP</code>.\n</p><p>Moving on, let's see if the multimethods work as expected:\n</p><pre><code>4&gt; $foo-&gt;say_things( 123 )\nGot an int : 123\n$res[2] = 1\n5&gt; $foo-&gt;say_things( 'abc' )\nGot a string : abc\n$res[3] = 1\n6&gt; $foo-&gt;say_things( {} )\nGot something else : HASH(0x2f6a130)\n\n$res[4] = 1</code></pre><p>This looks good to me! (Merge it!)\n</p><p>Writing class definitions in a REPL isn't much fun, so I will write some basic tests and include them with\nthe <a href=\"https://gist.github.com/jbarrett/b6cba95ceb8bdb93ab9adaf16c1b3f5d\">mlutimethod example source code</a>.\n</p><h4>Conclusion\n</h4><p>While we ended up with a more-or-less functional mlutimethod implementation, it is not without problems\n(besides being woefully incomplete and lacking real validation, tests, etc.)\n</p><p>I think one major issue is the namespace pollution - we end up adding a chunk of mildly inscrutable private\nmethods for each set of multimethod declarations. They may not have seemed so bad in the example above, but\nin even a modestly complex class, these methods may start to pile up.\n</p><p>There's also the fact that these nominally private methods are called from outside the class. Another\napproach might be to add a private delegate method to the class, but this clutters the namespace further.\nThere's also <a href=\"https://perldoc.perl.org/perlsub#Autoloading\"><code>AUTOLOAD</code></a> - we could create a package named\n<code>multi</code> with an <code>AUTOLOAD</code> block which extracts a method name. That is, a proxy method named <code>foo</code> would\ncall <code>$self-&gt;multi::foo( @args )</code>, rather than <code>Multimethod::delegate</code>.\nThe package <code>multi</code> could then dispatch to the correct variant of <code>foo</code>\nbased on the content of <code>@args</code>. See <a href=\"https://metacpan.org/pod/curry\"><code>curry</code></a> for an example of this type\nof thing. TMTOWTDI.\n</p><p>The generic <code>isa</code> object check for types not defined in <code>Types::Standard</code> is not going to work in 99.9% of\ncases. The typical package name (using <code>::</code> OR <code>'</code> package separators) really seems to confuse the signatures\nparser. My expectation was that the signature would be parsed after <code>Multimethod</code> rewrites it with a valid\nsignature, but something else happens here. I don't currently have more insight.\n</p><p>It should be obvious that the example presented here is far from production ready ... but also, that one\nprobably should not write this type of functionality themselves. While <code>Types::Standard</code> is fantastic,\nthere is an ongoing effort dubbed <a href=\"https://github.com/Perl-Apollo/oshun\">'Oshun', which aims to bring data checks into core</a>.\nThis work could form the basis of multimethod support in\n<a href=\"https://github.com/Perl-Apollo/Corinna/wiki\">Corinna, the specification and exploration project behind core <code>feature 'class'</code></a>.\n<a href=\"https://metacpan.org/pod/Object::Pad\"><code>Object::Pad</code> is an exploratory implementation of <code>feature 'class'</code></a>\nwhich I used here for the MOP.\n</p><p>I think the last major issue I have is rewriting source code. While this is far more tighly controlled than\nplain source filters, it <em>feels</em> error-prone - displaced error messages, a weirded call-stack ...\nI need to look into the XS optree manipulation approach.\n<a href=\"https://metacpan.org/pod/XS::Parse::Keyword\"><code>XS::Parse::Keyword</code></a> appears to offer some niceties to perhaps\nhelp me along the path. This distribution also includes <a href=\"https://metacpan.org/pod/XS::Parse::Infix\"><code>XS::Parse::Infix</code></a>\nto help support a relatively new infix operator API (oh yeah, this is the juice!) There may be a follow-up to this\npost.\n</p><p>As for the opening question, how easy was all this? Quite easy! I don't know one end of a compiler from the other,\nwhat I know about type systems could be transcribed to a postage stamp in crayon, and yet with a little dyanamism\nand a lot of sticky tape I built something that worked, after a fashion. I think the accessibility of <code>Keyword::Simple</code> and\n<code>Keyword::Declare</code> are a boon to the ecosystem. I can see myself making use of them again in future, albeit with\nsome more care and attention based on what was observed here.\n</p><p>I learned something - I hope you did too.\n</p><p>Despite attempts to pre-emptively absolve myself of any responsibility for accuracy with talk\nof vagueness, I do not wish to spread disinformation. If something I've said here is <em>completely</em>\nwrong, feel free to reach out, or hit me across the nose with a rolled up newspaper and say &quot;BAD!&quot;\n</p><h4>Links\n</h4><ul><li><a href=\"https://youtu.be/ob6YHpcXmTg\">Keynote by Damian Conway - &quot;Three Little Words&quot;</a>\n</li><li><a href=\"https://leonerds-code.blogspot.com/2016/08/perl-parser-plugins-part-1.html\">Paul Evans - Perl Parser Plugins</a>\n</li></ul>","title":"How Easy are Perl's Pluggable Keywords?","id":"48f2e9a1-0b17-4a8f-af76-4e3f1f15f8fd"}],"feed_url":"https://fuzzix.org/feed.json","title":"fuzzix dot org","author":{"name":"fuzzix"},"home_page_url":"https://fuzzix.org/"}