<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Markus Velten</title>
    <description>The latest articles on DEV Community by Markus Velten (@markus_velten_e7807418293).</description>
    <link>https://dev.to/markus_velten_e7807418293</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3654774%2Fbb7b7c18-7a3c-443a-a49d-3e35d7c300df.jpg</url>
      <title>DEV Community: Markus Velten</title>
      <link>https://dev.to/markus_velten_e7807418293</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/markus_velten_e7807418293"/>
    <language>en</language>
    <item>
      <title>YM2149 in Rust, Part 2: CLI Player and Browser Demo</title>
      <dc:creator>Markus Velten</dc:creator>
      <pubDate>Wed, 17 Dec 2025 06:34:35 +0000</pubDate>
      <link>https://dev.to/markus_velten_e7807418293/ym2149-in-rust-part-2-cli-player-and-browser-demo-5dg4</link>
      <guid>https://dev.to/markus_velten_e7807418293/ym2149-in-rust-part-2-cli-player-and-browser-demo-5dg4</guid>
      <description>&lt;p&gt;&lt;strong&gt;From terminal oscilloscopes to WebAssembly in 150KB&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is Part 2 of a 4-part series on building a complete YM2149 chiptune ecosystem in Rust. In &lt;a href="https://dev.to/markus_velten_e7807418293/ym2149-in-rust-part-1-building-a-cycle-accurate-emulator-33om"&gt;Part 1&lt;/a&gt;, we covered the chip itself and the cycle-accurate emulator core. Now let's look at the tools built on top of it.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Terminal Player
&lt;/h2&gt;

