emacs.d/clones/lisp/stevelosh.com/blog/2016/12/chip8-sound/index.html

596 lines
46 KiB
HTML
Raw Normal View History

2022-10-07 15:47:14 +02:00
<!DOCTYPE html>
<html lang='en'><head><meta charset='utf-8' /><meta name='pinterest' content='nopin' /><link href='http://stevelosh.com/static/css/style.css' rel='stylesheet' type='text/css' /><link href='http://stevelosh.com/static/css/print.css' rel='stylesheet' type='text/css' media='print' /><title>CHIP-8 in Common Lisp: Sound / Steve Losh</title></head><body><header><a id='logo' href='http://stevelosh.com/'>Steve Losh</a><nav><a href='http://stevelosh.com/blog/'>Blog</a> - <a href='http://stevelosh.com/projects/'>Projects</a> - <a href='http://stevelosh.com/photography/'>Photography</a> - <a href='http://stevelosh.com/links/'>Links</a> - <a href='http://stevelosh.com/rss.xml'>Feed</a></nav></header><hr class='main-separator' /><main id='page-blog-entry'><article><h1><a href='index.html'>CHIP-8 in Common Lisp: Sound</a></h1><p class='date'>Posted on December 26th, 2016.</p><p>In the previous posts we looked at how to emulate a <a href="https://en.wikipedia.org/wiki/CHIP-8">CHIP-8</a> CPU with Common
Lisp, added a screen to see the results, and added user input so we could play
games. This is good enough for basic play, but if we want the full experience
we'll need to add sound. Let's <a href="http://makegames.tumblr.com/post/1136623767/finishing-a-game">finish</a> the emulator.</p>
<p>The full series of posts so far:</p>
<ol>
<li><a href="../chip8-cpu/index.html">CHIP-8 in Common Lisp: The CPU</a></li>
<li><a href="../chip8-graphics/index.html">CHIP-8 in Common Lisp: Graphics</a></li>
<li><a href="../chip8-input/index.html">CHIP-8 in Common Lisp: Input</a></li>
<li><a href="index.html">CHIP-8 in Common Lisp: Sound</a></li>
<li><a href="../../../2017/01/chip8-disassembly/index.html">CHIP-8 in Common Lisp: Disassembly</a></li>
<li><a href="../../../2017/01/chip8-debugging-infrastructure/index.html">CHIP-8 in Common Lisp: Debugging Infrastructure</a></li>
<li><a href="../../../2017/01/chip8-menus/index.html">CHIP-8 in Common Lisp: Menus</a></li>
</ol>
<p>The full emulator source is on <a href="https://bitbucket.org/sjl/cl-chip8">BitBucket</a> and <a href="https://github.com/sjl/cl-chip8">GitHub</a>.</p>
<ol class="table-of-contents"><li><a href="index.html#s1-chip-8-sound">CHIP-8 Sound</a></li><li><a href="index.html#s2-the-emulation-layer">The Emulation Layer</a><ol><li><a href="index.html#s3-data">Data</a></li><li><a href="index.html#s4-instructions">Instructions</a></li><li><a href="index.html#s5-timers">Timers</a></li></ol></li><li><a href="index.html#s6-sound-from-scratch">Sound From Scratch</a><ol><li><a href="index.html#s7-sine-waves">Sine Waves</a></li><li><a href="index.html#s8-square-waves">Square Waves</a></li><li><a href="index.html#s9-sawtooth-waves">Sawtooth Waves</a></li><li><a href="index.html#s10-triangle-waves">Triangle Waves</a></li></ol></li><li><a href="index.html#s11-playing-sound">Playing Sound</a><ol><li><a href="index.html#s12-sampling">Sampling</a></li><li><a href="index.html#s13-buffering">Buffering</a></li><li><a href="index.html#s14-configuration">Configuration</a></li><li><a href="index.html#s15-angle-rate-frequency">Angle Rate &amp; Frequency</a></li><li><a href="index.html#s16-running-the-sound-loop">Running the Sound Loop</a></li><li><a href="index.html#s17-threading-issues">Threading Issues</a></li></ol></li><li><a href="index.html#s18-result">Result</a></li><li><a href="index.html#s19-future">Future</a></li></ol>
<h2 id="s1-chip-8-sound"><a href="index.html#s1-chip-8-sound">CHIP-8 Sound</a></h2>
<p>The CHIP-8 has an <em>extremely</em> simple sound and timer system. See <a href="http://devernay.free.fr/hacks/chip8/C8TECH10.HTM#2.5">Cowgod's
documentation</a> for an overview.</p>
<p>In a nutshell there are two registers: the &quot;sound timer&quot; and &quot;delay timer&quot;.
Each of these is decremented sixty times per second whenever they are non-zero.</p>
<p>The delay timer has no special behavior beyond this, but ROMs can read its value
and use it as a real-time clock.</p>
<p>The sound timer cannot be read by ROMs, only written. Whenever its value is
positive the CHIP-8's buzzer will sound. That's the extent of the CHIP-8's
sound: one buzzer that's either on or off.</p>
<h2 id="s2-the-emulation-layer"><a href="index.html#s2-the-emulation-layer">The Emulation Layer</a></h2>
<p>Let's add the required registers and instructions to the emulator.</p>
<h3 id="s3-data"><a href="index.html#s3-data">Data</a></h3>
<p>First we'll add the registers into the <code>chip</code> struct:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defstruct</span></i> chip
<span class="comment">; ...
</span> <span class="paren2">(<span class="code">delay-timer 0 <span class="keyword">:type</span> fixnum</span>)</span>
<span class="paren2">(<span class="code">sound-timer 0 <span class="keyword">:type</span> fixnum</span>)</span>
<span class="comment">; ...
</span> </span>)</span></span></code></pre>
<p>These are of type <code>fixnum</code> instead of <code>int8</code> for reasons we'll see later.</p>
<h3 id="s4-instructions"><a href="index.html#s4-instructions">Instructions</a></h3>
<p>The CHIP-8 has three <code>LD</code> instructions for dealing with these registers. We
actually saw them back in the first post, but now we know their purpose:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">macro-map <span class="comment">;; LD
</span> <span class="paren2">(<span class="code">NAME ARGLIST DESTINATION SOURCE</span>)</span>
<span class="paren2">(<span class="code"><span class="comment">; ...
</span> <span class="paren3">(<span class="code">op-ld-reg&lt;dt <span class="paren4">(<span class="code">_ r _ _</span>)</span> <span class="paren4">(<span class="code">register r</span>)</span> delay-timer</span>)</span>
<span class="paren3">(<span class="code">op-ld-dt&lt;reg <span class="paren4">(<span class="code">_ r _ _</span>)</span> delay-timer <span class="paren4">(<span class="code">register r</span>)</span></span>)</span>
<span class="paren3">(<span class="code">op-ld-st&lt;reg <span class="paren4">(<span class="code">_ r _ _</span>)</span> sound-timer <span class="paren4">(<span class="code">register r</span>)</span></span>)</span></span>)</span>
`<span class="paren2">(<span class="code"><i><span class="symbol">define-instruction</span></i> ,name ,arglist
<span class="paren3">(<span class="code">setf ,destination ,source</span>)</span></span>)</span></span>)</span></span></code></pre>
<h3 id="s5-timers"><a href="index.html#s5-timers">Timers</a></h3>
<p>Next we'll need to decrement the timers at a rate of 60hz. We could do some
math to figure out the number of cycles between each and do this in the main
loop, but let's use a separate thread instead:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> run <span class="paren2">(<span class="code">rom-filename</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">chip <span class="paren5">(<span class="code">make-chip</span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code">setf <span class="special">*c*</span> chip</span>)</span>
<span class="paren3">(<span class="code">load-rom chip rom-filename</span>)</span>
<span class="paren3">(<span class="code">bt:make-thread <span class="paren4">(<span class="code">curry #'run-cpu chip</span>)</span></span>)</span>
<span class="paren3">(<span class="code">bt:make-thread <span class="paren4">(<span class="code">curry #'run-timers chip</span>)</span></span>)</span> <span class="comment">; NEW
</span> <span class="paren3">(<span class="code">chip8.gui.screen::run-gui chip</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Now we just need to define the <code>run-timers</code> function that will be run in that
separate thread:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> run-timers <span class="paren2">(<span class="code">chip</span>)</span>
<span class="paren2">(<span class="code">iterate
<span class="paren3">(<span class="code">while running</span>)</span>
<span class="paren3">(<span class="code">decrement-timers chip</span>)</span>
<span class="paren3">(<span class="code">sleep 1/60</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Simple enough. Technically this will run slightly <em>slower</em> than 60hz, because
it will take some time to actually decrement the timers, and our thread might
not get woken up exactly 1/60 of a second later.</p>
<p>We could try to fix this by sleeping for less or using a <a href="https://github.com/npatrick04/timer-wheel">timer wheel</a>, but
I think this is good enough for our needs here. We already have to suffer
through GC pauses anyway, so trying to get absolute precision is going to be
more trouble that it's worth.</p>
<p>Now to decrement the timers. The CPU thread might be executing a write
instruction as this thread is updating, so we'll use SBCL's atomic
compare-and-swap functionality to avoid losing decrements:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> decrement-timers <span class="paren2">(<span class="code">chip</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">flet</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">decrement <span class="paren5">(<span class="code">i</span>)</span>
<span class="paren5">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren6">(<span class="code">plusp i</span>)</span>
<span class="paren6">(<span class="code">1- i</span>)</span>
0</span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code"><i><span class="symbol">with-chip</span></i> <span class="paren4">(<span class="code">chip</span>)</span>
<span class="paren4">(<span class="code">sb-ext:atomic-update delay-timer #'decrement</span>)</span>
<span class="paren4">(<span class="code">sb-ext:atomic-update sound-timer #'decrement</span>)</span></span>)</span></span>)</span>
nil</span>)</span></span></code></pre>
<p>This is why we declared the timer slots in the <code>chip</code> struct to be <code>fixnum</code>
— the <a href="http://www.sbcl.org/manual/#Atomic-Operations">SBCL manual</a> states that for <code>(atomic-update place function ...)</code>:</p>
<blockquote>
<p>place can be any place supported by <code>sb-ext:compare-and-swap</code></p>
</blockquote>
<p>And that:</p>
<blockquote>
<p>Built-in CAS-able places are accessor forms whose car is one of the following:</p>
<p>...</p>
<p><strong>or the name of a defstruct created accessor for a slot whose declared type is
either fixnum or t. Results are unspecified if the slot has a declared type
other than fixnum or t.</strong></p>
</blockquote>
<p>(emphasis mine). Remember that <code>delay-timer</code> is <code>macrolet</code>ed to
<code>(chip-delay-timer ...)</code> by <code>with-chip</code>, so it does refer to a &quot;defstruct
created accessor&quot;.</p>
<p>We could have used a lock from bordeaux threads to manage this portably (though
more slowly), but I wanted to play around with SBCL's concurrency stuff.</p>
<h2 id="s6-sound-from-scratch"><a href="index.html#s6-sound-from-scratch">Sound From Scratch</a></h2>
<p>Now that we've got the timers all set up, all that's left is to play a sound
whenever <code>sound-timer</code> is positive. We could do this in a number of ways, for
example: loading a <code>WAV</code> file and looping it. But that's boring and almost
cheating, so let's do it from scratch.</p>
<p><a href="https://en.wikipedia.org/wiki/Sound">Sound</a> is a pretty complicated beast. For this CHIP-8 emulator we'll only
dip our toes into the water and work with the very basics. I'll explain some
basics here but will simplify and gloss over a lot — there are plenty of
resources online if you want to learn more.</p>
<p>For our purposes we'll think of &quot;sound&quot; as a pressure value over time. For
example, a simple sound wave might look something like this:</p>
<p><a href="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-basic.png"><img src="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-basic.png" alt="Graph of a basic sound wave"></a></p>
<p>The pressure starts at 0, gradually climbs until it hits 1, then falls and
gradually hits -1, then returns to 0 and starts the process over again.</p>
<p>The distance from the highest pressure value to the lowest is called the
&quot;amplitude&quot; of the wave (specifically &quot;peak-to-peak amplitude&quot;). For sound,
this is what determines how loud the sound is. For the CHIP-8 emulator we'll
always just be using pressure values between -1 and 1.</p>
<p>There are (infinitely) many different types of sound waves, each with their own
distinct character. Let's look at a few of them that might fit well with the
CHIP-8's retro aesthetic. <a href="http://public.wsu.edu/~jkrug/MUS364/audio/Waveforms.htm">This page</a> has some example audio files
so you can hear what they sound like.</p>
<h3 id="s7-sine-waves"><a href="index.html#s7-sine-waves">Sine Waves</a></h3>
<p>The wave I used as the example above is a <a href="https://en.wikipedia.org/wiki/Sine_wave">sine wave</a>. It's based on <a href="https://en.wikipedia.org/wiki/Sine">the
<code>sine</code> function</a> you might have learned about in trigonometry class.</p>
<p>We usually think of <code>sin</code> as taking an angle as an argument, not a time. But we
can convert time to an appropriate angle value later, so let's not get hung up
on that. Sine performs one complete &quot;wave&quot; in exactly <a href="https://www.youtube.com/watch?v=jG7vhMMXagQ">τ</a> radians:</p>
<p><a href="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-sine.png"><img src="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-sine.png" alt="Graph of a basic sine wave"></a></p>
<p>Common Lisp has this built in as the <code>sin</code> function, so we don't have any work
to do for this one.</p>
<h3 id="s8-square-waves"><a href="index.html#s8-square-waves">Square Waves</a></h3>
<p>The next wave we'll look at is the <a href="https://en.wikipedia.org/wiki/Square_wave">square wave</a>. Instead of gradually moving
between -1 and 1 over time like sine, it stays at 1 for half its wave then
immediately jumps straight to -1:</p>
<p><a href="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-square.png"><img src="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-square.png" alt="Graph of a basic square wave"></a></p>
<p>This &quot;jump&quot; gives the square wave kind of a &quot;buzzy&quot; character that you may have
heard before if you've played many old computer games (or like to listen to
chiptunes).</p>
<p>Common Lisp doesn't have this function built in, but we can make it pretty
easily. First we'll define some useful constants:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defconstant</span></i> +pi+ <span class="paren2">(<span class="code">coerce pi 'single-float</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defconstant</span></i> +tau+ <span class="paren2">(<span class="code">* 2 +pi+</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defconstant</span></i> +1/4tau+ <span class="paren2">(<span class="code">* 1/4 +tau+</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defconstant</span></i> +1/2tau+ <span class="paren2">(<span class="code">* 1/2 +tau+</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defconstant</span></i> +3/4tau+ <span class="paren2">(<span class="code">* 3/4 +tau+</span>)</span></span>)</span></span></code></pre>
<p>We're using single-floats because our audio library is going to want those
later.</p>
<p>Now we can define the square-wave function:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> sqr <span class="paren2">(<span class="code">angle</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren3">(<span class="code">&lt; <span class="paren4">(<span class="code">mod angle +tau+</span>)</span> +1/2tau+</span>)</span>
1.0
-1.0</span>)</span></span>)</span></span></code></pre>
<p>I've called it <code>sqr</code> because my utility library already has a function called
&quot;square&quot;, and I like that it's three letters like the <code>sin</code> function.</p>
<p>The implementation is pretty simple. We just have to make sure to <code>mod</code> the
angle by τ to make the results repeat properly, like this:</p>
<p><a href="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-square-repeat.png"><img src="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-square-repeat.png" alt="Graph of several square waves"></a></p>
<h3 id="s9-sawtooth-waves"><a href="index.html#s9-sawtooth-waves">Sawtooth Waves</a></h3>
<p>The <a href="https://en.wikipedia.org/wiki/Sawtooth_wave">sawtooth wave</a> is next up in our little menagerie of waveforms. The name
comes from what it looks like when you have a few in a row:</p>
<p><a href="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-saw-repeat.png"><img src="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-saw-repeat.png" alt="Graph of several sawtooth waves"></a></p>
<p>A single wave of it looks like this:</p>
<p><a href="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-saw.png"><img src="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-saw.png" alt="Graph of a basic sawtooth wave"></a></p>
<p>Sawtooth waves still have a bit of a &quot;buzzy&quot; feel to them because of the jump
halfway through their period, but unlike square waves they have <em>some</em> gradual
change, so they're often a nice happy medium.</p>
<p>To implement the <code>saw</code> function, notice that for the first half of the wave's
life (0 to τ) it goes from 0 to 1, and for the second half (½τ to τ) it goes
from -1 to 0. We'll also remember to <code>mod</code> by τ to proper repeating:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> saw <span class="paren2">(<span class="code">angle</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">a <span class="paren5">(<span class="code">mod angle +tau+</span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren4">(<span class="code">&lt; a +1/2tau+</span>)</span>
<span class="paren4">(<span class="code">map-range 0 +1/2tau+
0.0 1.0
a</span>)</span>
<span class="paren4">(<span class="code">map-range +1/2tau+ +tau+
-1.0 0.0
a</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p><a href="https://github.com/sjl/cl-losh/blob/master/DOCUMENTATION.markdown#map-range-function"><code>map-range</code></a> is a <em>really</em> handy function from my utility library.
I wish I could think of a better name for it. It's kind of like a <a href="https://en.wikipedia.org/wiki/Linear_interpolation">lerp</a>
function, but instead of assuming the input value is between 0 and 1 it allows
you to specify the input range.</p>
<p><code>map-range</code> takes five arguments:</p>
<pre><code>(map-range source-start source-end
dest-start dest-end
value)
</code></pre>
<p>I think of it as taking a source number line with a value on it, stretching that
number line to become the destination line, and finding the new location of the
value:</p>
<pre class="lineart">
2 3 4 5 6 2 3 4 5 6
━━◉━━━━━━━━━━ ━━━━━━━━━◎━━━
│ ╲ │ ╲
│ ╲ │ ╲
┌──┘ ╲ └───┐ ╲
│ ╲ │ ╲
▼ ╲ ▼ ╲
━━━━◉━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━◎━━━━━
0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8
</pre>
<h3 id="s10-triangle-waves"><a href="index.html#s10-triangle-waves">Triangle Waves</a></h3>
<p>Let's look at one more kind of wave before moving on: the <a href="https://en.wikipedia.org/wiki/Triangle_wave">triangle wave</a>. As
you might expect, this wave looks like a big triangle:</p>
<p><a href="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-tri.png"><img src="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-tri.png" alt="Graph of a basic triangle wave"></a></p>
<p>Triangle waves are closer to sine waves than square or sawtooth waves were, but
they've still got a bit of &quot;sharpness&quot; to them because they don't have that
gradual rounding off at the peak like sine waves.</p>
<p>Much like <code>saw</code>, we can define <code>tri</code> by defining each half of the wave
separately:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> tri <span class="paren2">(<span class="code">angle</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">a <span class="paren5">(<span class="code">mod angle +tau+</span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren4">(<span class="code">&lt; a +1/2tau+</span>)</span>
<span class="paren4">(<span class="code">map-range 0 +1/2tau+
-1.0 1.0
a</span>)</span>
<span class="paren4">(<span class="code">map-range +1/2tau+ +tau+
1.0 -1.0
a</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p>That's it for our whirlwind tour of simple sound waves.</p>
<h2 id="s11-playing-sound"><a href="index.html#s11-playing-sound">Playing Sound</a></h2>
<p>We've got four functions (<code>sin</code>, <code>sqr</code>, <code>saw</code>, and <code>tri</code>) that take in angles
and spit out pressure values between -1 and 1, so the next step is to somehow
use them to make the computer play sound.</p>
<p>We'll be using <a href="http://www.portaudio.com/">PortAudio</a> and <a href="https://filonenko-mikhail.github.io/cl-portaudio/">cl-portaudio</a> to handle the OS and sound
device interaction. If you're following along you'll need to install PortAudio
separately (before Quickloading <code>cl-portaudio</code>). On OS X you can do this with
<code>brew install portaudio</code>, for Linux use your distro's package manager.</p>
<h3 id="s12-sampling"><a href="index.html#s12-sampling">Sampling</a></h3>
<p>In the real world pressure and time vary continuously, but our computer handles
audio differently. Modern digital audio uses the concept of sampling to split
up the pressure-over-time graph into discrete pieces. Instead of trying to work
with an infinite number of times, we look at a finite number of individual
samples.</p>
<p>A &quot;sample&quot; is just a pressure value at a particular point in time. The number
of times per second the computer reads or writes a sample is called the &quot;<a href="http://wiki.audacityteam.org/wiki/Sample_Rates">sample
rate</a>&quot;. If the sampling rate is too low, we won't be able to tell much about
the original wave, and playing it would sound like noise:</p>
<p><a href="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-sample-sparse.png"><img src="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-sample-sparse.png" alt="Graph of a sparse sampling"></a></p>
<p>But a higher sample rate can get us nice and close to the original wave:</p>
<p><a href="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-sample-dense.png"><img src="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-sample-dense.png" alt="Graph of a dense sampling"></a></p>
<p>We'll stick with the most common sample rate, 44.1khz, because it's the most
widely supported (even though it's overkill for our simple waves):</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defconstant</span></i> +sample-rate+ 44100d0</span>)</span></span></code></pre>
<h3 id="s13-buffering"><a href="index.html#s13-buffering">Buffering</a></h3>
<p>It would be wasteful to keep constantly calling back and forth between our code
and PortAudio's code, so instead we'll be giving PortAudio a buffer of samples
to play which will effectively contain a &quot;chunk&quot; of sound. This buffer will be
a vanilla Lisp array of <code>single-float</code>s. The size of the buffer is
configurable, with larger buffers representing bigger chunks of sound.</p>
<p>There's a tradeoff between efficiency and responsiveness we
need to decide on. Larger buffers are more efficient (less switching back and
forth between us and PortAudio) but once we've sent a buffer it's going to play
to its end — we can't stop it midway. I've found <code>512</code> to be a happy medium:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defconstant</span></i> +audio-buffer-size+ 512
<span class="string">&quot;The number of samples in the audio buffer.&quot;</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defconstant</span></i> +audio-buffer-time+ <span class="paren2">(<span class="code">* +audio-buffer-size+ <span class="paren3">(<span class="code">/ +sample-rate+</span>)</span></span>)</span>
<span class="string">&quot;The total time the information in the audio buffer represents, in seconds.&quot;</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> make-audio-buffer <span class="paren2">(<span class="code"></span>)</span>
<span class="paren2">(<span class="code">make-array +audio-buffer-size+
<span class="keyword">:element-type</span> 'single-float
<span class="keyword">:initial-element</span> 0.0</span>)</span></span>)</span></span></code></pre>
<p>A 512-sample buffer with a sample rate of 44100 samples per second means that
each buffer will represent about 11.6 milliseconds of sound.</p>
<p>We're going to need a way to fill an audio buffer with sample values, so let's
make a <code>fill-buffer</code> function:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> fill-buffer <span class="paren2">(<span class="code">buffer <i><span class="symbol">function</span></i> rate start</span>)</span>
<span class="paren2">(<span class="code">iterate
<span class="paren3">(<span class="code">for i <span class="keyword">:index-of-vector</span> buffer</span>)</span>
<span class="paren3">(<span class="code">for angle <span class="keyword">:from</span> start <span class="keyword">:by</span> rate</span>)</span>
<span class="paren3">(<span class="code">setf <span class="paren4">(<span class="code">aref buffer i</span>)</span> <span class="paren4">(<span class="code">funcall <i><span class="symbol">function</span></i> angle</span>)</span></span>)</span>
<span class="paren3">(<span class="code">finally <span class="paren4">(<span class="code">return <span class="paren5">(<span class="code">mod angle +tau+</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p><code>fill-buffer</code> will take one of our four waveform functions and fill the given
buffer with samples generated by it. <code>rate</code> will be the rate at which the angle
should be incremented per-sample, which we'll figure out in a moment.</p>
<p>The one snag is that we can't just start from an angle of zero each time we fill
a buffer (unless our buffer size happens to exactly match the period of our
wave). If we <em>did</em> we'd only ever be sending the first chunk of our wave, and
we'd end up with something like:</p>
<p><a href="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-borked.png"><img src="http://stevelosh.com/static/images/blog/2016/12/chip8-sound-borked.png" alt="Graph of a shitty buffer filling strategy"></a></p>
<p>This is obviously not what we want. The solution is to return the angle we
ended at from <code>fill-buffer</code>, and then pass it in as <code>start</code> on the next round so
we can pick up where we left off.</p>
<h3 id="s14-configuration"><a href="index.html#s14-configuration">Configuration</a></h3>
<p>Since we've gone to the trouble of writing four separate wave functions, each
with their own character/timbre, let's make the sound the emulator plays
configurable. First we'll make some helper functions for filling the audio
buffer with a particular wave:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> fill-square <span class="paren2">(<span class="code">buffer rate start</span>)</span>
<span class="paren2">(<span class="code">fill-buffer buffer #'sqr rate start</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> fill-sine <span class="paren2">(<span class="code">buffer rate start</span>)</span>
<span class="paren2">(<span class="code">fill-buffer buffer #'sin rate start</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> fill-sawtooth <span class="paren2">(<span class="code">buffer rate start</span>)</span>
<span class="paren2">(<span class="code">fill-buffer buffer #'saw rate start</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> fill-triangle <span class="paren2">(<span class="code">buffer rate start</span>)</span>
<span class="paren2">(<span class="code">fill-buffer buffer #'tri rate start</span>)</span></span>)</span></span></code></pre>
<p>Then we'll add a slot to the <code>chip</code> struct:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defstruct</span></i> chip
<span class="comment">; ...
</span> <span class="paren2">(<span class="code">sound-type <span class="keyword">:sine</span> <span class="keyword">:type</span> keyword</span>)</span>
<span class="comment">; ...
</span> </span>)</span></span></code></pre>
<p>And we'll make a helper function for retrieving the appropriate buffer-filling
function:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> audio-buffer-filler <span class="paren2">(<span class="code">chip</span>)</span>
<span class="paren2">(<span class="code">ecase <span class="paren3">(<span class="code">chip-sound-type chip</span>)</span>
<span class="paren3">(<span class="code"><span class="keyword">:square</span> #'fill-square</span>)</span>
<span class="paren3">(<span class="code"><span class="keyword">:sine</span> #'fill-sine</span>)</span>
<span class="paren3">(<span class="code"><span class="keyword">:sawtooth</span> #'fill-sawtooth</span>)</span>
<span class="paren3">(<span class="code"><span class="keyword">:triangle</span> #'fill-triangle</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>We'll use <code>ecase</code> instead of vanilla <code>case</code> so we get a nicer error message if
the slot is set to something incorrect. I actually find myself reaching for
<code>ecase</code> more often than <code>case</code> these days because a silent <code>nil</code> result is
almost never what I want.</p>
<h3 id="s15-angle-rate-frequency"><a href="index.html#s15-angle-rate-frequency">Angle Rate &amp; Frequency</a></h3>
<p>One last bit we need to figure out is how much to increment the angle for each
sample.</p>
<p>All four of our wave functions assume that one wave happens in exactly
τ radians. So if we assume that we want one wave per second, and there are
<code>+sample-rate+</code> samples in every second, we'd just use <code>(/ +tau+ +sample-rate+)</code>
to get the angle increment.</p>
<p>But one wave per second is below the range of human hearing. We want something
more like 440 waves per second (the &quot;frequency&quot; of the note <a href="https://en.wikipedia.org/wiki/A440_(pitch_standard)">A440</a>), so our
function to find the angle increment will looks like this:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> audio-rate <span class="paren2">(<span class="code">frequency</span>)</span>
<span class="paren2">(<span class="code">coerce <span class="paren3">(<span class="code">* <span class="paren4">(<span class="code">/ +tau+ +sample-rate+</span>)</span> frequency</span>)</span> 'single-float</span>)</span></span>)</span></span></code></pre>
<p>We coerce it to a <code>single-float</code> here to avoid having to do it on every addition
later.</p>
<h3 id="s16-running-the-sound-loop"><a href="index.html#s16-running-the-sound-loop">Running the Sound Loop</a></h3>
<p>We've now got all the bits and pieces we need to make some noise. Let's build
a <code>run-sound</code> function bit by bit. First we initialize the output stream with
PortAudio:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> run-sound <span class="paren2">(<span class="code">chip</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">portaudio:with-audio</span></i>
<span class="paren3">(<span class="code"><i><span class="symbol">portaudio:with-default-audio-stream</span></i>
<span class="paren4">(<span class="code">audio-stream 0 1
<span class="keyword">:sample-format</span> <span class="keyword">:float</span>
<span class="keyword">:sample-rate</span> +sample-rate+
<span class="keyword">:frames-per-buffer</span> +audio-buffer-size+</span>)</span>
<span class="comment">; ...
</span> </span>)</span></span>)</span>
nil</span>)</span></span></code></pre>
<p>The <code>0 1</code> arguments mean &quot;zero input channels&quot; (we don't need access to the
microphone!) and &quot;one output channel&quot;.</p>
<p>Now we'll add our sound loop:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> run-sound <span class="paren2">(<span class="code">chip</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">portaudio:with-audio</span></i>
<span class="paren3">(<span class="code"><i><span class="symbol">portaudio:with-default-audio-stream</span></i>
<span class="paren4">(<span class="code">audio-stream 0 1
<span class="keyword">:sample-format</span> <span class="keyword">:float</span>
<span class="keyword">:sample-rate</span> +sample-rate+
<span class="keyword">:frames-per-buffer</span> +audio-buffer-size+</span>)</span>
<span class="paren4">(<span class="code"><i><span class="symbol">with-chip</span></i> <span class="paren5">(<span class="code">chip</span>)</span> <span class="comment">; NEW
</span> <span class="paren5">(<span class="code">iterate <span class="paren6">(<span class="code">with buffer = <span class="paren1">(<span class="code">make-audio-buffer</span>)</span></span>)</span> <span class="comment">; NEW
</span> <span class="paren6">(<span class="code">with angle = 0.0</span>)</span> <span class="comment">; NEW
</span> <span class="paren6">(<span class="code">with rate = <span class="paren1">(<span class="code">audio-rate 440</span>)</span></span>)</span> <span class="comment">; NEW
</span> <span class="paren6">(<span class="code">while running</span>)</span> <span class="comment">; NEW
</span> <span class="paren6">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren1">(<span class="code">plusp sound-timer</span>)</span> <span class="comment">; NEW
</span> <span class="comment">; ... ; NEW
</span> <span class="paren1">(<span class="code">sleep +audio-buffer-time+</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>)</span> <span class="comment">; NEW
</span> nil</span>)</span></span></code></pre>
<p>We create a buffer and some extra variables, then we check if the sound timer is
positive. If <em>not</em>, we just sleep for a while. I decided to sleep for the same
amount of time as a single audio buffer would take so that each iteration of the
loop represents roughly the same slice of time, but this isn't strictly
necessary.</p>
<p>If the sound timer <em>is</em> positive we'll need to fill the buffer with pressure
values and ship it off to PortAudio:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> run-sound <span class="paren2">(<span class="code">chip</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">portaudio:with-audio</span></i>
<span class="paren3">(<span class="code"><i><span class="symbol">portaudio:with-default-audio-stream</span></i>
<span class="paren4">(<span class="code">audio-stream 0 1
<span class="keyword">:sample-format</span> <span class="keyword">:float</span>
<span class="keyword">:sample-rate</span> +sample-rate+
<span class="keyword">:frames-per-buffer</span> +audio-buffer-size+</span>)</span>
<span class="paren4">(<span class="code"><i><span class="symbol">with-chip</span></i> <span class="paren5">(<span class="code">chip</span>)</span>
<span class="paren5">(<span class="code">iterate <span class="paren6">(<span class="code">with buffer = <span class="paren1">(<span class="code">make-audio-buffer</span>)</span></span>)</span>
<span class="paren6">(<span class="code">with angle = 0.0</span>)</span>
<span class="paren6">(<span class="code">with rate = <span class="paren1">(<span class="code">audio-rate 440</span>)</span></span>)</span>
<span class="paren6">(<span class="code">while running</span>)</span>
<span class="paren6">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren1">(<span class="code">plusp sound-timer</span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">progn</span></i> <span class="comment">; NEW
</span> <span class="paren2">(<span class="code">setf angle <span class="paren3">(<span class="code">funcall <span class="paren4">(<span class="code">audio-buffer-filler chip</span>)</span> <span class="comment">; NEW
</span> buffer rate angle</span>)</span></span>)</span> <span class="comment">; NEW
</span> <span class="paren2">(<span class="code">portaudio:write-stream audio-stream buffer</span>)</span></span>)</span> <span class="comment">; NEW
</span> <span class="paren1">(<span class="code">sleep +audio-buffer-time+</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>)</span>
nil</span>)</span></span></code></pre>
<p>Pretty simple: just fill the buffer and ship it. We keep track of the angle the
buffer-filler returned so we can pass it in on the next iteration to avoid the
&quot;truncated waves&quot; problem we talked about earlier.</p>
<h3 id="s17-threading-issues"><a href="index.html#s17-threading-issues">Threading Issues</a></h3>
<p>In a perfect world we could just add one more thread like we did with the others
and be done with it:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> run <span class="paren2">(<span class="code">rom-filename</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">chip <span class="paren5">(<span class="code">make-chip</span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code">setf <span class="special">*c*</span> chip</span>)</span>
<span class="paren3">(<span class="code">load-rom chip rom-filename</span>)</span>
<span class="paren3">(<span class="code">bt:make-thread <span class="paren4">(<span class="code">curry #'run-cpu chip</span>)</span></span>)</span>
<span class="paren3">(<span class="code">bt:make-thread <span class="paren4">(<span class="code">curry #'run-timers chip</span>)</span></span>)</span>
<span class="paren3">(<span class="code">bt:make-thread <span class="paren4">(<span class="code">curry #'run-sound chip</span>)</span></span>)</span> <span class="comment">; NEW
</span> <span class="paren3">(<span class="code">chip8.gui.screen::run-gui chip</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Unfortunately there's one more snag we need to deal with. It turns out that Qt
becomes very unhappy if we set up our threads this way. I'm not 100% sure what
the problem is, but it has something to do with control of the main thread on
OS X.</p>
<p>The solution is to let Qt take control of the main thread, and spawn all our
other threads from there. We'll update <code>run-gui</code> to take a thunk and call it
once it's ready:</p>
<pre><code><span class="code"> <span class="comment">; NEW
</span><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> run-gui <span class="paren2">(<span class="code">chip thunk</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-main-window</span></i>
<span class="paren3">(<span class="code">window <span class="paren4">(<span class="code">make-screen chip</span>)</span></span>)</span>
<span class="paren3">(<span class="code">funcall thunk</span>)</span></span>)</span></span>)</span> <span class="comment">; NEW</span></span></code></pre>
<p>And now we can move all the thread spawning into the thunk:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> run <span class="paren2">(<span class="code">rom-filename &amp;key start-paused</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">chip <span class="paren5">(<span class="code">make-chip</span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code">setf <span class="special">*c*</span> chip</span>)</span>
<span class="paren3">(<span class="code">load-rom chip rom-filename</span>)</span>
<span class="paren3">(<span class="code">chip8.gui.screen::run-gui chip
<span class="paren4">(<span class="code"><i><span class="symbol">lambda</span></i> <span class="paren5">(<span class="code"></span>)</span> <span class="comment">; NEW
</span> <span class="paren5">(<span class="code">bt:make-thread <span class="paren6">(<span class="code">curry #'run-cpu chip</span>)</span></span>)</span> <span class="comment">; NEW
</span> <span class="paren5">(<span class="code">bt:make-thread <span class="paren6">(<span class="code">curry #'run-timers chip</span>)</span></span>)</span> <span class="comment">; NEW
</span> <span class="paren5">(<span class="code">bt:make-thread <span class="paren6">(<span class="code">curry #'run-sound chip</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>)</span> <span class="comment">; NEW</span></span></code></pre>
<p>Technically only the sound thread needs to be handled this way, but I figured
I'd treat them all the same.</p>
<h2 id="s18-result"><a href="index.html#s18-result">Result</a></h2>
<p>That's it! Now you can play games and should get a nice loud buzz when the
sound should fire. <code>ufo.rom</code> is a good game to test it out with — it should
buzz whenever you fire and whenever a ship gets hit. Turn down your speakers if
you don't want to scare the cat.</p>
<p>The sound type can be changed on the fly (e.g. <code>(setf (chip-sound-type *c*)
:sawtooth)</code>), so play around and decide which type is your favorite. I'm
partial to sawtooth myself.</p>
<h2 id="s19-future"><a href="index.html#s19-future">Future</a></h2>
<p>With that we've got a full-featured CHIP-8 emulator! It works, but there's
still work left to be done. In the next few posts we'll look at things like:</p>
<ul>
<li>A menu system for runtime configuration</li>
<li>Disassembling/debugging infrastructure</li>
<li>A graphical debugger</li>
</ul>
<p><em>Thanks to <a href="https://twitter.com/joekarl">Joe Karl</a> for reading a draft of this
post.</em></p>
</article></main><hr class='main-separator' /><footer><nav><a href='https://github.com/sjl/'>GitHub</a><a href='https://twitter.com/stevelosh/'>Twitter</a><a href='https://instagram.com/thirtytwobirds/'>Instagram</a><a href='https://hg.stevelosh.com/.plan/'>.plan</a></nav></footer></body></html>