<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Get Info: #audio</title>
    <description>Posts tagged “audio” — Blog of independent game and app developer Matt Sephton. Featuring vintage Macintosh, game development, digital artwork, Japanese esoterica, video game reviews, hacks and tips, and much more.</description>
    <link>https://blog.gingerbeardman.com/tag/audio/</link>
    <atom:link href="https://blog.gingerbeardman.com/tag/audio/index.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sat, 11 Jul 2026 16:22:12 +0000</pubDate>
    <lastBuildDate>Sat, 11 Jul 2026 16:22:12 +0000</lastBuildDate>
    <generator>Jekyll v4.4.1</generator>

    
      
        <item>
          <title>Enhanced sfxr for Love2D</title>
          <description>&lt;p&gt;I’ve made a bunch of usability and quality of life changes to the &lt;a href=&quot;https://love2d.org/wiki/sfxr.lua&quot;&gt;sfxr.lua&lt;/a&gt; demo app.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;fix: getDirectoryItems (from a PR)&lt;/li&gt;
  &lt;li&gt;fix: off by one sample length error (from a PR)&lt;/li&gt;
  &lt;li&gt;fix: space bar was not playing the sound&lt;/li&gt;
  &lt;li&gt;fix: selected wave form was being ignored (sounds were always square wave)&lt;/li&gt;
  &lt;li&gt;fix: loaded wave form not updating the interface (but sound was playing correctly)&lt;/li&gt;
  &lt;li&gt;add: clone method added to sfxr.lua&lt;/li&gt;
  &lt;li&gt;add: history form with list of previous sounds, save current, and undo/redo&lt;/li&gt;
  &lt;li&gt;add: “play on changes” so that any time you adjust a sound it plays automatically&lt;/li&gt;
  &lt;li&gt;add: keyboard navigation in file picker (A–Z: jump to files, Enter: choose, Esc: close picker)&lt;/li&gt;
  &lt;li&gt;add: window title reflects the most recent loaded/saved file&lt;/li&gt;
  &lt;li&gt;add: window title and filename reflects last operation&lt;/li&gt;
  &lt;li&gt;add: file selector shows data directory path&lt;/li&gt;
  &lt;li&gt;add: unified save directory across platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can get it at: &lt;a href=&quot;https://gingerbeardman.itch.io/enhanced-sfxr-for-love2d&quot;&gt;gingerbeardman.itch.io/enhanced-sfxr-for-love2d&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At some point I’ll fork the project on GitHub and file some PRs with my changes, but the repo for it seems very dead so I’ll wait until I hear back from the owner. Source code is at the above link.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/sfxr-lua.png&quot; alt=&quot;IMG&quot; title=&quot;“Enhance!”&quot; /&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Tue, 11 Mar 2025 18:23:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2025/03/11/enhanced-sfxr-for-love2d/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2025/03/11/enhanced-sfxr-for-love2d/</guid>
        </item>
      
    
      
        <item>
          <title>Dynamic music and sound techniques for video games</title>
          <description>&lt;p&gt;The only aspect of game development I’ve not attempted myself is the music. I mostly use royalty free music of Japanese origin (just because I dig their vibe, man) as in the case of &lt;a href=&quot;https://soundcloud.com/mac-vogelsang/sets/sparrow-solitaire&quot;&gt;&lt;em&gt;Sparrow Solitaire&lt;/em&gt;&lt;/a&gt; or &lt;a href=&quot;https://twitter.com/gingerbeardman/status/1732555533863751691&quot;&gt;&lt;em&gt;Fore! Track&lt;/em&gt;&lt;/a&gt; or in rare cases I pay friends (like the amazing Jamie Hamshere) to write music specifically for a game as in the case of &lt;a href=&quot;https://soundcloud.com/gingerbeardman/sets/yoyozo-soundtrack&quot;&gt;&lt;em&gt;YOYOZO&lt;/em&gt;&lt;/a&gt;. Maybe one day that will change, but until then I’m enjoying gaining more understanding and control of the music in my games. Whilst I develop games for &lt;a href=&quot;https://play.date&quot;&gt;Playdate&lt;/a&gt; these techniques are general enough to apply anywhere.&lt;/p&gt;