&lt;p&gt;Before building anything graphical, I wanted a simple way to test playback. The CLI player started as a debugging tool and grew into something I actually enjoy using.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2286fb609ctjvoaowk34.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2286fb609ctjvoaowk34.jpg" alt="CLI player with Playlist support" width="640" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The CLI player supports YM, SNDH, AY and AKS songs, with real-time oscilloscope, spectrum analyzer, and a searchable playlist browser.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0s1mdp44ss527zcftno.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0s1mdp44ss527zcftno.jpeg" alt="CLI player playlist replay" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The CLI player with spectrum bins and oscillators for visuals&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The interface shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Metadata:&lt;/strong&gt; Song title, author, format, duration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Oscilloscope:&lt;/strong&gt; Per-channel waveform display (those aren't just pretty — they help debug envelope timing issues)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spectrum analyzer:&lt;/strong&gt; Frequency content of each channel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Playback controls:&lt;/strong&gt; Keyboard shortcuts for mute, pause, volume, navigation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The playlist browser pulls from a local music directory and displays format tags — [SNDH], [AY], [YM1-6/YMT1/2], [AKS] — so you can see at a glance what you're loading. Type to search, arrow keys to navigate, Enter to play.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa880gds7y9kxker4fcuf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa880gds7y9kxker4fcuf.png" alt="CLI standalone replay" width="706" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Playing "Wings of Death 6" by Jochen Hippel — a YM5 format file with digi-drum effects.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install the CLI globally&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;ym2149-replayer-cli

&lt;span class="c"&gt;# Browse a directory&lt;/span&gt;
ym2149-replayer ./chiptunes/

&lt;span class="c"&gt;# Play a single file&lt;/span&gt;
ym2149-replayer ./examples/ym/ND-Toxygene.ym
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The status line at the bottom shows per-channel state: tone (T) or noise (N), amplitude, frequency, and note name. When a channel plays a digi-drum sample, it shows [DRUM] instead of a frequency. These details matter when you're trying to figure out why a particular track sounds different from the original hardware.&lt;/p&gt;

&lt;p&gt;Arkos Tracker supports multiple YM2149 chips in one song — something that makes it a unique soundchip editor. The CLI handles multi-PSG files transparently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Browser Playback in 150KB
&lt;/h2&gt;

&lt;p&gt;WebAssembly compilation was almost anticlimactic. Rust's &lt;code&gt;wasm-pack&lt;/code&gt; tool handled most of the work. The tricky part was the audio API.&lt;/p&gt;

&lt;p&gt;Web Audio requires you to run code in an &lt;code&gt;AudioWorklet&lt;/code&gt; — a separate thread with strict timing requirements. The WASM module exposes a simple interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Ym2149Player&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// YM, SNDH, AY, or AKS&lt;/span&gt;
&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// In the AudioWorklet&lt;/span&gt;
&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;samples&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_samples&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compiled WASM weighs about 150KB — smaller than most MP3 files, yet it contains complete emulation of the YM2149, Z80 CPU (for AY files), and Motorola 68000 (for SNDH files).&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://ym2149-rs.org/demo/" rel="noopener noreferrer"&gt;browser demo&lt;/a&gt; supports loading your own song files (AY, SNDH, YM, AKS) besides providing some examples for each format. The UI shows metadata, playback progress, and per-channel mute toggles.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance Characteristics
&lt;/h2&gt;

&lt;p&gt;Numbers matter. Here's what to expect:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU per active player&lt;/td&gt;
&lt;td&gt;~2-5% of one core @ 44.1kHz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory per player&lt;/td&gt;
&lt;td&gt;~1-2MB (mostly frame data)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency (native)&lt;/td&gt;
&lt;td&gt;~20ms with default buffer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency (WASM)&lt;/td&gt;
&lt;td&gt;~40-80ms depending on browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WASM binary size&lt;/td&gt;
&lt;td&gt;~150KB (gzipped: ~60KB)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The hot path — &lt;code&gt;clock()&lt;/code&gt; and &lt;code&gt;get_sample()&lt;/code&gt; — runs 44,100 times per second per player. It's optimized for this: no allocations, no branches in the inner loop, cache-friendly data layout. On modern hardware, you could run dozens of simultaneous players before audio becomes a bottleneck.&lt;/p&gt;

&lt;p&gt;For comparison, decoding a compressed audio format like Vorbis or MP3 typically uses similar CPU but requires megabytes of audio data to be loaded into memory. The YM2149 approach trades decode complexity for generation complexity, and the generation is simpler.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;Part 3&lt;/strong&gt;, we'll integrate with the Bevy game engine — playlists, crossfading, sound effects, and a demoscene-style visualization example.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://ym2149-rs.org" rel="noopener noreferrer"&gt;ym2149-rs.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser demo:&lt;/strong&gt; &lt;a href="https://ym2149-rs.org/demo/" rel="noopener noreferrer"&gt;ym2149-rs.org/demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI install:&lt;/strong&gt; &lt;code&gt;cargo install ym2149-replayer-cli&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/slippyex/ym2149-rs" rel="noopener noreferrer"&gt;github.com/slippyex/ym2149-rs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rust</category>
      <category>gamedev</category>
      <category>audio</category>
      <category>emulation</category>
    </item>
    <item>
      <title>YM2149 in Rust, Part 1: Building a Cycle-Accurate Emulator</title>
      <dc:creator>Markus Velten</dc:creator>
      <pubDate>Wed, 10 Dec 2025 06:51:46 +0000</pubDate>
      <link>https://dev.to/markus_velten_e7807418293/ym2149-in-rust-part-1-building-a-cycle-accurate-emulator-33om</link>
      <guid>https://dev.to/markus_velten_e7807418293/ym2149-in-rust-part-1-building-a-cycle-accurate-emulator-33om</guid>
      <description>&lt;p&gt;&lt;strong&gt;What made this 1980s sound chip special — and what it takes to emulate it faithfully&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;There's a particular sound that defined a generation of computing. If you grew up with an Atari ST, ZX Spectrum, Amstrad CPC, or MSX, you know it immediately: bright, buzzy square waves layered into melodies that somehow felt more alive than they had any right to be. That sound came from a single chip — the Yamaha YM2149, or its near-identical sibling, the General Instrument AY-3-8910.&lt;/p&gt;

&lt;p&gt;I spent the past year building &lt;a href="https://ym2149-rs.org" rel="noopener noreferrer"&gt;ym2149-rs.org&lt;/a&gt;, a complete Rust ecosystem for emulating this chip and playing back the music created for it. What started as a weekend experiment turned into something much larger: a cycle-accurate emulator, seven song format replayers, a Bevy game engine plugin, a terminal player, and a browser demo.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;Part 1 of a 4-part&lt;/strong&gt; series covering the project. We'll start with the chip itself and what it takes to emulate it properly.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Chip That Defined an Era
&lt;/h2&gt;

&lt;p&gt;The YM2149 is deceptively simple on paper. Three square-wave tone generators. One noise generator. A hardware envelope unit with 10 shapes. A mixer that combines them. That's it.&lt;/p&gt;

&lt;p&gt;But simplicity breeds creativity. Demoscene musicians in the 1980s and 1990s discovered that by manipulating these limited resources in clever ways, they could produce sounds the chip was never designed to make. Rapid envelope manipulation created additional waveforms (the "SID voice" technique, named after the Commodore 64's more capable chip). Synchronizing envelope restarts to tone periods produced the distinctive "Sync Buzzer" effect. Sample playback through volume register abuse gave us "digi-drums."&lt;/p&gt;

&lt;p&gt;These weren't bugs — they were features discovered by programmers who knew the hardware intimately.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Makes Emulation "Cycle-Accurate"?
&lt;/h2&gt;

&lt;p&gt;Most audio emulators take shortcuts. They read the register values and approximate what the output should sound like. This works for casual listening, but it fails to reproduce the timing-dependent effects that make YM2149 music distinctive.&lt;/p&gt;

&lt;p&gt;The real chip runs on a master clock divided by 8 — roughly 250 kHz on most systems. At every tick:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tone counters decrement and flip their output when they hit zero&lt;/li&gt;
&lt;li&gt;The noise LFSR shifts, generating pseudo-random bits&lt;/li&gt;
&lt;li&gt;The envelope generator steps through its 128-entry shape table&lt;/li&gt;
&lt;li&gt;All outputs feed into a logarithmic DAC&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To reproduce effects like SID voice or Sync Buzzer, you have to emulate every one of these ticks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.subclock_counter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.subclock_counter&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.subclock_divisor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.subclock_counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.tick&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Full internal state update&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Tone generators&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.tone_counters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.tone_counters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.tone_counters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.tone_periods&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.tone_outputs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;^=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Noise generator (17-bit LFSR)&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.noise_counter&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.noise_counter&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.noise_counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.noise_period&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;bit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.noise_lfsr&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.noise_lfsr&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.noise_lfsr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.noise_lfsr&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bit&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Envelope generator...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach costs more CPU cycles than approximation, but it's the only way to faithfully reproduce the tricks that musicians used.&lt;/p&gt;




&lt;h2&gt;
  
  
  Seven Formats, Three Chips
&lt;/h2&gt;

&lt;p&gt;Here's where things got complicated. Chiptune files aren't just audio recordings — they're programs. Different platforms stored YM2149 music in different ways, and supporting them meant building multiple playback engines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;YM files (YM1-YM6, YMT1/2)&lt;/strong&gt; are the simplest: pre-rendered register dumps, one 16-byte frame per vertical blank interrupt (50Hz in Europe, 60Hz elsewhere). The player just loads each frame into the emulated chip and generates samples. These files originated from Leonard/Oxygene's ST-Sound project, which defined the format and provided reference implementations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Arkos Tracker files (.aks)&lt;/strong&gt; are pattern-based tracker data.    Instead of pre-rendered registers, they contain note sequences, instrument definitions, and effect commands. The player interprets these in real-time, computing register values on the fly. Arkos Tracker is the modern tool of choice for YM2149 composers — it runs on PC/Mac but targets the original hardware.&lt;/p&gt;

&lt;p&gt;What makes Arkos Tracker unique is its multi-PSG support. While the original YM2149 has only three channels, Arkos Tracker lets composers write music for two or even three chips simultaneously — up to nine voices. The replayer handles this natively, mixing multiple emulated PSGs in parallel. This opens up a sonic palette that simply wasn't possible on stock hardware, while staying true to the chip's character.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SNDH files&lt;/strong&gt; are where things got interesting. These contain native Motorola 68000 machine code ripped directly from Atari ST demos and games. To play them, I couldn't just emulate the sound chip — I had to emulate the entire machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;AtariMachine&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;M68000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// Motorola 68000 CPU&lt;/span&gt;
    &lt;span class="n"&gt;ram&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// 512KB-4MB depending on model&lt;/span&gt;
    &lt;span class="n"&gt;psg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Ym2149&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// Our sound chip&lt;/span&gt;
    &lt;span class="n"&gt;mfp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mfp68901&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// Timer chip (for interrupts)&lt;/span&gt;
    &lt;span class="n"&gt;ste_dac&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Dac&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// STE-only DMA audio&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;AtariMachine&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;run_frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Execute 68000 code until next VBL&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.vbl_reached&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.cpu&lt;/span&gt;&lt;span class="nf"&gt;.execute_instruction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.bus&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.mfp&lt;/span&gt;&lt;span class="nf"&gt;.tick&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="c1"&gt;// Collect PSG samples at audio rate&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.sample_clock_elapsed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;samples&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.psg&lt;/span&gt;&lt;span class="nf"&gt;.get_sample&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;samples&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MFP 68901 timer chip was essential — many SNDH files use timer interrupts to trigger envelope restarts for SID voice effects. Without accurate timer emulation, those tracks would sound wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AY files (ZXAY/EMUL)&lt;/strong&gt; presented a similar challenge, but for a different CPU. These files contain Z80 machine code from ZX Spectrum games. Another CPU emulator, another memory map, another set of I/O ports.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GIST files (.snd)&lt;/strong&gt; are a lesser-known format from the Atari ST era. GIST (Graphics, Images, Sound, Text) was a sound effect editor that allowed game developers to create short, punchy audio — laser shots, explosions, menu beeps, item pickups. Unlike music formats, GIST files describe single sound effects using instrument definitions and envelope sequences. They’re tiny (often just a few bytes) but surprisingly expressive. The replayer supports multi-voice playback, so effects that used multiple channels play back correctly. These files are a nice complement to the music formats — authentic retro SFX without sampling.&lt;/p&gt;

&lt;p&gt;Supporting all four format families (plus YMT tracker variants and GIST sound effects) meant building a layered architecture where the YM2149 emulator sits at the bottom, format-specific replayers sit in the middle, and applications sit on top.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture: Layers That Don't Leak
&lt;/h2&gt;

&lt;p&gt;The ecosystem is organized into strict layers, each with a single responsibility. At the bottom sits &lt;code&gt;ym2149-common&lt;/code&gt; with shared traits and types. Above that, &lt;code&gt;ym2149-core&lt;/code&gt; provides the cycle-accurate chip emulation. The replayers — YM, SNDH, AY, Arkos, GIST — build on the core. And at the top, applications like the CLI player, Bevy plugin, and WASM demo consume the replayers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fce0u1xg243g9gylue4nt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fce0u1xg243g9gylue4nt.png" alt="ym2149-rs architecture diagram" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each layer only depends on layers below it. The chip emulator knows nothing about file formats. The replayers know nothing about Bevy or browsers. This separation paid off when adding new formats — the SNDH replayer was mostly new 68000 code; the integration points were already defined.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using the chip directly:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You don't need file replayers at all. The &lt;code&gt;ym2149&lt;/code&gt; crate exposes the raw chip for direct register manipulation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;ym2149&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Ym2149&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;chip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Ym2149&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Set channel A to 440 Hz (A4)&lt;/span&gt;
&lt;span class="c1"&gt;// Period = master_clock / (16 * frequency)&lt;/span&gt;
&lt;span class="c1"&gt;// At 2 MHz: 2_000_000 / (16 * 440) ≈ 284&lt;/span&gt;
&lt;span class="n"&gt;chip&lt;/span&gt;&lt;span class="nf"&gt;.write_register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;284&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// Period low byte&lt;/span&gt;
&lt;span class="n"&gt;chip&lt;/span&gt;&lt;span class="nf"&gt;.write_register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0x01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;284&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;0x0F&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Period high byte&lt;/span&gt;
&lt;span class="n"&gt;chip&lt;/span&gt;&lt;span class="nf"&gt;.write_register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0x07&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0b111_110&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;         &lt;span class="c1"&gt;// Mixer: enable tone A only&lt;/span&gt;
&lt;span class="n"&gt;chip&lt;/span&gt;&lt;span class="nf"&gt;.write_register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0x08&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                &lt;span class="c1"&gt;// Channel A volume: max&lt;/span&gt;

&lt;span class="c1"&gt;// Generate samples&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;44100&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;chip&lt;/span&gt;&lt;span class="nf"&gt;.clock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chip&lt;/span&gt;&lt;span class="nf"&gt;.get_sample&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Send to audio output...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is exactly how emulator authors would use it — plug the chip into your Atari ST, ZX Spectrum, or custom system emulator, write registers when the emulated CPU accesses the PSG's memory-mapped I/O, and clock it in sync with your master clock.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hardware effects API:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For advanced users, the chip exposes methods that replayers use for demoscene effects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SID voice: override mixer for rapid envelope manipulation&lt;/span&gt;
&lt;span class="n"&gt;chip&lt;/span&gt;&lt;span class="nf"&gt;.set_mixer_overrides&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0b000_110&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// DigiDrum: inject sample values directly&lt;/span&gt;
&lt;span class="n"&gt;chip&lt;/span&gt;&lt;span class="nf"&gt;.set_drum_sample_override&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample_value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// Sync Buzzer: restart envelope synchronized to tone&lt;/span&gt;
&lt;span class="n"&gt;chip&lt;/span&gt;&lt;span class="nf"&gt;.trigger_envelope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These aren't documented in &lt;a href="https://www.ym2149.com/ym2149.pdf" rel="noopener noreferrer"&gt;Yamaha's datasheet&lt;/a&gt; — they're the building blocks of techniques that demoscene musicians discovered through experimentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most chiptune libraries are black boxes. Give them a file, get audio. YM2149-rs lets you operate at whatever level you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Just want music?&lt;/strong&gt; Use &lt;code&gt;Ym2149Playback&lt;/code&gt; in Bevy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Building a player?&lt;/strong&gt; Use the format replayers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writing an emulator?&lt;/strong&gt; Use the chip directly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Experimenting with synthesis?&lt;/strong&gt; Manipulate registers in real-time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same cycle-accurate core powers all of these use cases.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;Part 2&lt;/strong&gt;, we'll look at the tools built on top of this emulator: a terminal player with real-time visualization, and a browser demo that fits in 150KB.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://ym2149-rs.org" rel="noopener noreferrer"&gt;ym2149-rs.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/slippyex/ym2149-rs" rel="noopener noreferrer"&gt;github.com/slippyex/ym2149-rs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;crates.io:&lt;/strong&gt; &lt;a href="https://crates.io/crates/ym2149" rel="noopener noreferrer"&gt;ym2149&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;Demoscene contributor in the early-to-mid 1990s on the Atari ST, and developer of the &lt;strong&gt;SidSound Designer&lt;/strong&gt; — the first soundchip editor on the Atari ST that enabled up to three SID voices simultaneously, pushing the YM2149 far beyond its original design.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;30+ years of continuous software development — from assembly on 16-bit machines to Rust on modern systems.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>gamedev</category>
      <category>audio</category>
      <category>emulation</category>
    </item>
  </channel>
</rss>