&lt;p&gt;The main way I make the music into more than a static track is to apply a dynamic, reactive, or adaptive effect in one way or another. In this blog post I’ll go into how I’ve achieved this. Please note this is by no means an exhaustive list, rather it’s just the ones I have personally used.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;dynamic-bpm&quot;&gt;Dynamic BPM&lt;/h2&gt;

&lt;p&gt;I use this method in &lt;a href=&quot;https://play.date/games/yoyozo/&quot;&gt;&lt;em&gt;YOYOZO&lt;/em&gt;&lt;/a&gt; because it uses “chip tune” music data representing songs composed by my friend Jamie Hamshere using &lt;a href=&quot;https://play.date/pulp/&quot;&gt;&lt;em&gt;Playdate Pulp&lt;/em&gt;&lt;/a&gt;. A playback engine for this data, written by Pulp creator Shaun Inman, works beautifully when integrated into games written using Lua and the &lt;a href=&quot;https://play.date/dev/&quot;&gt;&lt;em&gt;Playdate SDK&lt;/em&gt;&lt;/a&gt;. I added hook to allow me to set the BPM at any point to any value. The end result is that the BPM of the music scales from 130 to 135 as your score increases. As you improve at the game you’ll notice the music speed up ever so slightly along with an increase in tension and anxiety.&lt;/p&gt;

&lt;iframe width=&quot;100%&quot; height=&quot;140&quot; scrolling=&quot;no&quot; frameborder=&quot;no&quot; allow=&quot;autoplay&quot; src=&quot;https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/1685873466&amp;amp;color=%23ff5500&amp;amp;auto_play=false&amp;amp;hide_related=false&amp;amp;show_comments=true&amp;amp;show_user=true&amp;amp;show_reposts=false&amp;amp;show_teaser=true&amp;amp;visual=true&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;Of course, it’s possible to do this in a pre-recorded song stored as a digital music file, but it’s much more difficult for that to respond to the what the player does in the game. An example that takes an interesting approach to this is the track &lt;a href=&quot;https://www.youtube.com/watch?v=1_iZh_2li4M&quot;&gt;“Sunny Day” from the game Vib Ribbon&lt;/a&gt;, and indeed the rest of its soundtrack, where tempo changes over the duration of each song.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;infinite-variations&quot;&gt;Infinite Variations&lt;/h2&gt;

&lt;p&gt;Another technique I use in &lt;a href=&quot;https://play.date/games/yoyozo/&quot;&gt;&lt;em&gt;YOYOZO&lt;/em&gt;&lt;/a&gt;, again made possible because I can modify the music data and playback parameters in real time. With this one I cycle the values for the instrument voices pseudo-randomly so that the track plays once as it was programmed and then morphs slightly for each subsequent playback. The track is quite minimal and repetitive in &lt;a href=&quot;https://sites.barbican.org.uk/reichglassadams/&quot;&gt;Steve Reich, Philip Glass or John Adams&lt;/a&gt; sort of way, so there are automated variations wandering around the original arrangement work really well. Perhaps the &lt;a href=&quot;https://www.youtube.com/watch?v=NkBXgcN3fXo&quot;&gt;ultimate implementation of this approach is &lt;em&gt;Wii Play’s&lt;/em&gt; Tanks game&lt;/a&gt;.&lt;/p&gt;

&lt;iframe width=&quot;100%&quot; height=&quot;140&quot; scrolling=&quot;no&quot; frameborder=&quot;no&quot; allow=&quot;autoplay&quot; src=&quot;https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/1685873439&amp;amp;color=%23ff5500&amp;amp;auto_play=false&amp;amp;hide_related=false&amp;amp;show_comments=true&amp;amp;show_user=true&amp;amp;show_reposts=false&amp;amp;show_teaser=true&amp;amp;visual=true&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;For sound effects, I vary the playback sample rate to change the pitch of sound effects. This prevents the same sound effect becoming monotonous. Two examples might be &lt;em&gt;Lara Croft&lt;/em&gt; in the first &lt;em&gt;Tomb Raider&lt;/em&gt; game, &lt;a href=&quot;https://youtu.be/Roi2UelYGsU?si=_17TmHon5JenRxCM&amp;amp;t=1079&quot;&gt;groaning the same way every time she climbs up a platform&lt;/a&gt; compared with &lt;a href=&quot;https://www.youtube.com/watch?v=JGQeQmUuMas&quot;&gt;the rich variety of sounds when &lt;em&gt;Mario&lt;/em&gt; walks on different surfaces&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;blendingfadingbalance&quot;&gt;Blending/Fading/Balance&lt;/h2&gt;

&lt;p&gt;Another idea I had was to fade or blend two tracks as the player makes progress in the game. But how to find two tracks that can be cross-faded in a way that always makes sense? Of course you can have them composed, but what about in music that already exists? If only there was an easy way to find such tracks!&lt;/p&gt;

&lt;p&gt;There is: stereo pairs! You’d be surprised at how different the left and right channels can sound whilst obviously being the same tune. Of course this means that are output audio will be mono but for me on Playdate that’s just fine. I use this method in &lt;a href=&quot;https://play.date/games/fore-track/&quot;&gt;&lt;em&gt;Fore! Track&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The idea is to adjust the balance of the two parts of the audio, at once point you’re playing just the left audio across both outputs, then you adjust the balance to play a mix of both, at the other end of the scale you’d be playing just the right audio. Unfortunately the Playdate SDK currently has no API to easily adjust balance, so I had to program a method myself. First, I convert to the destination format which is for me ADPCM using &lt;a href=&quot;https://github.com/dbry/adpcm-xq&quot;&gt;adpcm-xq&lt;/a&gt;. Once the files are in this format I split the stereo pair into two files, one for the left channel and one for the right channel. Converting to the destination format before splitting ensures that the two are exactly the same length in terms of samples/bytes.&lt;/p&gt;

&lt;p&gt;In the game I fade between the two as the score/chain increases, which has the effect of subtly changing the instrumentation of the tune. It’s one of those things that most people wouldn’t notice, but that once you know about it you can’t miss it. Sadly, I don’t have an easy way to demo this in a video or sound file.&lt;/p&gt;

&lt;p&gt;For sound effects, you might consider panning to increase immersion and guide the players visual focus through use of audio. In &lt;a href=&quot;https://play.date/games/yoyozo/&quot;&gt;&lt;em&gt;YOYOZO&lt;/em&gt;&lt;/a&gt; I pan certain sounds relative to the location of the ball, certain other sounds relative to the location of the player, and there are global sound effects that are not panned at all.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/yoyozo-teaser.gif#playdate&quot; alt=&quot;YOYOZO&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;progressive-loops&quot;&gt;Progressive Loops&lt;/h2&gt;

&lt;p&gt;Digital audio is a different beast. I always try to find a time that fits the game, going so far as to audition many hundreds of tracks and creating playlist of songs far ahead of ever making a game or even having and idea for a game. I try to find tracks that will loop well and not get annoying, which is easier said than done. If I can’t find a track that loops well, there’s another way.&lt;/p&gt;

&lt;p&gt;You can use &lt;a href=&quot;https://github.com/arkrow/PyMusicLooper&quot;&gt;PyMusicLooper&lt;/a&gt; to analyse a digital audio track and spit out information about ranges that loop nicely, along with a percentage indicating how good it considers the loop. In other words you can identify and extract a loop from digital audio files that sound like they could loop. Of course, can’t identify loops in tracks that aren’t repetitive or consistent in their structure.  You might get PyMusicLooper to split the file into into three sections (intro, loop, outro) or just export the loop information as a text file to use in your game. Which I choose depends on how much of the file I want to use.&lt;/p&gt;

&lt;p&gt;For an example, in my game &lt;a href=&quot;https://play.date/games/icarus&quot;&gt;Super ICARUS&lt;/a&gt; I’m using a file that gives the vibe I wanted in the game and sounded like it contained some loops even though it was not provided as a looping song. PyMusicLooper reported that it contains tens of possible loops of varying quality.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;loop&lt;/th&gt;
      &lt;th&gt;start&lt;/th&gt;
      &lt;th&gt;end&lt;/th&gt;
      &lt;th&gt;duration&lt;/th&gt;
      &lt;th&gt;match&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;3.437&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;30.755&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;27.318&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;94.87%&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;0.023&lt;/td&gt;
      &lt;td&gt;27.341&lt;/td&gt;
      &lt;td&gt;27.318&lt;/td&gt;
      &lt;td&gt;94.79%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;7.279&lt;/td&gt;
      &lt;td&gt;34.598&lt;/td&gt;
      &lt;td&gt;27.319&lt;/td&gt;
      &lt;td&gt;94.63%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;6.850&lt;/td&gt;
      &lt;td&gt;34.168&lt;/td&gt;
      &lt;td&gt;27.318&lt;/td&gt;
      &lt;td&gt;93.95%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt;8.127&lt;/td&gt;
      &lt;td&gt;35.445&lt;/td&gt;
      &lt;td&gt;27.318&lt;/td&gt;
      &lt;td&gt;93.92%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;5&lt;/td&gt;
      &lt;td&gt;22.221&lt;/td&gt;
      &lt;td&gt;52.953&lt;/td&gt;
      &lt;td&gt;30.732&lt;/td&gt;
      &lt;td&gt;93.80%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;6&lt;/td&gt;
      &lt;td&gt;25.635&lt;/td&gt;
      &lt;td&gt;56.366&lt;/td&gt;
      &lt;td&gt;30.731&lt;/td&gt;
      &lt;td&gt;93.23%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;7&lt;/td&gt;
      &lt;td&gt;11.970&lt;/td&gt;
      &lt;td&gt;39.288&lt;/td&gt;
      &lt;td&gt;27.318&lt;/td&gt;
      &lt;td&gt;93.17%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;8&lt;/td&gt;
      &lt;td&gt;6.850&lt;/td&gt;
      &lt;td&gt;35.875&lt;/td&gt;
      &lt;td&gt;29.025&lt;/td&gt;
      &lt;td&gt;92.13%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;9&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;6.850&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;54.660&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;47.810&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;91.98%&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10&lt;/td&gt;
      &lt;td&gt;20.515&lt;/td&gt;
      &lt;td&gt;52.953&lt;/td&gt;
      &lt;td&gt;32.438&lt;/td&gt;
      &lt;td&gt;91.54%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;11&lt;/td&gt;
      &lt;td&gt;10.263&lt;/td&gt;
      &lt;td&gt;37.581&lt;/td&gt;
      &lt;td&gt;27.318&lt;/td&gt;
      &lt;td&gt;91.26%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;12&lt;/td&gt;
      &lt;td&gt;22.221&lt;/td&gt;
      &lt;td&gt;54.660&lt;/td&gt;
      &lt;td&gt;32.439&lt;/td&gt;
      &lt;td&gt;91.11%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;13&lt;/td&gt;
      &lt;td&gt;23.928&lt;/td&gt;
      &lt;td&gt;68.325&lt;/td&gt;
      &lt;td&gt;44.397&lt;/td&gt;
      &lt;td&gt;90.92%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;14&lt;/td&gt;
      &lt;td&gt;39.300&lt;/td&gt;
      &lt;td&gt;70.031&lt;/td&gt;
      &lt;td&gt;30.731&lt;/td&gt;
      &lt;td&gt;90.82%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;12.399&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;70.449&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;58.050&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;90.78%&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;My goal was to find three loops of increasing length and with a high percentage loop quality. After some experimentation and listening, I decided on loops 0, 9, and 15 (table only shows the top 15 loops from this track, even though their percentage loop match are not 100% they still sound like good loops, so selecting these loops was a case of finding three of suitable length and content. PyMusicLooper will let you audition the loops directly, so there’s no need to use an audio editor.&lt;/p&gt;

&lt;p&gt;Using the Playdate SDK I can do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setRange()&lt;/code&gt; on the audio track to change the playback range and the music will loop between those new points when the playhead reaches the end of the range. For this reason, this method does not provide immediate results so is better used to signify a large change in progress as the delay until the change is noticed will be an unknown amount of time. But when the change does kick in it’s a really nice surprise!&lt;/p&gt;

&lt;p&gt;The final result sees the game start by playing loop 1 (synth and drums) and then as the player gets makes some good progress I switch to loop 2 (synth, drums, guitar licks), and finally as they pass a certain threshold I switch to loop 3 (synth, drums, guitar licks into guitar solo). This provides music that sounds very dynamic with little effort. You could even drop back to the shorter loops if the player lost a life, missed a target, and so on. Again, there’s no real way of me demoing this as it’s something that will become apparent through play, and the final result is just one long dynamic song!&lt;/p&gt;

&lt;p&gt;For sound effects I use the same approach as above. As an example, in &lt;a href=&quot;https://play.date/games/fore-track/&quot;&gt;&lt;em&gt;Fore! Track&lt;/em&gt;&lt;/a&gt; there is a clapping sound effect after the player gets the ball in a hole. This is a long sound effect but I play three increasingly long sections of it as the player’s chain increases (number of successive holes-in-one). It starts off as a short clap, increases to a longer more enthusiastic clap, and finally it begins with a whoop and continues to enthusiastic clap. I have a separate sound effect for the end game cheer that plays over the top of the full clap, resulting in a raucous end of game celebration.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;quantised-sounds&quot;&gt;Quantised Sounds&lt;/h2&gt;

&lt;p&gt;In &lt;a href=&quot;https://play.date/games/icarus&quot;&gt;Super ICARUS&lt;/a&gt; created certain game event sounds from small sections of the music track. I then adjust playback rate/speed/pitch. The result is that the sounds appear to be quantised or matched to the music. I’m not sure how to describe this phenomenon accurately in musical/technical terms.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I’d love to hear about other methods of achieving dynamic music and sound in video games. Feel free to reach out to me on social media!&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;further-reading&quot;&gt;Further reading&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2023/11/21/yoyozo-how-i-made-a-playdate-game-in-39kb/&quot;&gt;&lt;em&gt;YOYOZO&lt;/em&gt; (or, how I made a Playdate game in 39KiB)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2023/11/26/easter-egg-emoji-converting-pixels-into-particles/&quot;&gt;Easter egg emoji: converting pixels into particles&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2023/12/09/dynamic-music-and-sound-techniques-for-video-games/&quot;&gt;Dynamic music and sound techniques for video games&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;elsewhere&quot;&gt;Elsewhere&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;2023-11-22—&lt;a href=&quot;https://news.ycombinator.com/item?id=38584336&quot;&gt;Hacker News&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;2023-11-22—&lt;a href=&quot;https://tildes.net/~games/1crg/dynamic_music_and_sound_techniques_for_video_games&quot;&gt;Tildes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 09 Dec 2023 01:22:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2023/12/09/dynamic-music-and-sound-techniques-for-video-games/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2023/12/09/dynamic-music-and-sound-techniques-for-video-games/</guid>
        </item>
      
    
      
        <item>
          <title>Automatically classifying the content of sound files using ML</title>
          <description>&lt;p&gt;Following on from yesterday’s &lt;a href=&quot;/2023/08/12/extracting-sounds-from-macromedia-director-files/&quot;&gt;extraction of old sound effects&lt;/a&gt;, I quickly realised I needed an easier way to search them as they came out of Director as unlabelled, numbered files. I can use QuickLook or a media player to quickly audition them, but how could I easily find the sample that contains the sound of running water or a horse trotting?&lt;/p&gt;

&lt;p&gt;I wondered if there was a way of using Machine Learning (ML) to automatically categorise sounds. It seemed like something that should be possible, especially given the recent explosion in “AI” (really: ML) tools. I quickly found Google’s AudioSet, which sounded like the perfect dataset:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;AudioSet consists of an expanding ontology of 632 audio event classes and a collection of 2,084,320 human-labeled 10-second sound clips drawn from YouTube videos. The ontology is specified as a hierarchical graph of event categories, covering a wide range of human and animal sounds, musical instruments and genres, and common everyday environmental sounds.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But the data set is only one half of the solution. You need to use the dataset to create a model and then run that model against your own data to get the required results. Thankfully, I found YAMNet:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;YAMNet is a deep net that predicts ~521 audio event classes from the AudioSet-YouTube corpus it was trained on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I guess YAMNet is tracking behind AudioSet in terms of total categories, but it is good enough for me. Here is a &lt;a href=&quot;https://github.com/tensorflow/models/blob/master/research/audioset/yamnet/yamnet_class_map.csv&quot;&gt;list of all the classes&lt;/a&gt; of sounds it can recognise.&lt;/p&gt;

&lt;h2 id=&quot;lets-go&quot;&gt;Let’s go&lt;/h2&gt;

&lt;p&gt;I used the script described in &lt;a href=&quot;https://www.tensorflow.org/hub/tutorials/yamnet&quot;&gt;this tutorial&lt;/a&gt; as a starting point. I’m not a regular python user but using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pip&lt;/code&gt; to &lt;a href=&quot;https://www.tensorflow.org/install&quot;&gt;install tensorflow&lt;/a&gt;, along with any other missing imports, and after that it …just worked.&lt;/p&gt;

&lt;h2 id=&quot;getting-your-files-in-order&quot;&gt;Getting your files in order&lt;/h2&gt;

&lt;p&gt;According to the documentation all sound files need to be at a sample rate of 16000Hz. After getting some calssification results of “Silence”, I realised they also need to be 16-bit resolution. So I ran a quick &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sox&lt;/code&gt; command to create compliant copies of all my sounds. I’ll delete these when I’m done. Notice how I decided to trim sounds to a maximum length of 3 seconds. This helps speed things up and most sounds can still be recognised with such a short starting section.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;find &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-iname&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;*.wav&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-exec&lt;/span&gt; sox &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; 1 &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; 16000 &lt;span class=&quot;nt&quot;&gt;-b&lt;/span&gt; 16 &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt;_16k.wav trim 0 00:03 &lt;span class=&quot;se&quot;&gt;\;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;optimisation&quot;&gt;Optimisation&lt;/h2&gt;

&lt;p&gt;Running the classifier works at about real-time, a few seconds per sound, but I noticed that it was leaving a lot of my CPU unused. This struck me as a prime candidate for parallelisation, which is pretty easy on the command line. I used the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parallel&lt;/code&gt; command to scale up the classification to use all 10-cores of the M1 Pro CPU in my 2021 MacBook Pro.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;find &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-iname&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;*.wav&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-exec&lt;/span&gt; parallel python3 classify.py &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt; ::: &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As I type my computer is making short order of the task, whilst remaining perfectly responsive, if a little warm. Final speed for me is one sound every ~0.85 seconds.&lt;/p&gt;

&lt;h2 id=&quot;python-script&quot;&gt;Python Script&lt;/h2&gt;

&lt;noscript&gt;&lt;p&gt;&lt;a href=&quot;https://gist.github.com/gingerbeardman/9e9bde623673ed2f50aeb15e97aae4a3&quot;&gt;View the source code as a Gist&lt;/a&gt;&lt;/p&gt;&lt;/noscript&gt;
&lt;script src=&quot;https://gist.github.com/gingerbeardman/9e9bde623673ed2f50aeb15e97aae4a3.js&quot;&gt;&lt;/script&gt;

&lt;h2 id=&quot;creating-my-sfx-library&quot;&gt;Creating my SFX Library&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://getsoundly.com&quot;&gt;Soundly&lt;/a&gt; is a sort of iTunes for sound effects. It’s an app that enables easy, automatic organisation of files, quick searching of metadata, painless playback/auditioning, non-destructive edits, and simple exporting of the final sounds. The free version allows a local library of 10,000 files which is more than enough for my usage. I’m not affiliated with them in any way, but they offer a free version and a &lt;a href=&quot;https://getsoundly.com/news/soundly-promo-code-free/&quot;&gt;1-month free trial of their paid version&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As you add your local folder of files it allows you to import a (semicolon-separated) .csv file containing additional metadata. It’s here that I point it to the file that was generated by the classifier. The categories are imported as the description of the sound, and are able to be searched. Perfect!&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sun, 13 Aug 2023 17:01:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2023/08/13/automatically-classifying-the-content-of-sound-files-using-ml/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2023/08/13/automatically-classifying-the-content-of-sound-files-using-ml/</guid>
        </item>
      
    
      
        <item>
          <title>Extracting sounds from Macromedia Director files</title>
          <description>&lt;p&gt;For my latest “quick” &lt;a href=&quot;https://play.date&quot;&gt;Playdate&lt;/a&gt; project—a remaster of a ~1997 web game by &lt;a href=&quot;https://lostmediawiki.com/Thoru_Yamamoto_works_(partially_found_interactive_media;_1990s)&quot;&gt;Thoru Yamamoto&lt;/a&gt;—I decided to add sound effects. In order to keep it as authentic as possible I decided to use only sound effects created by Thoru Yamamoto that were used in his other productions.&lt;/p&gt;

&lt;p&gt;The largest collection of sounds I could think of were those included in his Macromedia Director web experiments which include everything from short animations, through games and interactive toys, to abstract slideshows. The problem is, these Director files are tricky to deal with some 25 years later.&lt;/p&gt;

&lt;h2 id=&quot;halt-and-catch-fire&quot;&gt;Halt and Catch Fire&lt;/h2&gt;

&lt;p&gt;A bit of reading and some help from &lt;a href=&quot;https://www.mistys-internet.website&quot;&gt;Misty De Méo&lt;/a&gt; led me to &lt;a href=&quot;https://github.com/ProjectorRays/ProjectorRays&quot;&gt;ProjectorRays&lt;/a&gt; which can convert a protected .dcr file into an editable .dir file, and also allows saving of all the individual chunks that comprise each file. Think of it as one chunk for each piece of graphics, sound, and so on.&lt;/p&gt;

&lt;p&gt;Once you’ve converted the .dcr to .dir you can open the file in, say, &lt;a href=&quot;https://vinizinho.net/projects/shockwave-rip&quot;&gt;Macromedia Director 2004 and use CastRipperTool&lt;/a&gt; to export sounds and graphics and more. But it’s a very manual process and the whole setup is prone to crashing. Crucially, it won’t open some older Director movies so this wasn’t a good enough solution for me.&lt;/p&gt;

&lt;h2 id=&quot;chunks&quot;&gt;Chunks&lt;/h2&gt;

&lt;p&gt;I looked to the chunks that had been dumped by ProjectorRays and some quick experimentation showed that the ‘&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;snd &lt;/code&gt;’ chunks contained raw PCM audio date with a bespoke header. Loading these into something like ocenaudio wave editor was proof enough, but I would need to figure out some of the header to see if I could get the correct sample rate of each file.&lt;/p&gt;

&lt;p&gt;With a bit of help bouncing ideas off &lt;a href=&quot;https://hikari.noyu.me&quot;&gt;hikari_no_yume&lt;/a&gt; in a &lt;a href=&quot;https://github.com/hikari-no-yume/dream-sparer/issues/1&quot;&gt;bug report&lt;/a&gt; on one of their tools I figured out the location of the sample rate. Interestingly, the values were unexpected and not the usual values 11025, 22050, etc. It turns out that classic Apple Macintosh used some slightly &lt;a href=&quot;https://whitefiles.org/dta/pgs/c08.htm&quot;&gt;different sample rate values&lt;/a&gt;: 11127, 22254, etc.&lt;/p&gt;

&lt;p&gt;Using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xxd&lt;/code&gt; tool I dumped the relevant section of the headers of 1163 sounds from 105 .dcr files. The breakdown in sample rates found was as follows:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Rate (Hz)&lt;/th&gt;
      &lt;th&gt;Hex&lt;/th&gt;
      &lt;th&gt;Quantity Found&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;7,418&lt;/td&gt;
      &lt;td&gt;0x1cfa&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;11,127&lt;/td&gt;
      &lt;td&gt;0x2b77&lt;/td&gt;
      &lt;td&gt;616&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;22,050&lt;/td&gt;
      &lt;td&gt;0x5622&lt;/td&gt;
      &lt;td&gt;31&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;22,254&lt;/td&gt;
      &lt;td&gt;0x56ee&lt;/td&gt;
      &lt;td&gt;515&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The lack of any unexpected sample rate values was further proof that this was the real deal.&lt;/p&gt;

&lt;h2 id=&quot;shell-script&quot;&gt;Shell script&lt;/h2&gt;

&lt;p&gt;Armed with this information I decided to write a short shell script that would do the following:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;extract the sample rate from the ‘&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;snd &lt;/code&gt;’ chunk&lt;/li&gt;
  &lt;li&gt;create a trimmed raw pcm file excluding the 78-byte header&lt;/li&gt;
  &lt;li&gt;use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sox&lt;/code&gt; to add a new WAV header using the correct sample rate&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This script allows me to batch process all the audio and that’s much more to my liking: it captures everything compared to CastRipperTool and it’s a lot quicker.&lt;/p&gt;

&lt;p&gt;So, dump your chunks using ProjectorRays and then call my script like this:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;find &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-iname&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;*.bin&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-exec&lt;/span&gt; ./bin2wav.sh &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;noscript&gt;&lt;p&gt;&lt;a href=&quot;https://gist.github.com/gingerbeardman/1e6170d2652352bf30623b2a6c8d12fd&quot;&gt;View the source code as a Gist&lt;/a&gt;&lt;/p&gt;&lt;/noscript&gt;
&lt;script src=&quot;https://gist.github.com/gingerbeardman/1e6170d2652352bf30623b2a6c8d12fd.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;I’m assuming 8-bit, mono, unsigned PCM data in the ‘&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;snd &lt;/code&gt;’ chunks and have not found anything else in them.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://sox.sourceforge.net/sox.html&quot;&gt;SoX&lt;/a&gt; (Sound eXchange, the Swiss Army knife of audio manipulation) requires the .raw extension for raw PCM audio data, it will refuse to process the files without it.&lt;/li&gt;
&lt;/ul&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 12 Aug 2023 19:42:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2023/08/12/extracting-sounds-from-macromedia-director-files/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2023/08/12/extracting-sounds-from-macromedia-director-files/</guid>
        </item>
      
    
      
        <item>
          <title>The biggest crime in pop music?</title>
          <description>&lt;p&gt;The re-recorded version of “Ride on Time” (featuring an uncredited Heather Small on vocals) released in the UK on Deconstruction Records.&lt;/p&gt;

&lt;lite-youtube style=&quot;aspect-ratio: 16/9;&quot; videoid=&quot;M0quXl_od3g&quot; params=&quot;start=0&amp;amp;modestbranding=2&quot;&gt;
&lt;/lite-youtube&gt;

&lt;p&gt;It was (re)made as the original samples couldn’t be cleared. The track’s success is what gave us &lt;em&gt;M People&lt;/em&gt;, for better or worse. 😅  Sadly this version is the one you hear today on UK radio.&lt;/p&gt;

&lt;p&gt;And here’s the original version of “Ride on Time”, released in Italy by the &lt;em&gt;real&lt;/em&gt; BLACK BOX in 1989.&lt;/p&gt;

&lt;lite-youtube style=&quot;aspect-ratio: 16/9;&quot; videoid=&quot;R3aW9kkfvWk&quot; params=&quot;start=0&amp;amp;modestbranding=2&quot;&gt;
&lt;/lite-youtube&gt;

&lt;p&gt;The original Italian version of “Ride on Time” can be found today because the samples of the Salsoul Records classic, &lt;a href=&quot;https://www.youtube.com/watch?v=ddsO36srle0&quot;&gt;Loleatta Holloway’s “Love Sensation”&lt;/a&gt;, were eventually (finally!) cleared in 2018!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Ride_on_Time#Sampling_dispute&quot;&gt;Read more about this on Wikipedia&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;the-sample&quot;&gt;The Sample&lt;/h2&gt;

&lt;lite-youtube style=&quot;aspect-ratio: 16/9;&quot; videoid=&quot;ddsO36srle0&quot; params=&quot;start=0&amp;amp;modestbranding=2&quot;&gt;
&lt;/lite-youtube&gt;

</description>
          <author>by Matt Sephton</author>
          <pubDate>Fri, 25 Nov 2022 00:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2022/11/25/the-biggest-crime-in-pop-music/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2022/11/25/the-biggest-crime-in-pop-music/</guid>
        </item>
      
    

  </channel>
</rss>
