<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:base="https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/classes/863.25/people/SunChuanqi/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>HTMAA 2025 BLog</title>
    <link>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/classes/863.25/people/SunChuanqi/</link>
    <atom:link href="https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/feed.xml" rel="self" type="application/rss+xml" />
    <description>This is a longer description about your blog.</description>
    <language>en</language>
    <item>
      <title>Week 14: Wildcard for Wildcut</title>
      <link>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/</link>
      <description>&lt;p&gt;In the wild card week, we get to choose from a selection of topics to dive deep with one of our TAs.&lt;/p&gt;
&lt;p&gt;I wanted to celebrate everything we learned about time management from this class by engraving the cover of The Dark Side of The Moon, the album that includes &amp;quot;Time&amp;quot;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;And then one day you find ten years have got behind you
No one told you when to run, you missed the starting gun&lt;/p&gt;
&lt;p&gt;— Pink Floyd, &amp;quot;Time&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;asset-preparation&quot;&gt;Asset Preparation&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.24/people/JiamingLiu/About%20me.html&quot;&gt;Jiaming&lt;/a&gt; is our great TA who helped organize lab sessions and hands-on practice. We used the &lt;a href=&quot;https://www.xtool.com/products/xtool-f2-ultra-60w-mopa-40w-diode-dual-laser-engraver&quot;&gt;XTool F2 Ultra 60W MOPA Laser Cutter&lt;/a&gt; for engraving and the &lt;a href=&quot;https://www.xtool.com/products/xtool-metalfab-laser-welder-and-cnc-cutters&quot;&gt;xTool MetalFab Laser Welder/CNC Cutter&lt;/a&gt; for cutting 3mm stainless steel.&lt;/p&gt;
&lt;p&gt;I prepared 3 different designs to sample a variety of capabilities of the laser cutter.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Design&lt;/th&gt;
&lt;th&gt;Focus&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dark Side of The Moon&lt;/td&gt;
&lt;td&gt;Multi-color marking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spectrogram&lt;/td&gt;
&lt;td&gt;Raster image resolution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nameplate&lt;/td&gt;
&lt;td&gt;Typography details&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;dark-side-of-the-moon&quot;&gt;Dark Side of The Moon&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/dark-side-of-the-moon.webp&quot; alt=&quot;The Dark Side of The Moon album cover&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Iconic album cover of Pink Floyd&#39;s &amp;quot;The Dark Side of The Moon&amp;quot; (1973)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I learned that &lt;a href=&quot;https://www.xtool.com/blogs/xtool-academy/what-is-mopa-laser&quot;&gt;Master Oscillator Power Amplifier (MOPA)&lt;/a&gt; lasers can modulate the temperature at which metal oxidizes, producing different colors on stainless steel, aluminum, and titanium. I started with the original album cover and traced it with SVG.&lt;/p&gt;
&lt;p&gt;In preparation for the lab session, I separated the traces by process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Single line for incoming light and the prism outline&lt;/li&gt;
&lt;li&gt;Gradient fill for the triangle inside the prism&lt;/li&gt;
&lt;li&gt;Solid fill for the line beams exiting the prism&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/decomposing-colors.webp&quot; alt=&quot;Decomposing layers&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Decomposing the design by process&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It turned out I over-prepared. Jiaming showed me how to separate elements with the XTool software. We just needed to select each part and apply different parameters.&lt;/p&gt;
&lt;p&gt;In an earlier session, &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/EdwardChen/&quot;&gt;Edward&lt;/a&gt; had worked with Jiaming to characterize the color output as a function of power, speed, and pulse frequency. All I needed was choosing from their color palette, mainly in the bottom righ corner below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/picking-color.webp&quot; alt=&quot;Picking color other people&#39;s characterization result&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Picking color from other people&#39;s characterization&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After the first cut, the line stroke was not coming out. We updated the parameters and the rest of the engraving went smoothly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/engraving-01.webp&quot; alt=&quot;Acceptable result after 2nd&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Acceptable result after 2nd attempt&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;My design file messed up the gradient part inside the prism. It came out as a black triangle, losing the original gradient effect. The colors were limited but at least we had a rainbow-like pattern.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/macro-lens-01.webp&quot; alt=&quot;Macro&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Macro lens close-up of the engraving&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Under the macro lens, I saw strong aliasing patterns in the rainbow beams. I wonder if there is a way to avoid quantized laser movement.&lt;/p&gt;
&lt;h2 id=&quot;spectrogram-of-neils-saying&quot;&gt;Spectrogram of Neil&#39;s Saying&lt;/h2&gt;
&lt;p&gt;I wanted to test if we can store sound as an image engraved on metal.&lt;/p&gt;
&lt;p&gt;I had separately vibe-coded &lt;a href=&quot;https://code.chuanqisun.com/spectrogram-recorder/&quot;&gt;a program&lt;/a&gt; for conversion between audio and spectrogram. The program was all AI generated with 50+ rounds of revision. Since I don&#39;t have the full history of the AI coding session, I won&#39;t claim credit for the code. For this project, I&#39;m only using the tool to generate a spectrogram from sound and to verify the sound from the engraved spectrogram.&lt;/p&gt;
&lt;p&gt;Luckily, we had gathered lots of &lt;a href=&quot;https://gitlab.cba.mit.edu/classes/863.25/CBA/cba-machine/-/tree/main/software/media/neil_audio&quot;&gt;quotes from Neil&lt;/a&gt; during the &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-11-mob-making-a-machine/&quot;&gt;Machine Building Week&lt;/a&gt;. Let&#39;s pick a hot take!&lt;/p&gt;
&lt;p&gt;&lt;audio src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/quote-before.mp3&quot; controls=&quot;&quot;&gt;&lt;/audio&gt;
&lt;strong&gt;Original quote from Neil&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/restore-original.webp&quot; alt=&quot;Original spectrogram&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Original spectrogram generated from audio&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Engraving didn&#39;t go well. All the gray levels were lost, and worse still, some of the darkest areas were inverted to light color.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/engraving-02.webp&quot; alt=&quot;Engraved spectrogram&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Engraved spectrogram with unwanted artifacts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Upon a closer look, the engraving cut too deep. We should have used color marking process instead.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/macro-lens-02.webp&quot; alt=&quot;Close-up of engraving&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Close-up inspection revealed surface damage&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I took a photo of the engraved metal, processed it in Figma, and decoded it into sound. The restoration steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Desaturate&lt;/li&gt;
&lt;li&gt;Invert&lt;/li&gt;
&lt;li&gt;Color burn (HEX &lt;code&gt;#666666&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Reduce exposure&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/restore-human.webp&quot; alt=&quot;Manual restoration process&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Manual restoration process&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To make this project more interesting, I also used a large-language model with vision skills to restore the spectrogram. The AI restoration is similar to the manual process except I prompted AI to make most edits. AI was able to visually restore the &lt;a href=&quot;https://en.wikipedia.org/wiki/Formant&quot;&gt;formants&lt;/a&gt; lines, which in theory, contribute to a more natural utterance.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/restore-ai.webp&quot; alt=&quot;AI restoration&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;AI restoration process (&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/code/reconstruction-trace.txt&quot;&gt;chat log&lt;/a&gt;)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I uploaded both reconstructions to my custom &lt;a href=&quot;https://code.chuanqisun.com/spectrogram-recorder/&quot;&gt;spectrogram player&lt;/a&gt; and compared the results:&lt;/p&gt;
&lt;p&gt;&lt;audio src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/quote-before.mp3&quot; controls=&quot;&quot;&gt;&lt;/audio&gt;
&lt;strong&gt;Original&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;audio src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/quote-after-human.mp3&quot; controls=&quot;&quot;&gt;&lt;/audio&gt;
&lt;strong&gt;Manual reconstruction from photo of engraving&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;audio src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/quote-after-ai.mp3&quot; controls=&quot;&quot;&gt;&lt;/audio&gt;
&lt;strong&gt;AI reconstruction from photo of engraving&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Neither reconstruction is very good. Given more time, I would attempt 2.5D engraving to capture the spectrogram in 3D and use computer vision to restore the depth. But, wait, did I just re-invent CDs?&lt;/p&gt;
&lt;h2 id=&quot;nameplate-for-final-project&quot;&gt;Nameplate for Final Project&lt;/h2&gt;
&lt;p&gt;I wanted to design a &lt;a href=&quot;https://en.wikipedia.org/wiki/Rating_plate&quot;&gt;nameplate&lt;/a&gt; for my &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/final-project-tangible-adventure/&quot;&gt;final project&lt;/a&gt;. The 3mm thickness of the stainless steel was too thick for this purpose, but I proceeded anyway just to test how much detail we can get with typography.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/nameplate-design.webp&quot; alt=&quot;Nameplate design&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Nameplate design with layers separated by process&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We failed in creating small texts using SVG exported from Figma. The program tried to trace the outline of the text instead of engraving the internal strokes. We switched to XTool&#39;s built-in font and the same issue persisted. In addition, the &lt;a href=&quot;https://www.compart.com/en/unicode/U+2393&quot;&gt;Direct Current Symbol&lt;/a&gt; was too small for our process.&lt;/p&gt;
&lt;p&gt;Had we had more time, I would play with different SVG font options until the machine can recognize the internal strokes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/engraving-03.webp&quot; alt=&quot;Nameplate result&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Engraved nameplate&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Zooming in, I saw crisp texts. But the MIT logo suffered the same aliasing issue as before.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/macro-lens-03.webp&quot; alt=&quot;Macro lens close-up of nameplate&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;MIT logo was aliased&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;post-processing&quot;&gt;Post-processing&lt;/h2&gt;
&lt;p&gt;Jiaming showed me how to cut the metal with the MetalFab laser cutter. It was the same process we practiced in &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-2-making-the-cut/&quot;&gt;Machine Cutting Week&lt;/a&gt; except for the use of a more powerful laser and the addition of air blasting for material removal.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/laser-cut-metal.mp4&quot; controls=&quot;&quot; title=&quot;Cutting metal with laser&quot;&gt;&lt;/video&gt;
&lt;strong&gt;The violent process of cutting metal with laser&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After cutting, the molten metal edges need to be sanded down for safety and aesthetics. Jiaming showed me how to use the belt sander to remove the edges.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/engraving-04.webp&quot; alt=&quot;Before and after sanding&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Before and after sanding, showing bottom side&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Following Jiaming&#39;s demo, I sanded out the corners. The final results look great!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/media/final-results.webp&quot; alt=&quot;Final results&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Final results of all 3 designs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Shout out to &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.24/people/JiamingLiu/About%20me.html&quot;&gt;Jiaming&lt;/a&gt; for hands-on coaching, and &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/EdwardChen/&quot;&gt;Edward&lt;/a&gt; for sharing his characterization results.&lt;/p&gt;
&lt;h2 id=&quot;reflection&quot;&gt;Reflection&lt;/h2&gt;
&lt;p&gt;Color engraving is very trial-and-error based. It takes 2 hours just to map out a few possible color options. The parameters must be determined for the specific material and machine, and results may still vary.&lt;/p&gt;
&lt;p&gt;I only had access to yellow, blue, purple colors on stainless steel. It would be vastly more interesting if we can access green and red colors as well.&lt;/p&gt;
&lt;p&gt;Engraving raster image was very hard. I wish to have a process that can generate more gray levels. At the very minimum, I need to prevent inversions where darker designs become lighter due to over-burning.&lt;/p&gt;
&lt;p&gt;AI was able to reconstruct somewhat intelligible spectrogram from a photo. This could become a new research topic or a multi-sensor AI benchmark.&lt;/p&gt;
&lt;h2 id=&quot;appendix&quot;&gt;Appendix&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/code/design.zip&quot;&gt;All graphic design assets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/code/dark-side-of-the-moon.xcs&quot;&gt;Album cover xTool project file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/code/spectrogram-app.zip&quot;&gt;Spectrogram app&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Sun, 07 Dec 2025 00:00:00 GMT</pubDate>
      <dc:creator>Sun Chuanqi</dc:creator>
      <guid>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-14-wildcard-for-wildcut/</guid>
    </item>
    <item>
      <title>Week 13: An Audio Adventure</title>
      <link>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-13-an-audio-adventure/</link>
      <description>&lt;p&gt;This week, I set out to bring my &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/final-project-tangible-adventure/&quot;&gt;walkie-talkie device&lt;/a&gt; closer to an interactive demo. The goal was to write an application that interfaces a user with the input and output devices I made throughout the semester: the Operator (ESP32 with probe and buttons) and the Switchboard (ESP32 with TRRS sockets and LEDs). The application would orchestrate state management while providing a web UI for device management and debugging.&lt;/p&gt;
&lt;p&gt;I grew tired of AI-generated frontend code with its &lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind&lt;/a&gt; classes, &lt;a href=&quot;https://react.dev/&quot;&gt;React&lt;/a&gt; boilerplate, blue-purple gradients, oversized typography, and distracting animations. I decided to build as close to the web platform as possible: vanilla &lt;a href=&quot;https://www.typescriptlang.org/&quot;&gt;TypeScript&lt;/a&gt;, &lt;a href=&quot;https://rxjs.dev/&quot;&gt;RxJS&lt;/a&gt; for reactive state, and &lt;a href=&quot;https://lit.dev/docs/libraries/standalone-templates/&quot;&gt;lit-html&lt;/a&gt; for templating.&lt;/p&gt;
&lt;h2 id=&quot;reviving-the-foundation&quot;&gt;Reviving the Foundation&lt;/h2&gt;
&lt;p&gt;I started by reviving the walkie-talkie code from &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-9-machine-melody/#transcribing-audio-and-streaming-voice-from-computer-to-device&quot;&gt;week 9&lt;/a&gt;. The immediate challenge was IP discovery. Both devices had hardcoded addresses that broke whenever the network changed.&lt;/p&gt;
&lt;h3 id=&quot;discovering-esp32-address&quot;&gt;Discovering ESP32 Address&lt;/h3&gt;
&lt;p&gt;The laptop could discover the ESP32&#39;s IP by inspecting &lt;code&gt;rinfo.address&lt;/code&gt; from incoming UDP packets:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt; as&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; dgram&lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;dgram&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; udpReceiver&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;dgram&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;createSocket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;udp4&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;udpReceiver&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bind&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;8888&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;udpReceiver&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rinfo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // rinfo contains sender information&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; senderIp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rinfo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Get sender&#39;s IP address&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; senderPort&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rinfo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Get sender&#39;s port&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`Received from &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;senderIp&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;senderPort&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`Data: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;discovering-laptop-address&quot;&gt;Discovering Laptop Address&lt;/h3&gt;
&lt;p&gt;The reverse problem was harder. The ESP32 needed to know the laptop&#39;s IP address. I used the Node.js &lt;a href=&quot;https://nodejs.org/api/os.html#osnetworkinterfaces&quot;&gt;&lt;code&gt;os.networkInterfaces()&lt;/code&gt;&lt;/a&gt; API to expose the laptop&#39;s address through an HTTP endpoint:&lt;/p&gt;
&lt;p&gt;Server&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt; as&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; os&lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;os&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; express&lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;express&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;express&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/api/origin&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; interfaces&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;os&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;networkInterfaces&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; ipv4&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Object&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;values&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;interfaces&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;flat&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;addr&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; addr&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;family&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;IPv4&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; !&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;addr&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;internal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  res&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;host:&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; ipv4&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; || &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;localhost&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;listen&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;3000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Web UI&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; ipInput&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;document&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getElementById&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;ipInput&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; HTMLInputElement&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; fetchButton&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;document&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getElementById&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;fetchButton&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; HTMLButtonElement&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;fetchButton&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;click&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;http://localhost:3000/api/origin&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    ipInput&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;host&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Failed to fetch origin:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    ipInput&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Error fetching origin&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The web UI allowed users to fetch the laptop IP. We still need to push it to the ESP32 somehow.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-13-an-audio-adventure/media/app-v0.webp&quot; alt=&quot;App v0&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Initial UI for manually reading IP address&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;automated-handshake-protocol&quot;&gt;Automated Handshake Protocol&lt;/h2&gt;
&lt;p&gt;I borrowed the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API&quot;&gt;BLE&lt;/a&gt; networking code from the &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-12-network3/#network-2-mac-and-name-as-ble-address&quot;&gt;Networking week&lt;/a&gt; and enhanced it with an automated handshake protocol for the laptop and the ESP32 to exchange IP addresses that can be used for full duplex UDP streaming. As soon as ESP32 is paired over BLE, the following sequence occurs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Web requests server address from Node.js&lt;/li&gt;
&lt;li&gt;Server responds with its own IP&lt;/li&gt;
&lt;li&gt;Web sends server address to Operator via BLE&lt;/li&gt;
&lt;li&gt;Operator responds with its own IP&lt;/li&gt;
&lt;li&gt;Web registers Operator address with server&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;web&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Fetch server IP on page load&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;http://localhost:3000/api/origin&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;serverAddressSpan&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;textContent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;host&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// After Operator BLE connects, send server address&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`server:&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;hostname&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sendMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Receive operator address from device&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;startsWith&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;operator:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;substring&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  operatorAddressSpan&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;textContent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  fetch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`http://localhost:3000/api/locate-operator?address=&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;encodeURIComponent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    method:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;POST&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;operator&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Receive server address from web&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; handleRxMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;startsWith&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;server:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    String serverIp = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;substring&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;lastIndexOf&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;:&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; port = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;substring&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;lastIndexOf&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;:&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) + &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toInt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Store and use for UDP&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    setupUDP&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;serverIp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;c_str&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(), port);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Send operator address to web for registration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; sendOperatorAddress&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  String myIP = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;localIP&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  sendBLE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;operator:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + myIP);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the UI, I displayed both addresses for verification. In addition, the probe state is streamed over BLE and shown in real-time.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-13-an-audio-adventure/media/app-v2.webp&quot; alt=&quot;App screen&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Automated handshake and BLE data streaming&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;adding-the-switchboard-ui&quot;&gt;Adding the Switchboard UI&lt;/h2&gt;
&lt;p&gt;I added a Switchboard UI for connection testing, again, reusing the BLE communication patterns from the Networking week.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-13-an-audio-adventure/media/app-v3.webp&quot; alt=&quot;App screen&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Switchboard connection panel&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;real-time-server-push-with-sse&quot;&gt;Real-time Server Push with SSE&lt;/h3&gt;
&lt;p&gt;I wanted the web UI to receive live updates from the server without polling. I added a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events&quot;&gt;Server-Sent Events (SSE)&lt;/a&gt; endpoint to push data to the browser with minimal latency:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Server: SSE endpoint for pushing events to web&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/api/events&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  res&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;writeHead&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, { &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;text/event-stream&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Cache-Control&quot;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;no-cache&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  sseClients&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  req&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;close&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    sseClients&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;sseClients&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; c&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; !== &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Server: /api/speak endpoint - triggers speech synthesis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/api/speak&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;voice&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } = &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; synthesizeAndStreamSpeech&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;voice&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  emitServerEvent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Push to SSE clients&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Web: Listen to SSE stream&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; eventSource&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; EventSource&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;http://localhost:3000/api/events&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;eventSource&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;onmessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  logDiv&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;textContent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; += &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`[&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;timestamp&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;] SSE: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Web: Trigger speak from UI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;speakBtn&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;click&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;speakTextarea&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;trim&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;http://localhost:3000/api/speak&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    method:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;POST&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    body:&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; JSON&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The web UI could now trigger speech synthesis and receive real-time updates regarding any speech events from the server.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-13-an-audio-adventure/media/app-v4.webp&quot; alt=&quot;App screen&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Speech synthesis controls with live event log&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;taming-complexity&quot;&gt;Taming Complexity&lt;/h2&gt;
&lt;p&gt;As features accumulated, the codebase became unwieldy. BLE communication lived in the web UI, but UDP communication lived in the Server.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-13-an-audio-adventure/media/diagram-v4.webp&quot; alt=&quot;Architecture v1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Entangled architecture with BLE in web UI and UDP in server&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I refactored nearly all the code to move BLE communication into the server, creating a modular architecture. Normally I would do this with AI. But the stake is too high: I&#39;m running out of time for this week&#39;s project, and I knew I need a solid foundation to carry me through the final project. Reliability and maintainability became my priority, which meant no AI coding for this refactor.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-13-an-audio-adventure/media/diagram-v5.webp&quot; alt=&quot;Architecture v2&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Simplified architecture after refactoring&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In the simplified architecture, I heavily relied on RxJS to create reactive data streams.&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;rxjs&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;HTTP_PORT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;LAPTOP_UDP_RX_PORT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./config&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;BLEDevice&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;opMac&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;swMac&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./features/ble&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;createButtonStateMachine&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./features/buttons&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;geminiResponse$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;geminiTranscript$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;handleConnectGemini&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;handleDisconnectGemini&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./features/gemini-live&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;createHttpServer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./features/http&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  handleButtonsMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  handleConnectOperator&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  handleDisconnectOperator&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  handleOpAddressMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  handleProbeMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  handleRequestOperatorAddress&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  logOperatorMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  operatorAddress$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  operatorButtons$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  operatorProbeNum$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./features/operator&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;silence$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./features/silence-detection&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;handleAudio&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;handleConnectSession&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;handleDisconnectSession&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;interrupt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;triggerResponse&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./features/simulation&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;broadcast&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;handleSSE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;newSseClient$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./features/sse&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;appState$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;updateState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./features/state&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;handleBlinkLED&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;handleConnectSwitchboard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;handleDisconnectSwitchboard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./features/switchboard&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;createUDPServer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./features/udp&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; main&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; operator&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; BLEDevice&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;opMac&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; switchboard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; BLEDevice&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;swMac&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  createUDPServer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;handleAudio&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()], &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;LAPTOP_UDP_RX_PORT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  createHttpServer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      handleSSE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      handleBlinkLED&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;switchboard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      handleConnectSwitchboard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;switchboard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      handleDisconnectSwitchboard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;switchboard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      handleConnectOperator&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;operator&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      handleDisconnectOperator&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;operator&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      handleRequestOperatorAddress&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;operator&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      handleConnectSession&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      handleDisconnectSession&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      handleConnectGemini&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      handleDisconnectGemini&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;    HTTP_PORT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  appState$&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      map&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ({ &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; })),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;broadcast&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  newSseClient$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; broadcast&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;state:&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; appState$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; }))).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  operator&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;logOperatorMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;handleProbeMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;handleOpAddressMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;handleButtonsMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;())).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  operatorProbeNum$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;num&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; updateState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ({ ...&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;probeNum:&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; num&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; })))).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  operatorAddress$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; updateState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ({ ...&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;opAddress:&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; })))).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  operatorButtons$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;buttons&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; updateState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ({ ...&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;btn1:&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; buttons&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;btn1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;btn2:&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; buttons&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;btn2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; })))).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; operataorButtons&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;createButtonStateMachine&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;operatorButtons$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  operataorButtons&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;leaveIdle$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;interrupt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  silence$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;triggerResponse&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Gemini Live API subscriptions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  geminiTranscript$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`🎤 Transcript: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  geminiResponse$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`🤖 Gemini: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The web UI code followed the same modular pattern:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;rxjs&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;appendDiagnosticsError&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;updateDiagnosticsState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./features/diagnostics&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;initOperatorUI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;updateOperatorUI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./features/operator&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;initSimulationUI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;updateSimulationUI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./features/simulation&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;createSSEObservable&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./features/sse&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;state$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;stateChange$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./features/state&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;initSwitchboardUI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;updateSwitchboardUI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./features/switchboard&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./style.css&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;initSwitchboardUI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;initOperatorUI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;initSimulationUI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;state$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;updateDiagnosticsState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;stateChange$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;updateSwitchboardUI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;updateOperatorUI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;updateSimulationUI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; sseEvents$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;createSSEObservable&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;http://localhost:3000/api/events&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;sseEvents$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  next&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      state$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  error&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    appendDiagnosticsError&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The UI now embodied the principle that view is a pure function of state. I rendered the JSON state of the server directly on the web UI. The rest of the interface was derived from that state.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-13-an-audio-adventure/media/app-v5.webp&quot; alt=&quot;App screen&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Diagnostics panel showing raw server state&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-pivot&quot;&gt;The Pivot&lt;/h2&gt;
&lt;p&gt;Hardware failure struck. The microphone randomly picked up high noise. The speaker had unstable contact and barely worked. With no time left to debug flaky audio hardware, I made a strategic decision: keep the custom input devices (probe and buttons), keep the custom output device (LEDs), but route audio through the computer&#39;s speakers. Based on the remaining functional hardware, I redesigned the application to be an interactive &lt;a href=&quot;https://en.wikipedia.org/wiki/Interactive_fiction&quot;&gt;text adventure game&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks to the modular refactor, this pivot was fast.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gemini API&lt;/strong&gt; generates branching story options&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenAI TTS&lt;/strong&gt; synthesizes speech for each option&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LEDs&lt;/strong&gt; illuminate when story options become available&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Probe&lt;/strong&gt; selects which option to preview (triggers audio playback)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Buttons&lt;/strong&gt; commit the selection, advancing the story&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The physical interface became a tangible story navigator. Probe a position to hear an option, press a button to choose your path.&lt;/p&gt;
&lt;h3 id=&quot;game-logic&quot;&gt;Game Logic&lt;/h3&gt;
&lt;p&gt;The main server wires together the game flow:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;textGenerated$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;concatMap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;index&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; turnOnLED&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;switchboard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;index&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;operatorProbeNum$&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    filter&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;num&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; num&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; !== &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;index&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; previewOption&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;index&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;operatorProbeNum$&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    filter&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;num&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; num&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;cancelAllSpeakerPlayback&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;operataorButtons&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;someButtonDown$&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    withLatestFrom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;operatorProbeNum$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;cancelAllSpeakerPlayback&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; turnOffAllLED&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;switchboard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(([&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;index&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; commitOption&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;index&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The game logic module handles story generation and option management:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;GoogleGenAI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;@google/genai&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;JSONParser&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;@streamparser/json&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;BehaviorSubject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Subject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;rxjs&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;z&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;zod&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;zodToJsonSchema&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;zod-to-json-schema&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt; type&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Handler&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./http&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;cancelAllSpeakerPlayback&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;playPcm16Buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./speaker&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;appState$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;updateState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./state&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;GenerateOpenAISpeech&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;./tts&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; storyOptionsSchema&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;z&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;object&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  storyOptions:&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; z&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;z&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;describe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;A story beginning for a text adventure game.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; ai&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; GoogleGenAI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;apiKey:&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; process&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;env&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;GEMINI_API_KEY&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;! });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; textGenerated$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; Subject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; assignments$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; BehaviorSubject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;#x3C;{ &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;index&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; }[]&gt;([]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; ongoingTasks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [] &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; AbortController&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; previewOption&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; assignment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;assignments$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; a&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;index&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;assignment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; || &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;assignment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; || &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;assignment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Assignment not found or not ready&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  cancelAllSpeakerPlayback&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  playPcm16Buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; assignment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; commitOption&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; assignment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;assignments$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; a&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;index&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;assignment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; || &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;assignment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Assignment not found or not ready&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  killAllTasks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  cancelAllSpeakerPlayback&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Commit the option to the story history&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  updateState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    ...&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    storyHistory:&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; [...&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;storyHistory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;assignment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;!],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // reset assignment slots&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  assignments$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;assignments$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ({ ...&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text:&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioBuffer:&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; })));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; ac&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; AbortController&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  ongoingTasks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ac&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; generateOptionsInternal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ac&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;finally&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    ongoingTasks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ongoingTasks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;task&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; task&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; !== &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ac&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; handleStartTextAdventures&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(): &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Handler&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;method&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; !== &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; || &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; !== &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/api/adventure/start&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    killAllTasks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    cancelAllSpeakerPlayback&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    assignments$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;].&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ({ &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;index:&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; i&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text:&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioBuffer:&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; })));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    updateState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ({ ...&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;storyHistory:&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; [] }));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; ac&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; AbortController&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    ongoingTasks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ac&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; generateOptionsInternal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ac&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      res&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;writeHead&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      res&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;finally&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      ongoingTasks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ongoingTasks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;task&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; task&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; !== &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ac&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; generateOptionsInternal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ac&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AbortController&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;escapeIndex&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;?: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; parser&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; JSONParser&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  parser&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;onValue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;entry&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;typeof&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; entry&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;number&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;typeof&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; entry&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Story option:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;entry&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; randomIndex&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;random&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; Set&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;assignments$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; a&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;index&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; !== &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;escapeIndex&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; a&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;index&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;randomIndex&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;No available assignment slots, skip&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      textGenerated$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;randomIndex&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      assignments$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        assignments$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;          a&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;index&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;randomIndex&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            ? {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                ...&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                text:&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; entry&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt; as&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                audioBuffer:&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; GenerateOpenAISpeech&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;entry&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt; as&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ac&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;signal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                visited:&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;              }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            : &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; ai&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;models&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;generateContentStream&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    model:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;gemini-2.5-flash&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    contents:&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; appState$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;storyHistory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      ? &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`Based on the following story so far, generate 3 different story continuations for a text adventure game. Each option should be a one short verbal sentence with only a few words.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Story so far:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;appState$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;storyHistory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;          `&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;trim&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      : &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`Generate 3 different story beginnings for a text adventure game. Each option should be a one short verbal sentence with only a few words.`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;trim&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    config:&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      responseMimeType:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      responseJsonSchema:&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; zodToJsonSchema&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;storyOptionsSchema&lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt; as&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; any&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      abortSignal:&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; ac&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;signal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; chunk&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; maybeOutput&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chunk&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;candidates&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;at&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)?.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;parts&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;at&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)?.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;maybeOutput&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;continue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    parser&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;maybeOutput&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; killAllTasks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  ongoingTasks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ac&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; ac&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;abort&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  ongoingTasks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; random&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Set&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&gt;): &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; items&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Array&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; items&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Math&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;floor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Math&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;random&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() * &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;items&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final UI only displays the options chosen so far. The rest of the user interface is in the physical devices.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-13-an-audio-adventure/media/app-v6.webp&quot; alt=&quot;App screen&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Final game UI with story plot&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let&#39;s go on an adventure!&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-13-an-audio-adventure/media/audio-adventures.mp4&quot; poster=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-13-an-audio-adventure/media/video-poster.webp&quot; controls=&quot;&quot;&gt;&lt;/video&gt;
&lt;strong&gt;Text adventure game in action&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;reflection&quot;&gt;Reflection&lt;/h2&gt;
&lt;p&gt;The final system uses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js as HTTP and UDP server&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/chrvadala/node-ble&quot;&gt;node-ble&lt;/a&gt; for BLE communication with ESP32 devices&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://rxjs.dev/&quot;&gt;RxJS&lt;/a&gt; for functional reactive programming&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lit.dev/docs/libraries/standalone-templates/&quot;&gt;lit-html&lt;/a&gt; for HTML templating&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/openai/openai-node&quot;&gt;OpenAI SDK&lt;/a&gt; and &lt;a href=&quot;https://github.com/googleapis/js-genai&quot;&gt;Google Gen AI SDK&lt;/a&gt; for text and speech generation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The pivot in the middle of the project put the modular design to the test. I was able to modify the main logic without touching any communication code. What began as a voice communication tool transformed into an AI Dungeon. The architecture enabled rapid adaptation when hardware failed.&lt;/p&gt;
&lt;p&gt;As a future improvement, I want to render each story decision point with a generated image that I can project into a room to bring more immersion to the game. If a room has multiple projectable surfaces, maybe each surface could represent a option that the player can &amp;quot;walk&amp;quot; into.&lt;/p&gt;
&lt;h2 id=&quot;appendix&quot;&gt;Appendix&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-13-an-audio-adventure/code/text-adventure.zip&quot;&gt;Full Project Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-13-an-audio-adventure/code/protocol.md&quot;&gt;Protocols and events&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Wed, 03 Dec 2025 00:00:00 GMT</pubDate>
      <dc:creator>Sun Chuanqi</dc:creator>
      <guid>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-13-an-audio-adventure/</guid>
    </item>
    <item>
      <title>Week 12: Network^3</title>
      <link>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-12-network3/</link>
      <description>&lt;p&gt;Neil noted that I had already satisfied the networking requirement during the &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-8-first-contact/&quot;&gt;Input Device&lt;/a&gt; and &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-11-mob-making-a-machine/&quot;&gt;Machine Building&lt;/a&gt; weeks. For this week, I made &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/final-project-tangible-adventure/#physical-assembly-test&quot;&gt;more progress&lt;/a&gt; on my final project without missing the opportunity in building something fun under the networking theme. I temporarily converted the hardware for my final project into a &amp;quot;whack-a-mole&amp;quot; game. In this setup, the switchboard lights up an LED, the user plugs in a phone jack to &amp;quot;whack&amp;quot; it, and then another LED lights up. By the end, I realized I had built three networks into a single project.&lt;/p&gt;
&lt;h2 id=&quot;network-1-voltage-as-physical-address&quot;&gt;Network 1: Voltage as physical address&lt;/h2&gt;
&lt;p&gt;For my final project, I am building a walkie-talkie (the &lt;strong&gt;Operator&lt;/strong&gt;) that plugs into a panel of phone jacks (the &lt;strong&gt;Switchboard&lt;/strong&gt;). During &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/&quot;&gt;Electronics fabrication week&lt;/a&gt;, I prototyped a 3-bit addressable interface using a TRRS connector. This week, I fabricated the phone jacks, complete with wiring, soldering, and mounting.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-12-network3/media/weekly-trrs-01.webp&quot; alt=&quot;Soldering TRRS connector&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Soldering TRRS connector to ribbon wires&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I used heat shrink tubes to reinforce the solder joints and prevent accidental shorts between adjacent pins. I repeatedly made the mistake of soldering the wire before adding the heat shrink tube. Because of the tube&#39;s size, I must add the tube first, solder the connection, slide the tube over the joint, and then shrink it with a heat gun. Eventually, I developed the muscle memory of &amp;quot;tube first, solder second.&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-12-network3/media/weekly-trrs-02.webp&quot; alt=&quot;Add heat shrink tube first&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Tube first, solder second, repeat after me...&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I fell victim to the information denial trap observed in behavioral economics. This occurs when someone avoids learning information because they fear the consequences. I was on a happy streak soldering the TRRS jack wires and assumed that consistent soldering would suffice. As soon as I finished, I recalled Neil&#39;s warning that these connectors are &amp;quot;nasty&amp;quot; because the different terminals touch all conductive parts during insertion.&lt;/p&gt;
&lt;p&gt;Here is my initial wiring configuration, which took over four hours to solder:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Switchboard&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - Tip: Address bit 0 (digital write high or ground)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - Ring1: Ground&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - Ring2: Address bit 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - Sleeve: Address bit 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Operator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - Tip: Address bit 0 (digital read)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - Ring1: Ground&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - Ring2: Address bit 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - Sleeve: Address bit 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s visualize the different contact possibilities caused by the sliding motion:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;TRRS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   TRRS 👈&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;TRRS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  TRRS 👈&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;TRRS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; TRRS 👈&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;TRRS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;TRRS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When visualized in a grid, I saw the digital writes (Ring2, Sleeve) touching the ground (Ring1) causes a short:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Tip (write)&lt;/th&gt;
&lt;th&gt;Ring1 (GRD)&lt;/th&gt;
&lt;th&gt;Ring2 (write)&lt;/th&gt;
&lt;th&gt;Sleeve (write)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tip (read)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ring1 (GRD)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;⚠️&lt;/td&gt;
&lt;td&gt;⚠️&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ring2 (read)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sleeve (read)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Realizing my mistake, I moved the ground to the tip so no other pins can touch it.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Tip (GRD)&lt;/th&gt;
&lt;th&gt;Ring1 (write)&lt;/th&gt;
&lt;th&gt;Ring2 (write)&lt;/th&gt;
&lt;th&gt;Sleeve (write)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tip (GRD)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ring1 (read)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ring2 (read)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sleeve (read)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Since I had already used heat shrink tubes to reinforce the pin headers, making these changes required removing the tubes and rearranging the wires. Fortunately, the ribbon wires allowed for rearrangement, though it took another two hours.&lt;/p&gt;
&lt;p&gt;In the programming, the Switchboard is hard-wired to have a digital write high or ground on each address bit. I originally planned for 8 addresses using 3 bits, but I need to reserve an address for the &amp;quot;Unplugged&amp;quot; state. I ended up with 7 usable addresses (&lt;code&gt;000&lt;/code&gt; to &lt;code&gt;110&lt;/code&gt;), with &lt;code&gt;111&lt;/code&gt; representing &amp;quot;Unplugged&amp;quot;.&lt;/p&gt;
&lt;p&gt;Here is the Switchboard code with digital write high on D6 pin, which supplies 3.3V to all the &lt;code&gt;1&lt;/code&gt; address bits:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; setup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  pinMode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(D6, OUTPUT);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  digitalWrite&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(D6, HIGH);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As seen on the PCB, each connector has six pins: two for the LED and four for the TRRS. Among the four TRRS pins, one is ground (second from the left in the left column, and second from the right in the right column).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-12-network3/media/final-switchboard-01.webp&quot; alt=&quot;Switchboard circuit&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;The address bits are baked into the hardware&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The Operator would read the address bits in a loop:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; inputPins[] = { D3, D4, D5 };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;* inputNames[] = { &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;D3&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;D4&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;D5&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; numInputs = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; setup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;115200&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  delay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; i = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; i &amp;#x3C; numInputs; ++i) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    pinMode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;inputPins&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[i], INPUT_PULLUP);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    digitalWrite&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;inputPins&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[i], HIGH);&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; // HIGH HIGH HIGH means unplugged&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; i = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; i &amp;#x3C; numInputs; ++i) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; v = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;digitalRead&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;inputPins&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[i]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;inputNames&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[i]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;: &quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (v == HIGH) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;HIGH&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;LOW&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  delay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was confirmed working in &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/#integration-test&quot;&gt;Electronics fabrication week&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;network-2-mac-and-name-as-ble-address&quot;&gt;Network 2: Mac and name as BLE address&lt;/h2&gt;
&lt;p&gt;I added LED lights to the Switchboard (see the &lt;a href=&quot;https://fab.cba.mit.edu/classes/MAS.863/CBA/group_assignments/week12/&quot;&gt;Group assignment page&lt;/a&gt; for details). Since the TRRS connection provides only one-way communication from the Switchboard to the Operator, I needed a method for the Operator to send information back to change the LED states. A browser app was introduced to be the hub. The full data flow operates as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The Operator reads a 3-bit address from the Switchboard.&lt;/li&gt;
&lt;li&gt;The Operator sends the address to the browser app.&lt;/li&gt;
&lt;li&gt;The browser app sends a new address to the Switchboard.&lt;/li&gt;
&lt;li&gt;The Switchboard lights up the LED corresponding to that address.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I needed to associate the address of the phone jacks with the specific LED next to them. The wiring had changed over time, so the only guarantee was that they were distinct. I approached this empirically by probing them to determine the final addresses.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-12-network3/media/weekly-assembly-02.webp&quot; alt=&quot;Probing for address&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Probing for address, with the help from &lt;a href=&quot;https://www.adafruit.com/product/2914&quot;&gt;Adafruit TRRS Terminal Block&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I placed a cheatsheet on the Switchboard case for easy reference:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-12-network3/media/weekly-assembly-05.webp&quot; alt=&quot;Cheatsheet for Switchboard addresses&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Cheatsheet for LED number, LED digital pin, and TRRS bit address&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here is the key logic for lighting up an LED based on a BLE command received by the Switchboard:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &amp;#x3C;BLEDevice.h&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &amp;#x3C;BLEServer.h&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &amp;#x3C;BLEUtils.h&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &amp;#x3C;BLE2902.h&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#define&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; UART_SERVICE_UUID &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;6e400001-b5a3-f393-e0a9-e50e24dcca9e&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#define&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; UART_RX_UUID      &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;6e400002-b5a3-f393-e0a9-e50e24dcca9e&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;BLEServer* pServer = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;NULL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;BLECharacteristic* pRxCharacteristic = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;NULL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; deviceConnected = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ledPins[] = {D0, D1, D2, D3, D7, D8, D9, D10};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; numLeds = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; MyServerCallbacks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; BLEServerCallbacks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; onConnect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;BLEServer&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; pServer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        deviceConnected = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; onDisconnect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;BLEServer&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; pServer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        deviceConnected = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; MyRxCallbacks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; BLECharacteristicCallbacks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; onWrite&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;BLECharacteristic&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; pCharacteristic&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        String rxValue = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pCharacteristic&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getValue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rxValue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &gt; &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rxValue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;startsWith&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;blink:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;            int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ledIndex = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rxValue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;substring&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toInt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;            if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (ledIndex &gt;= &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; ledIndex &amp;#x3C; numLeds) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;                digitalWrite&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ledPins&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[ledIndex], HIGH);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;                delay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;500&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;                digitalWrite&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ledPins&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[ledIndex], LOW);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; setup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; i = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; i &amp;#x3C; numLeds; i++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        pinMode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ledPins&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[i], OUTPUT);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        digitalWrite&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ledPins&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[i], LOW);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;    BLEDevice&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;init&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Switchboard&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    pServer = &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;BLEDevice&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;createServer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    pServer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setCallbacks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; MyServerCallbacks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    BLEService* pService = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pServer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;createService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(UART_SERVICE_UUID);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    pRxCharacteristic = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;createCharacteristic&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        UART_RX_UUID,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;        BLECharacteristic&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::PROPERTY_WRITE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    pRxCharacteristic&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setCallbacks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; MyRxCallbacks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    pService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;start&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    BLEAdvertising* pAdvertising = &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;BLEDevice&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getAdvertising&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    pAdvertising&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;addServiceUUID&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(UART_SERVICE_UUID);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    pAdvertising&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setScanResponse&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    pAdvertising&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setMinPreferred&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0x0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;    BLEDevice&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;startAdvertising&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    delay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I noticed that one of the Bluetooth devices did not appear in the browser&#39;s device list. I discovered that the device name &lt;code&gt;Switchboard&lt;/code&gt; became &lt;code&gt;Switchbo&lt;/code&gt;. The Bluetooth library appears to shorten the name to 8 characters. There is a &lt;a href=&quot;https://stackoverflow.com/questions/58772005/why-is-the-web-bluetooth-device-name-limited-to-8-bytes&quot;&gt;related issue&lt;/a&gt; reported by Android developers, though it is unclear if a similar issue lies with the ESP32 Bluetooth library or the Web Bluetooth API.&lt;/p&gt;
&lt;p&gt;I fixed the device name overflow by shortening it:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;deviceSw = await navigator.bluetooth.requestDevice({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;-  filters: [{ name: &quot;Switchboard&quot; }],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;+  filters: [{ name: &quot;sw&quot; }],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  optionalServices: [SERVICE_UUID],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Operator reads the TRRS address and sends it to the browser via BLE:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &amp;#x3C;BLEDevice.h&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &amp;#x3C;BLEServer.h&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &amp;#x3C;BLEUtils.h&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &amp;#x3C;BLE2902.h&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#define&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; UART_SERVICE_UUID &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;6e400001-b5a3-f393-e0a9-e50e24dcca9e&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#define&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; UART_TX_UUID      &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;6e400003-b5a3-f393-e0a9-e50e24dcca9e&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;BLEServer* pServer = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;NULL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;BLECharacteristic* pTxCharacteristic = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;NULL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; deviceConnected = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; oldDeviceConnected = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; inputPins[] = { D3, D4, D5 };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; numInputs = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; MyServerCallbacks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; BLEServerCallbacks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; onConnect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;BLEServer&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; pServer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        deviceConnected = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; onDisconnect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;BLEServer&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; pServer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        deviceConnected = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; setup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;115200&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  delay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; i = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; i &amp;#x3C; numInputs; ++i) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    pinMode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;inputPins&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[i], INPUT_PULLUP);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;  BLEDevice&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;init&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;op&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  pServer = &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;BLEDevice&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;createServer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  pServer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setCallbacks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; MyServerCallbacks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  BLEService* pService = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pServer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;createService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(UART_SERVICE_UUID);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  pTxCharacteristic = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;createCharacteristic&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      UART_TX_UUID,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;      BLECharacteristic&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::PROPERTY_NOTIFY&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  pTxCharacteristic&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;addDescriptor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; BLE2902&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  pService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;start&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  BLEAdvertising* pAdvertising = &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;BLEDevice&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getAdvertising&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  pAdvertising&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;addServiceUUID&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(UART_SERVICE_UUID);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  pAdvertising&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setScanResponse&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  pAdvertising&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setMinPreferred&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0x0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;  BLEDevice&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;startAdvertising&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!deviceConnected &amp;#x26;&amp;#x26; oldDeviceConnected) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    delay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;500&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    pServer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;startAdvertising&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    oldDeviceConnected = deviceConnected;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (deviceConnected &amp;#x26;&amp;#x26; !oldDeviceConnected) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    oldDeviceConnected = deviceConnected;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  String probe = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; i = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; i &amp;#x3C; numInputs; ++i) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; v = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;digitalRead&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;inputPins&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[i]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    probe += (v == HIGH) ? &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (deviceConnected) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    pTxCharacteristic&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setValue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;uint8_t&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;*)&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;probe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;c_str&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;probe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    pTxCharacteristic&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;notify&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  delay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the web app, I needed to account for connection bounce caused by the sliding motion. I used the RxJS library to handle the debounce. The web app serves as the brain. It tracks the current LED, checks if the user probes the correct address, and sends a command to the Switchboard to turn off that LED and light up another.&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Subject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;debounceTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;distinctUntilChanged&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;BehaviorSubject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;https://esm.sh/rxjs&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; SERVICE_UUID&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;6e400001-b5a3-f393-e0a9-e50e24dcca9e&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; TX_CHAR_UUID&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;6e400003-b5a3-f393-e0a9-e50e24dcca9e&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; RX_CHAR_UUID&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;6e400002-b5a3-f393-e0a9-e50e24dcca9e&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; charTxSw&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; charRxOp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; probeSubject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; Subject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; currentTarget&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; scoreSubject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; BehaviorSubject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;document&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getElementById&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;connectBtnSw&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;click&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; deviceSw&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; navigator&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;bluetooth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;requestDevice&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    filters:&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; [{ &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;name:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;sw&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; }],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    optionalServices:&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SERVICE_UUID&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; deviceSw&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;gatt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;connect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; service&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getPrimaryService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SERVICE_UUID&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  charTxSw&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; service&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getCharacteristic&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;RX_CHAR_UUID&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; encoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; TextEncoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; charTxSw&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;writeValue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;encoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;encode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;off:all&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; charTxSw&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;writeValue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;encoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;encode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;on:3&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;document&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getElementById&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;connectBtnOp&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;click&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  probeSubject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; Subject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  scoreSubject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; BehaviorSubject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; deviceOp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; navigator&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;bluetooth&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;requestDevice&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    filters:&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; [{ &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;name:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;op&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; }],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    optionalServices:&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SERVICE_UUID&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; deviceOp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;gatt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;connect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; service&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getPrimaryService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SERVICE_UUID&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  charRxOp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; service&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getCharacteristic&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;TX_CHAR_UUID&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; charRxOp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;startNotifications&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  charRxOp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;characteristicvaluechanged&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    probeSubject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; TextDecoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;decode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;trim&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  probeSubject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;distinctUntilChanged&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;debounceTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;500&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; num&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;parseInt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;num&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;currentTarget&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; encoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; TextEncoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      charTxSw&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;writeValue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;encoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;encode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;off:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;currentTarget&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; randomLed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Math&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;floor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Math&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;random&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() * &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;        while&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;randomLed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;currentTarget&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;randomLed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Math&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;floor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Math&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;random&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() * &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        charTxSw&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;writeValue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;encoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;encode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`on:&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;randomLed&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;          currentTarget&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;randomLed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;          scoreSubject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;scoreSubject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; apiKeyInput&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;document&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getElementById&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;apiKey&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;apiKeyInput&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;localStorage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getItem&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;htmaa-matti-key&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) || &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;apiKeyInput&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;input&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; localStorage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setItem&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;htmaa-matti-key&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;apiKeyInput&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;video src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-12-network3/media/whack-a-mole.mp4&quot; controls=&quot;&quot; title=&quot;Whack-a-mole demo&quot;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;h2 id=&quot;network-3-url-as-web-address&quot;&gt;Network 3: URL as web address&lt;/h2&gt;
&lt;p&gt;To satisfy the group assignment requirement of networking with another project, I added logic for the browser to HTTP POST the current score to &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/MattiGruener/&quot;&gt;Matti&lt;/a&gt;&#39;s server. This, in turn, displays the score on his e-ink display.&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;+scoreSubject.subscribe((score) =&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;+  const apiKey = document.getElementById(&quot;apiKey&quot;).value;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;+  if (apiKey) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;+    fetch(&quot;https://api.mistermatti.com/htmaa-final/text&quot;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;+      method: &quot;POST&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;+      headers: { &quot;X-API-Key&quot;: apiKey, &quot;Content-Type&quot;: &quot;application/json&quot; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;+      body: JSON.stringify({ text: score.toString() }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;+    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;+  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;+});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this add-on, the browser posts to the server whenever the score updates. See &lt;a href=&quot;https://fab.cba.mit.edu/classes/MAS.863/CBA/group_assignments/week12/&quot;&gt;Group assignment page&lt;/a&gt; for details.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-12-network3/media/networking-sender-recorder.mp4&quot; controls=&quot;&quot; title=&quot;Web app notifying remote server&quot;&gt;&lt;/video&gt;
&lt;strong&gt;Web app notifying remote server&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;appendix&quot;&gt;Appendix&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-12-network3/code/operator/operator.ino&quot;&gt;Operator code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-12-network3/code/switchboard/switchboard.ino&quot;&gt;Switchboard code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-12-network3/code/web/index.html&quot;&gt;Web app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-12-network3/code/led-mapping.json&quot;&gt;LED address map&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Tue, 25 Nov 2025 00:00:00 GMT</pubDate>
      <dc:creator>Sun Chuanqi</dc:creator>
      <guid>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-12-network3/</guid>
    </item>
    <item>
      <title>Week 11: Mob-making A Machine</title>
      <link>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-11-mob-making-a-machine/</link>
      <description>&lt;p&gt;This is a group project week. This document captures the tasks I was involved in. See the &lt;a href=&quot;https://fab.cba.mit.edu/classes/MAS.863/CBA/group_assignments/week11/&quot;&gt;group project page&lt;/a&gt; for the full context.&lt;/p&gt;
&lt;h2 id=&quot;labubu&quot;&gt;Labubu&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/MirandaLi/&quot;&gt;Miranda&lt;/a&gt; pitched the idea of building an icosahedron robot that can move based on IMU data. Each face would host a &lt;a href=&quot;https://en.wikipedia.org/wiki/Labubu&quot;&gt;Labubu&lt;/a&gt; that punches out to propel the icosahedron in the desired direction. I&#39;m too old to understand the cultural significance of Labubu, but the idea sounds cool. Towards the end of the project, we replaced Labubu with our professor &lt;a href=&quot;https://ng.cba.mit.edu/&quot;&gt;Neil&lt;/a&gt;&#39;s face. This made the project much more relatable but also raised the stakes considerably.&lt;/p&gt;
&lt;h2 id=&quot;project-organization&quot;&gt;Project Organization&lt;/h2&gt;
&lt;p&gt;The group divided into four sub-teams: MechE, Electronics, Software, and Creative. I focused on Software.&lt;/p&gt;
&lt;p&gt;During the initial project planning, we discussed how to organize the codebase. People originally proposed a formal PR process and branch-based workflow. I wanted to emphasize people over technology and advised that sub-team leaders should be responsible for anticipating dependencies and conflicts with other teams. Communication should happen early. Team leaders should adapt their version control strategy based on their team members&#39; skills and preferences.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Don&#39;t communicate by sharing memory; share memory by communicating.&lt;/p&gt;
&lt;p&gt;— Go Language Design Principle&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I proposed a controversial idea: organize code by people and duplicate code to surface full history at all times. Consider this folder structure:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- PersonA&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - sensing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - networking&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- PersonB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - actuation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - webUI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fundamentally, this is a copy-paste version control system that most professional developers would cringe at. I advocated for this style because we needed to quickly branch and fork each other&#39;s ideas. I expected that 90% of the code in the beginning would be self-contained one-off experiments that wouldn&#39;t evolve later. Exposing everyone&#39;s work in the same branch at the same time provides several benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Easy remixing of each other&#39;s code&lt;/li&gt;
&lt;li&gt;People using AI can reference multiple components from different people and get help with integration&lt;/li&gt;
&lt;li&gt;People&#39;s work becomes a natural source of documentation. History is not buried in git logs.&lt;/li&gt;
&lt;li&gt;No merge conflicts because everyone works in their own folder&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As a caveat, the person sharing code is responsible for discussing incoming breaking changes. The person consuming code is responsible for stating assumptions and expectations.&lt;/p&gt;
&lt;p&gt;This organization worked well initially. During the second half of the project, we concluded that we needed a point of integration. Our final folder structure became:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- integration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - controller&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - web&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- PersonA&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- PersonB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I fully understand this is not how git workflows are supposed to work. But for mob-like student projects, this organization helped us see each other&#39;s code without branching overhead. I would advocate for the same strategy in future projects.&lt;/p&gt;
&lt;h2 id=&quot;division-of-labor&quot;&gt;Division of Labor&lt;/h2&gt;
&lt;p&gt;On the first night, &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/MattiGruener/&quot;&gt;Matti&lt;/a&gt; and I discussed task division. We wanted to create independent modules that could be worked on in parallel and reduce dependencies between modules by defining clear interfaces. This is what we came up with:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sensing (C++)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Understand the format of IMU data from our sensor&lt;/li&gt;
&lt;li&gt;Prepare data on the Xiao before sending to network&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Networking (C++)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;WiFi connection management&lt;/li&gt;
&lt;li&gt;Track IP address of the remote control (PC)&lt;/li&gt;
&lt;li&gt;Send IMU data from Xiao to remote control over UDP&lt;/li&gt;
&lt;li&gt;Receive servo motor number from remote control to Xiao over UDP&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Planning (JavaScript)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use math to convert move front/back/left/right into the correct servo motor number&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Actuation (C++)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Drive the motor based on the received servo motor number&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;UI (JavaScript/HTML/CSS)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a web UI&lt;/li&gt;
&lt;li&gt;Visualize device orientation&lt;/li&gt;
&lt;li&gt;Show buttons to move front/back/left/right&lt;/li&gt;
&lt;li&gt;Show buttons to manually move individual servo motors&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At the end of the project, a similar structure was reflected in our code. This reminded me of Conway&#39;s Law. We can use Conway&#39;s Law to our advantage by asking what kind of teams and collaborations we desire, then modularize our project to maximize that organizational structure.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-11-mob-making-a-machine/media/folder-structure-detailed.webp&quot; alt=&quot;Folder Structure&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Integration Folder Structure&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;software-architecture&quot;&gt;Software Architecture&lt;/h2&gt;
&lt;p&gt;We agreed on a high level architecture: shift as much computation to the PC as possible. It&#39;s much easier to iterate and debug on the PC than on the Xiao. Running the server on PC offered several advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Easier to debug&lt;/li&gt;
&lt;li&gt;Avoid using ESP32 as Wifi access point so laptop can connect to the internet and use generative AI to accelerate coding&lt;/li&gt;
&lt;li&gt;Acknowledged drawback: higher latency because of going through school WiFi&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This concept was reflected in every subsequent decision. The Xiao&#39;s logic is simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Xiao sends low level IMU data to PC: quaternion WXYZ and accelerometer XYZ&lt;/li&gt;
&lt;li&gt;Xiao takes a servo motor number and fully extends it, then retracts it based on predefined PWM sequence&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The PC does the heavy lifting: interpreting IMU data, solving the geometric puzzle, calibrating, and determining which servos to move based on user commands.&lt;/p&gt;
&lt;h2 id=&quot;networking-proof-of-concept&quot;&gt;Networking Proof of Concept&lt;/h2&gt;
&lt;p&gt;We started with UDP over WiFi because I already had a similar system working from &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-8-first-contact/&quot;&gt;Input&lt;/a&gt;/&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-9-machine-melody/&quot;&gt;Output&lt;/a&gt; week for streaming voice. I implemented a few diagnostic programs to test UDP performance.&lt;/p&gt;
&lt;p&gt;First, I wanted to understand the performance characteristics of Xiao ESP32 UDP over WiFi. I wrote a simple Node.js server that echoes any UDP message back to the sender:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; dgram&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;dgram&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; os&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;os&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;dgram&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;createSocket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;udp4&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rinfo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`Received &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; bytes from &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rinfo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rinfo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`currentTime: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;currentTime&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;, latency: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;latency&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`Invalid JSON: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Send back the same message&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rinfo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rinfo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Error sending response:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;listening&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; interfaces&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;os&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;networkInterfaces&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; localIP&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;127.0.0.1&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// fallback&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; iface&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; interfaces&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; addr&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; interfaces&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;iface&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;addr&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;family&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;IPv4&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; !&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;addr&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;internal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        localIP&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;addr&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;localIP&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; !== &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;127.0.0.1&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;break&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`UDP server listening on &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;localIP&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bind&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;41234&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Bind to port 41234&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the ESP32 side, I sent UDP packets in bursts and measured latency during peak load:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &amp;#x3C;WiFi.h&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &amp;#x3C;AsyncUDP.h&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;* WIFI_SSID = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;MLDEV&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;* WIFI_PASSWORD = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;AsyncUDP udp;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IPAddress&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; targetIP&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;192&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;168&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;41&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;229&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; unsigned&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; targetPort = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;41234&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; packetNum = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;unsigned&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; lastLatency = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; burstCount = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; BURST_SIZE = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; setup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;115200&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;mode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(WIFI_STA);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(WIFI_SSID, WIFI_PASSWORD);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  unsigned&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; startTime = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;millis&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  while&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() != WL_CONNECTED &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;millis&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() - startTime &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;300000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    delay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;500&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() != WL_CONNECTED) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;WiFi Failed&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Connected to WiFi&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;IP Address: &quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;localIP&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;udp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;connect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(targetIP, targetPort)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;UDP connected&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    udp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;onPacket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;([](&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AsyncUDPPacket&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; packet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      String msg = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;*)&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;packet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;packet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; colonPos = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;indexOf&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;currentTime&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (colonPos != -&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        colonPos += &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;13&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; commaPos = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;indexOf&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, colonPos);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (commaPos != -&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;          String timeStr = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;substring&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(colonPos, commaPos);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;          unsigned&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; sentTime = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;strtoul&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;timeStr&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;c_str&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;NULL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;          lastLatency = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;millis&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() - sentTime;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      packet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;printf&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Got &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;%u&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; bytes of data&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;packet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (burstCount &amp;#x3C; BURST_SIZE) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    packetNum++;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    burstCount++;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    String json = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;{&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;currentTime&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;millis&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()) + &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;,&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;packetNum&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(packetNum) + &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;,&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;latency&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(lastLatency) + &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;}&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    udp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(json);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Sent: &quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + json);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Sleeping for 1 second...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    delay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    burstCount = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The testing revealed several characteristics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Xiao UDP can send over 1000 Hz to a PC&lt;/li&gt;
&lt;li&gt;The system crashes, presumably due to buffer overflow, when PC sends too much data&lt;/li&gt;
&lt;li&gt;Typical latency: 30-50ms&lt;/li&gt;
&lt;li&gt;Worst latency: 100-200ms&lt;/li&gt;
&lt;li&gt;Best latency: 4-6ms&lt;/li&gt;
&lt;li&gt;Xiao needs to sleep 1ms between sends. Otherwise, it will be blocked from reading packets&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I realized that each person&#39;s laptop has a different IP address. I implemented a simple IP discovery protocol. People can announce their IP address on a server. The ESP32 polls the server to get the latest IP address of the laptop.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-11-mob-making-a-machine/media/ip-discovery.webp&quot; alt=&quot;IP Discovery Tool&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;IP Discovery Tool&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This tool was eventually taken offline due to switching from Wifi to Bluetooth. But I plan to use the technique for my &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/final-project-tangible-adventure/&quot;&gt;final project&lt;/a&gt; in which IP discovery is still needed.&lt;/p&gt;
&lt;h2 id=&quot;interface-first&quot;&gt;Interface First&lt;/h2&gt;
&lt;p&gt;To implement the web server without existing microcontroller code, I encouraged the team to define the networking contract first. The payloads contain Quaternion (w, x, y, z) and Accelerometer (ax, ay, az) readings in newline-delimited JSON:&lt;/p&gt;
&lt;h3 id=&quot;esp32-laptop&quot;&gt;esp32 -&amp;gt; laptop&lt;/h3&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;w&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.875&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;x&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.0243&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;y&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.0393&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;z&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;-0.482&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;ax&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;17.5781&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;ay&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;3.1738&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;az&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1025.3906&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each notification is parsed by &lt;code&gt;bluetooth.js&lt;/code&gt; and forwarded to the UI; &lt;code&gt;threejs-vis.js&lt;/code&gt; consumes the quaternion stream to animate the model.&lt;/p&gt;
&lt;h3 id=&quot;laptop-esp32&quot;&gt;laptop -&amp;gt; esp32&lt;/h3&gt;
&lt;p&gt;General format is JSON with &amp;quot;cmd&amp;quot; and &amp;quot;args&amp;quot; fields. For simplicity, &amp;quot;args&amp;quot; is a string. It is optional.&lt;/p&gt;
&lt;p&gt;Move servo command&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{ &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;&quot;cmd&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;move_servo&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;&quot;args&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;5,12&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reset device command&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{ &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;&quot;cmd&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;reset&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We discussed custom bit packing to reduce bandwidth consumption. I advocated for JSON for simplicity and agreed that we could optimize later if needed. The JSON protocol was eventually superseded by a custom op-code based binary protocol to conserve BLE bandwidth.&lt;/p&gt;
&lt;p&gt;In retrospect, I would still advocate for the JSON protocol with more concise names. The bit packing optimization had marginal gain and made serialization/deserialization much more error prone.&lt;/p&gt;
&lt;h2 id=&quot;server-implementation&quot;&gt;Server Implementation&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/YufengZhao/&quot;&gt;Yufeng&lt;/a&gt; ran the demo code for the Adafruit IMU board and started developing sensor data processing. Thanks to the data format contract, I was able to work in parallel. I mocked the IMU data in a separate Xiao and communicated with a Node.js server over UDP. This is my code that mocks the sensor data:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Mock IMU sensor data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;float&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; gx = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, gy = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, gz = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Gyroscope in degrees&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;float&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; mx = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, my = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, mz = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Compass/Magnetometer in degrees&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; updateMockSensorData&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Update mock IMU data with random changes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  gx += &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;random&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(-&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;11&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) * &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Change by -1.0 to +1.0 degrees&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  gy += &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;random&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(-&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;11&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) * &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  gz += &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;random&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(-&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;11&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) * &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  mx += &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;random&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(-&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;11&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) * &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  my += &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;random&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(-&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;11&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) * &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  mz += &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;random&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(-&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;11&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) * &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Keep values in reasonable ranges&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  gx = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;constrain&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(gx, -&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;180&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;180&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  gy = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;constrain&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(gy, -&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;180&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;180&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  gz = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;constrain&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(gz, -&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;180&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;180&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  mx = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;constrain&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(mx, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;360&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  my = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;constrain&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(my, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;360&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  mz = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;constrain&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(mz, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;360&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; getSensorDataJSON&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Create JSON array format: [gx,gy,gz,mx,my,mz]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;[&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(gx, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) + &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(gy, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) + &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(gz, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) + &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;             + &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(mx, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) + &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(my, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) + &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(mz, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) + &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;]&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;determine-high-level-logic&quot;&gt;Determine High Level Logic&lt;/h2&gt;
&lt;p&gt;Multiple people were adding pieces to the Xiao code. I started a refactoring effort to modularize the code so that the high level logic would be easier to understand. This is the pseudocode I came up with:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  wifi = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;connect_wifi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  laptop_ip = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;discover_laptop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(wifi);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  on_message_received = (message) =&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    handle_reset&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(message);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    handle_servo_command&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(message);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  handle_laptop_udp_message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(wifi, laptop_ip, on_message_received);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  sendor_data = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;read_imu_sensor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  send_udp_message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(wifi, laptop_ip, sendor_data);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I discussed the high level design with the group to ensure everyone shared the same understanding. In theory, the design allows future I/O to plug into the main program without needing to change other modules&#39; code.&lt;/p&gt;
&lt;h2 id=&quot;sensor-integration-test&quot;&gt;Sensor Integration Test&lt;/h2&gt;
&lt;p&gt;The EE team provided the electronics on Friday during the StudCom social tea hour. We uploaded our sketch and started testing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-11-mob-making-a-machine/media/test-02.webp&quot; alt=&quot;Testing UI&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Dumping IMU data to the web UI over UDP&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-11-mob-making-a-machine/media/test-01.webp&quot; alt=&quot;Testing in Tea Party&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Testing against interference in the crowded StudCom tea party&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The results were concerning. In the Media Lab tea party with about 40 people, the device experienced 3+ seconds of latency. The WXYZ quaternion took 12+ seconds to stabilize.&lt;/p&gt;
&lt;p&gt;Bad news: UDP plus WiFi clearly wouldn&#39;t work. Good news: we discovered this early enough. There was still time to pivot. Our communication wasn&#39;t tightly coupled to the sensor logic.&lt;/p&gt;
&lt;h2 id=&quot;the-big-migration&quot;&gt;The Big Migration&lt;/h2&gt;
&lt;p&gt;After testing, I took Miranda&#39;s Bluetooth 2-way communication code and used Claude 4.5 Sonnet to migrate the WiFi plus UDP code to Bluetooth:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Plan step by step, we are going to swap out the wifi + UDP + WebSocket based communication between ESP32 and Node.js with a simpler Bluetooth BLE based communication between ESP32 and the Web page, using Web Bluetooth API.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;The change will include at least the following:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1. Remove UDP and Wifi on both ESP32 and Node.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. Add BLE on both ESP32 and the web page&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. Remove IP discovery code&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;4. Treat the server folder as static. use simple npx command to serve the file and not worry about maintaining a node.js server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;We already have working reference implemention in #file:bluetooth. You can use that code as skeleton.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Keep the ESP32 organized by files, similar to existing structure lib-xx-name.ino; delete files that are no longer in use.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Make sure to carefully map out the migration and execute it with a checklist.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Miraculously, the migration worked on the first try. AI is such a game changer.&lt;/p&gt;
&lt;h2 id=&quot;integrating-servo&quot;&gt;Integrating Servo&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SaetbyeolLeeyouk/&quot;&gt;Saetbyeol&lt;/a&gt; implemented the servo motor control. When I integrated her work, the servo could not respond to commands from the PC. We suspected I/O blocking. After investigation, we realized that the BLE communication loop was blocking:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;isBLEConnected&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    String sensorData = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getSensorJSON&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    sendBLEMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(sensorData);&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; // &amp;#x3C;-- need to disable this line in order to send any command to the ESP32, why?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The solution was to use a timer to send sensor data every 20ms instead of blocking the main loop. The 20ms interval was empirically determined. I wasn&#39;t happy with this solution, as the cool down period could vary depending on unknown factors, but we proceeded and decided to revisit it later if needed.&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; unsigned&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; lastSendTime = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; unsigned&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; SEND_INTERVAL_MS = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; setupSensorTransmission&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  lastSendTime = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Sensor transmission initialized&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; sendSensorDataIfReady&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;isBLEConnected&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  unsigned&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; currentTime = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;millis&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (currentTime - lastSendTime &gt;= SEND_INTERVAL_MS) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    String sensorData = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getSensorJSON&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    sendBLEMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(sensorData);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    lastSendTime = currentTime;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this fix, we got the first full integration where sensors and servos are both working. Here is the celebratory dance:&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-11-mob-making-a-machine/media/servo-01.mp4&quot; controls=&quot;&quot;&gt;&lt;/video&gt;
&lt;strong&gt;Servos dancing while sensors streaming IMU data over BLE&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;debugging-mux-issue&quot;&gt;Debugging MUX Issue&lt;/h2&gt;
&lt;p&gt;We tested driving multiple servos through the MUX PWM PCA9685 board. The code behaved erratically. We discovered two issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The MUX board was very sensitive. Touching it with a hand could trigger weird behavior.&lt;/li&gt;
&lt;li&gt;Our code didn&#39;t fully implement the MUX behavior.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Matti directed us to run the &lt;a href=&quot;https://www.adafruit.com/product/815?srsltid=AfmBOopC6kYK4gODFQ-fP-w5XOa5UHXWy5K4ZIimy-BltCTrpr54ms1B&quot;&gt;Adafruit official MUX PWM PCA9685&lt;/a&gt; library code. We confirmed that the board wiring was correct and all servo motors were functional. We solved the issue by using the Adafruit official library &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-11-mob-making-a-machine/(https:/github.com/adafruit/Adafruit-PWM-Servo-Driver-Library/blob/master/examples/servo/servo.ino)&quot;&gt;example code&lt;/a&gt; as a starting point.&lt;/p&gt;
&lt;p&gt;Matti also discovered how to serial connect two MUX boards by soldering the address pin to set one board at 0x41 instead of 0x40.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-11-mob-making-a-machine/media/mux.webp&quot; alt=&quot;PCA9685 MUX Board&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Pads for changing the address of PCA9685 MUX Board&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The base address is 0x40. The EE team would later solder A3 (0x08) for 0x48 and A5 (0x20) for 0x60. Had we known sooner, we could have matched their addressing scheme in our dev boards.&lt;/p&gt;
&lt;h2 id=&quot;stress-testing-ble-communication&quot;&gt;Stress Testing BLE Communication&lt;/h2&gt;
&lt;p&gt;We discovered that rapidly sending BLE messages would cause the connection to drop. Matti suggested using different TX characteristics for namespaced communication. This would save bandwidth by eliminating command names.&lt;/p&gt;
&lt;h2 id=&quot;systematic-testing-of-ble-in-web-bluetooth-api&quot;&gt;Systematic Testing of BLE in Web Bluetooth API&lt;/h2&gt;
&lt;p&gt;Sending characters at high frequency triggered an error:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;sendInterval&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setInterval&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; encoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; TextEncoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;encoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;encode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; charTx&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;writeValue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`TX: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`SEND ERROR: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    stopSending&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;interval&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Error code from the Web Bluetooth API:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;SEND ERROR: GATT operation already in progress.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This indicated that flow control was needed. On the browser side, we could throttle or buffer the messages. I reasoned through the options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Throttling could help, but throughput is environment dependent. We would end up being very conservative and losing performance.&lt;/li&gt;
&lt;li&gt;Buffering made more sense. We just needed to ensure only one transmission at a time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I solved flow control with a queue-based scheduler. Using the RxJS &lt;a href=&quot;https://rxjs.dev/api/operators/mergeMap&quot;&gt;mergeMap&lt;/a&gt; operator, I could easily toggle between single-thread mode and unrestricted concurrency mode:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; concurrency&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;useScheduler&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ? &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;undefined&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; send$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;interval&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;intervalMs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  takeUntil&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;stopBrowserSend$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  tap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; browserQueueSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;++),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  mergeMap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; encoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; TextEncoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;encoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;encode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; charTx&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;writeValue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    browserQueueSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;--;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    messageSent$&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`[Test 1] TX: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;concurrency&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Based on this idea, I implemented a comprehensive diagnostic tool to profile the BLE performance.&lt;/p&gt;
&lt;h2 id=&quot;performance-characterization&quot;&gt;Performance Characterization&lt;/h2&gt;
&lt;p&gt;The tool allows users to measure latency and throughput for varying message sizes. I was able to confirm, the scheduler was able to max out the throughput without triggering errors.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-11-mob-making-a-machine/media/ble-test.mp4&quot; controls=&quot;&quot;&gt;&lt;/video&gt;
&lt;strong&gt;BLE benchmarking in action&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;best-case-side-by-side-in-same-room&quot;&gt;Best Case: Side by Side in Same Room&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Bandwidth:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Browser to ESP32: 14 messages/sec&lt;/li&gt;
&lt;li&gt;ESP32 to Browser: 100 messages/sec&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Latency:&lt;/strong&gt; 92ms average, min: 84ms, max: 140ms&lt;/p&gt;
&lt;p&gt;Removing the antenna did not reduce performance at close range. However, as I walked away, performance dropped quickly. The connection was lost at 5 meters.&lt;/p&gt;
&lt;h3 id=&quot;at-distance-of-30-meters-through-one-glass-wall&quot;&gt;At Distance of 30 Meters Through One Glass Wall&lt;/h3&gt;
&lt;p&gt;Unable to establish a new connection at this distance, but could maintain a previously established connection to 30 meters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bandwidth:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Browser to ESP32: 8 messages/sec&lt;/li&gt;
&lt;li&gt;ESP32 to Browser: 50 messages/sec&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Latency:&lt;/strong&gt; 250ms average, min: 89ms, max: 540ms&lt;/p&gt;
&lt;h3 id=&quot;realistic-usage-inside-metal-icosahedron-structure-at-10-meters-distance&quot;&gt;Realistic Usage: Inside Metal Icosahedron Structure at 10 Meters Distance&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Bandwidth:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Browser to ESP32: 14 messages/sec&lt;/li&gt;
&lt;li&gt;ESP32 to Browser: 100 messages/sec&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Latency:&lt;/strong&gt; 230ms average, min: 157ms, max: 332ms&lt;/p&gt;
&lt;h2 id=&quot;integrate-scheduler&quot;&gt;Integrate Scheduler&lt;/h2&gt;
&lt;p&gt;The diagnostic tool used RxJS. For production, I wanted to avoid adding more libraries. I implemented a standalone scheduler that uses a queue to ensure single threaded execution of tasks. I wanted the scheduler to be hidden behind the bluetooth module so the caller wouldn&#39;t have to worry about scheduling. Multiple callers of the bluetooth module would be scheduled in a first-in, first-out manner.&lt;/p&gt;
&lt;p&gt;Scheduler implementation:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; AsyncTaskScheduler&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;queue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;isProcessing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  /**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   * Add a task to the queue and process it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   * &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;@param&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; {Function}&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; taskFn&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; - Async function to execute&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   * &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;@returns&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; {Promise}&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; - Resolves when task completes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  async&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; enqueue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;taskFn&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;reject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      this&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;queue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;taskFn&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;reject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      this&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;processQueue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  /**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   * Process the queue sequentially&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  async&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; processQueue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // If already processing, return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;isProcessing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // If queue is empty, return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;queue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;isProcessing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;queue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &gt; &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;taskFn&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;reject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;queue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;shift&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; taskFn&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        resolve&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Task failed in scheduler:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        reject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;        // Continue processing the rest of the queue despite the error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;isProcessing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  /**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   * Clear all pending tasks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  clear&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Reject all pending tasks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;queue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &gt; &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;reject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;queue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;shift&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      reject&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; Error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Queue cleared&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  /**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   * Get queue size&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   * &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;@returns&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; {number}&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; - Number of pending tasks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  get&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; size&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;queue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  /**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   * Check if scheduler is currently processing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   * &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;@returns&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; {boolean}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  get&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; busy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;isProcessing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the scheduler in place, I was able to dispatch commands at high speed without causing BLE transmission errors.&lt;/p&gt;
&lt;h2 id=&quot;reflection&quot;&gt;Reflection&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The bearing of a child takes nine months, no matter how many women are assigned.&lt;/p&gt;
&lt;p&gt;— Fred Brooks, &lt;em&gt;The Mythical Man-Month&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&#39;s tempting to add more process and throw more people at problems. In this project, I found it most effective when two-people micro-teams pair programmed to solve one problem. I saw this working very well between Matti and Miranda, and between Saetbeyol and me. This reinforced the importance of decomposing problems into smaller chunks that can be solved by small teams in parallel.&lt;/p&gt;
&lt;p&gt;This project also challenged our conventional wisdom about formal software development. Version control, testing, code review, CI/CD, TypeScript, ES6 modules, linting, formatting, and design patterns were all thrown out the window. Maybe for the better. I&#39;m always fascinated by emerging practices under extreme constraints. In this project, I&#39;m convinced that less is more when the timeline is short and the skill levels are diverse.&lt;/p&gt;
&lt;h2 id=&quot;appendix&quot;&gt;Appendix&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-11-mob-making-a-machine/code/ip-discovery.zip&quot;&gt;IP Discovery code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-11-mob-making-a-machine/code/bluetooth-benchmark.zip&quot;&gt;Bluetooth Benchmark tool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-11-mob-making-a-machine/code/scheduler.js&quot;&gt;BLE Transmission Scheduler&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Tue, 18 Nov 2025 00:00:00 GMT</pubDate>
      <dc:creator>Sun Chuanqi</dc:creator>
      <guid>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-11-mob-making-a-machine/</guid>
    </item>
    <item>
      <title>Week 10: Neil&#39;s Law</title>
      <link>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/</link>
      <description>&lt;blockquote&gt;
&lt;p&gt;Hofstadter&#39;s law: It always takes longer than you expect, even when you take into account Hofstadter&#39;s law.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Neil&#39;s law: You will cast the opposite of what you expect, even after learning about Neil&#39;s law.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;group-assignment&quot;&gt;Group Assignment&lt;/h2&gt;
&lt;p&gt;This week, I joined &lt;a href=&quot;https://fabacademy.org/2023/labs/kamakura/students/ekaterina-kormilitsyna/&quot;&gt;Kat&lt;/a&gt;&#39;s training session to learn about molding and casting. She showed us several examples covering molding, casting techniques, and materials. I also reviewed Safety Data Sheets (SDS) for two materials by Smooth-On: Oomoo 30 silicone rubber and Smooth-Cast 300 plastic. Understanding material properties and safety protocols is essential when working with these chemicals.&lt;/p&gt;
&lt;p&gt;See additional documentation in the &lt;a href=&quot;https://fab.cba.mit.edu/classes/MAS.863/CBA/group_assignments/week10/&quot;&gt;group notes&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;manual-particle-accelerator&quot;&gt;Manual Particle Accelerator&lt;/h2&gt;
&lt;p&gt;Who doesn&#39;t like particle physics? Smashing particles together at near light speed could make cool &lt;a href=&quot;https://home.cern/news/series/lhc-physics-ten/recreating-big-bang-matter-earth&quot;&gt;images&lt;/a&gt; and win you a &lt;a href=&quot;https://en.wikipedia.org/wiki/Higgs_boson&quot;&gt;Nobel prize&lt;/a&gt;. Worried about creating a black hole? That&#39;s been &lt;a href=&quot;https://home.cern/resources/faqs/will-cern-generate-black-hole&quot;&gt;debunked&lt;/a&gt;. In this project, you can safely accelerate a particle by hand!&lt;/p&gt;
&lt;p&gt;I imagined a torus shaped device. It hurts my brain to imagine the positive/negative relationships involved in casting. I found sketching an effective way to develop the concept.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/concept.webp&quot; alt=&quot;Concept sketch&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Concept sketch&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After sketching out the positive/negative and hard/soft material relationships, I was ready to start fabrication. The design called for two shell components that, when closed like a clam, would form a torus in which you could accelerate a metal ball by shaking it. Similar to how &lt;a href=&quot;https://en.wikipedia.org/wiki/Automatic_watch&quot;&gt;self-winding watch&lt;/a&gt; feels.&lt;/p&gt;
&lt;h3 id=&quot;the-cam-debacle&quot;&gt;The CAM Debacle&lt;/h3&gt;
&lt;p&gt;My initial plan was simple and naive: CNC wax mold → plastic part. I&#39;ve been using Onshape for its Linux compatibility, but learning from &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/&quot;&gt;Week 7&lt;/a&gt; that using other people&#39;s Fusion 360 for CAM adds significant overhead, I decided to learn FreeCAD CAM this week to have a personalized workflow.&lt;/p&gt;
&lt;p&gt;I studied two reference tutorials to understand the CAM capabilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://academy.cba.mit.edu/classes/computer_machining/3DRough.mp4&quot;&gt;3D pocketing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://academy.cba.mit.edu/classes/computer_machining/AdaptiveRest.mp4&quot;&gt;Adaptive clearing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Attempt 1: Onshape Modeling → FreeCAD CAM&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I started by measuring the stock size so I could size the parts accordingly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/wax-01.webp&quot; alt=&quot;Stock measurement&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Stock measurement&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Width: 76.45mm&lt;/li&gt;
&lt;li&gt;Length: 87.45mm&lt;/li&gt;
&lt;li&gt;Height: 38.21mm&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I modeled the parts in Onshape and imported them into FreeCAD for toolpath generation. I found an effective workflow that uses a boolean operation to simulate the casting.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/simulate-casting.webp&quot; alt=&quot;Simulate casting&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Simulated casting using the subtract boolean operation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A quick boolean operation against stock geometry immediately revealed a design flaw: I had edges without thickness, which would be impossible to mill.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/simulated-casting-issue.webp&quot; alt=&quot;Poor modeling&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Boolean operation reveals design flaw&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After fixing the geometry, I managed to set up the machine job by creating tool bits based on example endmill shapes and modifying the JSON configuration, following this &lt;a href=&quot;https://www.youtube.com/watch?v=ER1wUvfIswk&quot;&gt;YouTube tutorial&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;version&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;1/8 inch Endmill&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;shape&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;endmill.fcstd&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;parameter&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    &quot;CuttingEdgeHeight&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;30.0000 mm&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    &quot;Diameter&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;3.1750 mm&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    &quot;Length&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;50.0000 mm&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    &quot;ShankDiameter&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;3.0000 mm&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;attribute&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, a new problem emerged. The toolpath algorithm couldn&#39;t fit the bit in the narrow, ring-like pocket geometry.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/tool-path-issue-01-mods.webp&quot; alt=&quot;Toolpath issue&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Toolpath with excessive vertical movement&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Increasing the ring width to be significantly greater than the tool diameter would solve the problem, but I didn&#39;t want to compromise the design. Additionally, FreeCAD&#39;s multiple toolpath feature proved very buggy. After generating the first toolpath, producing paths for the remaining material simply had no effect: subsequent toolpaths would start over from the surface of the stock.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Attempt 2: FreeCAD Modeling → FreeCAD CAM&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Thinking the import process might be causing issues, I decided to model directly in FreeCAD.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/cnc-mold-freecad.webp&quot; alt=&quot;Modeling in freecad&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Modeling in FreeCAD&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The transition from Onshape to FreeCAD was rough but manageable given that they share a similar mental model. I finished the mold design and tested it in CAM, but the multiple toolpath issues persisted.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/tool-path-issue-01-freecad.webp&quot; alt=&quot;Toolpath issue with FreeCAD&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Toolpath missing half of the geometry&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Attempt 3: Switching to Mods&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Frustrated with FreeCAD&#39;s CAM limitations, I switched to &lt;a href=&quot;https://modsproject.org/&quot;&gt;Mods&lt;/a&gt; for toolpath generation, using the &lt;code&gt;G-code/mill 2.5D stl&lt;/code&gt; program. Unfortunately, Mods couldn&#39;t generate sequential toolpaths that build on each other. Worse, I realized that my U-pipe geometry would require a ball endmill, and adding custom bit geometry would take too long to implement.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/tool-path-issue-03-resolved-3_4-gap.webp&quot; alt=&quot;Toolpath issue with Mods&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Excessive horizontal movement in Onshape model&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/mods-path-issue.webp&quot; alt=&quot;Toolpath issue with Mods&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Incomplete paths in FreeCAD model&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Mods failed to generate proper toolpaths with issues similar to what I experienced in FreeCAD. At this point, I suspected fundamental problems with my modeling approach rather than toolpath algorithms.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Summary of Failed Attempts&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CAD Software&lt;/th&gt;
&lt;th&gt;CAM Software&lt;/th&gt;
&lt;th&gt;Issues Encountered&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Onshape&lt;/td&gt;
&lt;td&gt;FreeCAD&lt;/td&gt;
&lt;td&gt;Unnecessary vertical travels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FreeCAD&lt;/td&gt;
&lt;td&gt;FreeCAD&lt;/td&gt;
&lt;td&gt;Ibid, and missing toolpaths on half of the ring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Onshape&lt;/td&gt;
&lt;td&gt;Mods&lt;/td&gt;
&lt;td&gt;Unnecessary horizontal back-and-forth travels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FreeCAD&lt;/td&gt;
&lt;td&gt;Mods&lt;/td&gt;
&lt;td&gt;Ibid, and missing toolpaths on half of the ring&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;After spending considerable time troubleshooting, I needed to embrace pragmatism. In the spirit of supply-driven project management, I decided to move forward with something I could make real progress on: 3D printing the mother mold instead of CNC milling wax.&lt;/p&gt;
&lt;h3 id=&quot;table-of-contents-literally&quot;&gt;Table of Contents, Literally&lt;/h3&gt;
&lt;p&gt;Before diving into the technical details, I want to show the physical manifestation of this week&#39;s iterative process. I laid out all my artifacts on a table. Each row visualizes an iteration cycle from mother mold to a point where I either reached a milestone or learned from a mistake.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/toc-01.webp&quot; alt=&quot;Table of contents&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;v1 through v7 of molding and casting using 3D printing&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;v1-perfectly-making-the-wrong-thing&quot;&gt;V1: Perfectly Making The Wrong Thing&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;First Iteration: PLA Mother Mold&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I slightly modified the CNC model to be the mother mold, sliced it with PrusaSlicer, and printed it in PLA.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v1-mold.webp&quot; alt=&quot;v1 mold&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Modeling the mold&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The print came out clean despite pronounced layer lines. I wanted to cast it first to see how bad the surface finish would be. Let&#39;s try Smooth-On Oomoo 30 silicone rubber.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v1-01.webp&quot; alt=&quot;v1 cast&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Gathering materials for casting&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v1-02.webp&quot; alt=&quot;v1 cast&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Estimating the amount of silicone rubber needed using water&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I noticed that part B has much higher density than part A. It makes sense to add A first so part B can sink and improve the mixing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v1-03.webp&quot; alt=&quot;v1 cast&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Before mixing, clear separation between part A and B&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v1-04.webp&quot; alt=&quot;v1 cast&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;After mixing, the color is uniform&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Using a small cup was a mistake. A larger cup would make mixing much easier.&lt;/p&gt;
&lt;p&gt;Pouring is a battle against bubbles and requires perfect balance: too fast, you could spill or introduce bubbles. Too slow, it could drip and also cause bubbles. Too high, it could splash and make bubbles. Too low, the bubbles wouldn&#39;t be able to stretch and pop before entering the mold.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v1-05.webp&quot; alt=&quot;v1 cast&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Pouring in action&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The curing process requires keeping the mold undisturbed for 6 hours.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v1-06.webp&quot; alt=&quot;v1 cast&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Curing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The result looks great, except... I cast the opposite of what I wanted. I knew Neil warned us in the lecture about making such a mistake. How on earth did I still manage to do it? In retrospect, I subconsciously believed that I could cast directly from the 3D printed mold. If I were able to cast hard material from the 3D printed mold, the outcome would be correct.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v1-07.webp&quot; alt=&quot;v1 cast&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Good but wrong result&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The result also confirmed the surface finish issue. The layer lines transferred to the silicone mold and would ultimately transfer to the final cast. I needed to smooth the surface, so I considered a few options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Resin coating&lt;/strong&gt;: Kat warned that resin inhibits silicone curing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wax coating&lt;/strong&gt;: Wax melts PLA, requiring a switch to PETG&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Acetone vapor smoothing&lt;/strong&gt;: Only works for ABS, not PLA or PETG&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sanding and polishing&lt;/strong&gt;: Labor intensive and inconsistent results&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By elimination, I decided to try wax coating with PETG for the next iteration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;V2: PETG with Wax Coating&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I started V2 before realizing the positive/negative issue. Since the goal was to characterize surface treatment methods, I proceeded with the PETG print with the wrong geometry. At least I would be able to compare identical geometries across different surface treatments.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v2-01.webp&quot; alt=&quot;v2 print&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;3D printed PETG mold&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I used a heat gun to melt wax pellets and brushed them onto the PETG mold.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v2-wax.webp&quot; alt=&quot;Wax&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Wax pellets&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v2-02.webp&quot; alt=&quot;Brushing&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Brushing wax onto the PETG mold&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The final step involved applying heat to re-melt the wax in the mold and drain the excess. Unfortunately, I warped the PETG during the drain process when the material was still hot and pliable.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v2-03.webp&quot; alt=&quot;Warped mold&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Warped PETG mold&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Despite the warping, I proceeded to cast the silicone mold. The results were disappointing. The wax coating couldn&#39;t eliminate the layer lines, and worse, it destroyed the sharp edges that were critical to my design.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v2-04.webp&quot; alt=&quot;Wax result comparison&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Left: PLA without surface treatment. Right: PETG with wax coating.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;v3-v5-pushing-the-limit-of-pla&quot;&gt;V3-V5: Pushing The Limit of PLA&lt;/h3&gt;
&lt;p&gt;I switched back to PLA and recalled a feature from the 3D printing assignment: ironing. This could potentially smooth the top surface without requiring post-processing. I explored several tweaks to optimize the print quality:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Added ironing at 10% flow and 0.1mm gap for surface smoothing&lt;/li&gt;
&lt;li&gt;Reduced layer height to 0.05mm&lt;/li&gt;
&lt;li&gt;Switched to concentric infill&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/prusa-setup.webp&quot; alt=&quot;Prusa Ironing&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Setup for ironing in PrusaSlicer&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The concentric infill change had an unexpected benefit of significantly speeding up the print. My geometry is circular, so concentric infill minimizes travel moves compared to the default rectilinear pattern.&lt;/p&gt;
&lt;p&gt;I tested various ironing parameters to find the optimal settings:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/infill-iron-test.webp&quot; alt=&quot;Characterizing ironing and infill&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Setting up different processes in one job&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/infill-iron-02.webp&quot; alt=&quot;Print result&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;2 by 2 grid of results&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Position&lt;/th&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Top left&lt;/td&gt;
&lt;td&gt;Monotonic line infill&lt;/td&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top right&lt;/td&gt;
&lt;td&gt;Concentric infill&lt;/td&gt;
&lt;td&gt;Cleaner, faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bottom left&lt;/td&gt;
&lt;td&gt;Ironing: 0.15mm spacing, 15% flow&lt;/td&gt;
&lt;td&gt;Good improvement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bottom right&lt;/td&gt;
&lt;td&gt;Ironing: 0.1mm spacing, 10% flow&lt;/td&gt;
&lt;td&gt;Best surface finish&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In summary, everything we need to know to improve 3D printing is already captured in the culinary wisdom:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Low and slow.&lt;/p&gt;
&lt;p&gt;-- Texas BBQ pitmasters&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Low layer height and slow ironing did the trick. I observed much better interior layers thanks to concentric infill:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v4-01.webp&quot; alt=&quot;Ironing and concentric infill&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Ironing and concentric infill in action&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here is the full mold using the ironing and concentric infill settings. Unfortunately, the 3D printer had some issues extruding consistently despite my tuning of the temperature. It was not as good as my characterization test from another printer.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v5-01.webp&quot; alt=&quot;Ironed mold&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Ironed mold, sub-optimal surface and rough wall texture&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When casting the silicone mold from the new PLA mother mold, I used a glass plate to press down the backside of the rubber as it cured, creating a flat surface to make the final casting easier to level.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v5-03.webp&quot; alt=&quot;Leveling the surface&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Leveling the surface&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This made a perfectly flat rubber surface, but it also created a vacuum suction that made it very difficult to remove it from the glass. I would not recommend this technique to others.&lt;/p&gt;
&lt;p&gt;Due to the rough wall texture, the silicone mold adhered strongly to the PLA mother mold. During demolding, I tore the wall apart from the rubber base.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v5-04.webp&quot; alt=&quot;Damaged mold&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Damaged silicone mold after demolding&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v5-wall-issue.webp&quot; alt=&quot;Wall visualized in model&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;The right-most gutter made demolding very difficult&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I wanted to proceed with the final casting to gain more experience and reveal other potential issues.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v5-05.webp&quot; alt=&quot;3D printed support&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;3D printed support ring to hold the damaged mold in shape&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;With the support ring, I was able to cast the final parts. The Smooth-On Smooth-Cast 300 plastic came with the instruction that you should stir or shake the bottles before mixing. That was a very bad idea. After shaking, the mixture was full of bubbles that would not go away. I had to switch to another bottle while letting those bubbles dissipate over time.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v5-06.webp&quot; alt=&quot;Casting&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Casting the final parts despite the damaged mold (left)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I attempted to remove bubbles using a vacuum chamber, but it backfired. The vacuum caused surface roughness on the bottom side of the mold that would be visible in the final part.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v5-02.webp&quot; alt=&quot;Vacuum chamber&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Using vacuum chamber to remove bubbles&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This marks the first production of the final parts. Good news is that the geometry is correct this time.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v5-07.webp&quot; alt=&quot;Validating&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Design validated&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To make the molds ready for assembly, I deburred the edges and sanded down rough surfaces.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v5-11.webp&quot; alt=&quot;Deburring&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Deburring the casted parts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For fun, I hand-painted some graphic details with a sharpie. It was clear that vacuum processing was not only unnecessary but also detrimental to surface quality.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v5-10.webp&quot; alt=&quot;Side without bubbles&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Smooth side without vacuum processing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v5-09.webp&quot; alt=&quot;Side with bubbles&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Rough side due to vacuum processing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The two sides didn&#39;t fit very well due to an inconsistent interface. I had to belt-sand the edges and deburr the groove to make them fit. I also repeatedly coupled and decoupled the two parts until they finally fit smoothly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v5-12.webp&quot; alt=&quot;In context&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;How hard should I shake to reach 99% the speed of light?&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;v6-v7-details&quot;&gt;V6-V7: Details&lt;/h3&gt;
&lt;p&gt;During these final iterations, I made the following improvements based on everything observed in previous versions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add chamfer to mother mold for easier demolding&lt;/li&gt;
&lt;li&gt;Increase silicone base thickness to prevent tearing&lt;/li&gt;
&lt;li&gt;Add fillets to interface for easier assembly&lt;/li&gt;
&lt;li&gt;Reduced coupling surface for easier assembly&lt;/li&gt;
&lt;li&gt;Use the highest quality PLA printer available in the lab&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v7-00.webp&quot; alt=&quot;Final model&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Simplified model with chamfers and fillets&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Using the highest level setting, it took 5 hours to print the mold. It was the highest quality print I&#39;ve ever made in this class.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v7-02.webp&quot; alt=&quot;Final mold&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Incredible surface finish&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Demolding and casting was smooth-sailing thanks to all the mistakes and lessons learned in previous iterations. The only issue is that due to the improved print quality, the fit between the two parts became a bit loose. I would have to adjust the model for future versions.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/v7-01.webp&quot; alt=&quot;Final assembly&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Mother mold, silicone mold, and final cast assembly&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I deeply enjoyed this project. It could even become a final project if I add a digital frequency analyzer and laser-cut graphic masks.&lt;/p&gt;
&lt;h3 id=&quot;reflections&quot;&gt;Reflections&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Sequential Workflows Demand Pipelining&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The mother mold → silicone mold → plastic cast workflow is inherently sequential, where any failure causes significant rework. However, by staggering multiple iterations in parallel, I could manage the long iteration cycles effectively and continuously integrate feedback into the next iterations. If I had worked in a strictly single-threaded manner, this project would have taken more than 60 hours.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Emotional Factors in Decision Making&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;During one 3D print that was near completion, I realized I should have added fillets to the interface geometry. But I couldn&#39;t bring myself to stop the job. It felt as if I was terminating a life. I had fallen victim to the sunk cost fallacy as well as anthropomorphizing objects.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don&#39;t Trust Yourself&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I mistakenly flipped the positive/negative relationship, even after being fully aware of Neil&#39;s warning from the lecture. This taught me that sometimes it&#39;s better to have others check your work than to trust your own brain, especially when you&#39;ve been staring at the same design for hours.&lt;/p&gt;
&lt;h2 id=&quot;appendix&quot;&gt;Appendix&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/model/infill-ironing-test.3mf&quot;&gt;Infill and ironing slicer file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/model/model-for-printing.zip&quot;&gt;Printable model and slicer file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/model/model-for-milling.FCStd&quot;&gt;CNC model (failed to CAM)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;steps-to-reproduce-freecam-cam-toolpath-issue&quot;&gt;Steps to Reproduce FreeCAM CAM Toolpath Issue&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Download &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/model/model-for-milling.FCStd&quot;&gt;model.FCStd&lt;/a&gt;, open it in FreeCAD (v1.0.2)&lt;/li&gt;
&lt;li&gt;Switch to the CAM workbench&lt;/li&gt;
&lt;li&gt;Create a CAM Job for the &lt;code&gt;female-part&lt;/code&gt; solid, with the following changes from the default
&lt;ul&gt;
&lt;li&gt;stock: &lt;strong&gt;Create Box&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;tool: &lt;strong&gt;1/8 inch Endmill&lt;/strong&gt; (download &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/model/toolbit.zip&quot;&gt;toolbit.zip&lt;/a&gt; and add 1/8&amp;quot; endmill to job)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;Model-female-part.Face1&lt;/code&gt; (the top facing surface of the outer-most gutter ring on the solid) and create 3D pocket toolpath, with the following changes from default
&lt;ul&gt;
&lt;li&gt;Operation/Pattern: &lt;strong&gt;Offset&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Min Travel: &lt;strong&gt;checked&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Use Rest Machining: &lt;strong&gt;checked&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Apply and visualize the toolpath&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Expected&lt;/strong&gt;: progressively deepening circular motion along the outer-most ring&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Actual&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Missing toolpaths on half of the ring&lt;/li&gt;
&lt;li&gt;Excessive vertical movements&lt;/li&gt;
&lt;li&gt;Unwanted toolpaths for inner rings&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/media/bug-report-screenshot.webp&quot; alt=&quot;Bug screenshot&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;FreeCAD screenshot of the 3 issues&lt;/strong&gt;&lt;/p&gt;
</description>
      <pubDate>Mon, 10 Nov 2025 00:00:00 GMT</pubDate>
      <dc:creator>Sun Chuanqi</dc:creator>
      <guid>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-10-neils-law/</guid>
    </item>
    <item>
      <title>Week 9: Machine Melody</title>
      <link>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-9-machine-melody/</link>
      <description>&lt;h2 id=&quot;group-assignment&quot;&gt;Group Assignment&lt;/h2&gt;
&lt;p&gt;This week&#39;s theme is output devices. Since my final project involves a speaker, I invited our group to measure the power consumption of a speaker. We characterized the speaker to understand its how power consumption might be influenced. The detailed findings are documented in our &lt;a href=&quot;https://fab.cba.mit.edu/classes/MAS.863/CBA/group_assignments/week9/&quot;&gt;group notes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-9-machine-melody/media/group-01.webp&quot; alt=&quot;Group activity&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Ceci explaining multimeter setup&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;give-ai-a-voice&quot;&gt;Give AI A Voice&lt;/h2&gt;
&lt;p&gt;In the &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-8-first-contact/&quot;&gt;previous week&lt;/a&gt;, I built a wireless microphone system that streams audio from an ESP32 to my laptop, where OpenAI generates speech responses. However, the generated voice was only playing on my laptop. This week, I wanted to complete the loop by streaming the synthesized voice back to the ESP32, creating a true wireless voice interaction system.&lt;/p&gt;
&lt;h3 id=&quot;playing-sine-wave&quot;&gt;Playing Sine Wave&lt;/h3&gt;
&lt;p&gt;I started by testing basic audio output using the Arduino Audio Tools &lt;a href=&quot;https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/examples-stream/streams-generator-i2s/streams-generator-i2s.ino&quot;&gt;example code&lt;/a&gt; to play a sine wave through the I2S DAC. This simple test would verify that my speaker hardware and connections were working correctly. The following program generates and plays a simple sine wave tone:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;AudioTools.h&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#define&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; I2S_BCLK D0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#define&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; I2S_DOUT D1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#define&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; I2S_LRC  D2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#define&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; I2S_DIN  D10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; frequency = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;440&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; sampleRate = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;44100&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AudioInfo&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; info&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;sampleRate&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;SineWaveGenerator&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int16_t&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sineWave&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;4000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; // sine wave with max amplitude of 4000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;GeneratedSoundStream&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int16_t&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sound&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;sineWave&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; // stream generated from sine wave&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;I2SStream out;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;StreamCopy&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; copier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;out&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;sound&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; // copies sound into i2s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; setup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;115200&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  AudioToolsLogger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(Serial, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AudioToolsLogLevel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::Info);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Starting I2S...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  auto&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; config = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;out&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;defaultConfig&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(TX_MODE);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  config&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copyFrom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(info);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  config&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_bck&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = I2S_BCLK;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  config&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_ws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = I2S_LRC;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  config&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = I2S_DIN;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  out&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(config);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  sineWave&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(info, frequency);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Started sine wave playback&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  copier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When I ran this code, I heard loud clicking sounds instead of a clean tone. I systematically experimented with different sampling rates to identify the source of the artifacts:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sample Rate (kHz)&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;44.1&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-9-machine-melody/media/audio-44_1kHz.mp3&quot;&gt;Pulsing sound artifact&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;td&gt;Same artifact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;Reduced artifact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;No artifact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-9-machine-melody/media/audio-22kHz.mp3&quot;&gt;No artifact&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I made several key observations during testing. Opening the serial port seemed to correlate positively with noise artifacts. Higher sampling frequencies also correlated with more artifacts, though this could be confounded by the fact that higher sampling rates mean higher data transmission rates.&lt;/p&gt;
&lt;p&gt;To identify the root cause, I conducted follow-up experiments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hypothesis 1:&lt;/strong&gt; The microphone on the device was causing interference since it shares BCLK and LRC lines with the speaker.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Experiment:&lt;/strong&gt; Unplugging the microphone should reduce or remove noise.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Result:&lt;/strong&gt; No effect observed.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hypothesis 2:&lt;/strong&gt; Serial port communication was causing interference.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Experiment:&lt;/strong&gt; Switching from a data-passing USB-C cable to a power-only cable, and removing all serial communication in the code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Result:&lt;/strong&gt; Noise reduced but not eliminated.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt; Over-sampling could cause noise artifacts, and serial communication could exacerbate the issue. For clean audio output, I needed to use lower sampling rates and minimize serial communication during playback.&lt;/p&gt;
&lt;h3 id=&quot;stream-sine-wave-from-computer-to-device&quot;&gt;Stream Sine Wave from Computer to Device&lt;/h3&gt;
&lt;p&gt;With local audio playback working, the next step was to stream audio from my computer to the ESP32. I started with the &lt;a href=&quot;https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/examples-communication/udp/communication-udp-receive/communication-udp-receive.ino&quot;&gt;sample code&lt;/a&gt; for receiving audio over UDP. On the server side, I wrote Node.js code to generate and stream a sine wave. This setup would help me verify the network streaming pipeline before adding the complexity of OpenAI voice synthesis.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Client&lt;/strong&gt; (other logic omitted for brevity)&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;AudioTools.h&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;AudioTools/Communication/UDPStream.h&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// ... pin definitions ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; *ssid = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; *password = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; SAMPLE_RATE = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;22000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; CHANNELS = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; BITS_PER_SAMPLE = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; UDP_PORT = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;8888&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AudioInfo&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; info&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;SAMPLE_RATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;CHANNELS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;BITS_PER_SAMPLE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;I2SStream i2s;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;           // I2S output to speaker&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;UDPStream&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; udp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ssid&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;password&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;StreamCopy&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; copier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;i2s&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;udp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1024&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; // copy UDP stream to I2S&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; setup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // ... WiFi connection ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Start I2S with custom pinout for speaker output&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  auto&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; i2sCfg = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i2s&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;defaultConfig&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(TX_MODE);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  i2sCfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copyFrom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(info);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  i2sCfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_bck&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = I2S_BCLK;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  i2sCfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_ws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = I2S_LRC;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  i2sCfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = I2S_DIN;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  i2sCfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i2s_format&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = I2S_STD_FORMAT;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  i2s&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(i2sCfg);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Start UDP receiver&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  udp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(UDP_PORT);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  copier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Server&lt;/strong&gt; (other logic omitted for brevity)&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; SAMPLE_RATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;22000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; PACKET_SIZE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1024&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// bytes per UDP packet&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; FREQUENCY&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;440&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Hz (A4 note)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; startSineWaveStream&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; phase&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; samplesPerPacket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;PACKET_SIZE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// 2 bytes per sample (16-bit)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; phaseIncrement&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = (&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; * &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Math&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;PI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; * &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;FREQUENCY&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) / &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SAMPLE_RATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  setInterval&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    () &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;generateSineWaveBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;phase&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;samplesPerPacket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;phaseIncrement&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      phase&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;phase&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      sendAudioPacket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;samplesPerPacket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SAMPLE_RATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) * &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; generateSineWaveBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;phase&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;samplesPerPacket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;phaseIncrement&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;alloc&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;PACKET_SIZE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; i&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;samplesPerPacket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; sample&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Math&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;phase&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) * &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0.3&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// 30% amplitude to avoid clipping&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; pcm16Value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Math&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;round&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;sample&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; * &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;32767&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;writeInt16LE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pcm16Value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; * &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    phase&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; += &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;phaseIncrement&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;phase&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &gt;= &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; * &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Math&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;PI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      phase&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -= &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; * &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Math&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;PI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;phase&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When I tested this setup, I noticed clicking sound artifacts again. Since we had already eliminated serial communication as a significant source of noise, networking became the prime suspect. I experimented with different UDP packet sizes to find the sweet spot:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Packet Size (bytes)&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-9-machine-melody/media/audio-22kHz-128-packet.mp3&quot;&gt;Continuous clicking&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-9-machine-melody/media/audio-22kHz-256-packet.mp3&quot;&gt;Continuous clicking, less frequent&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-9-machine-melody/media/audio-22kHz-512-packet.mp3&quot;&gt;A few clicks every second&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-9-machine-melody/media/audio-22kHz-1024-packet.mp3&quot;&gt;No clicks&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt; UDP buffer size directly affects noise artifacts. Larger buffer sizes reduce artifacts significantly.&lt;/p&gt;
&lt;p&gt;As a side note, the continous clicking reminds me of a Geiger counter. Idea for a future project!&lt;/p&gt;
&lt;p&gt;Upon reflection, I realized I only adjusted the UDP buffer size. There are additional parameters I could experiment with in future iterations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I2S buffer size (defaults to 6)&lt;/li&gt;
&lt;li&gt;I2S buffer count (defaults to 512)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The default parameters are defined in &lt;a href=&quot;https://github.com/pschatzmann/arduino-audio-tools/blob/c8e8eb74495521ed9402655cbc2e2ec3ce26fbe5/src/AudioToolsConfig.h&quot;&gt;AudioToolsConfig.h&lt;/a&gt;. It&#39;s interesting that noise occurs when the UDP packet size is smaller than or equal to the I2S buffer size. This relationship warrants further investigation given more time.&lt;/p&gt;
&lt;h3 id=&quot;transcribing-audio-and-streaming-voice-from-computer-to-device&quot;&gt;Transcribing Audio and Streaming Voice from Computer to Device&lt;/h3&gt;
&lt;p&gt;With the basic UDP streaming working, I moved on to integrating the complete voice interaction loop. I updated the client to handle push-to-talk button functionality. This logic is similar to what I implemented in the previous week for audio input, but now it also handles audio output for playback.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-9-machine-melody/media/knock-knock.mp4&quot; controls=&quot;&quot;&gt;&lt;/video&gt;
&lt;strong&gt;Voice interaction demo&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Client&lt;/strong&gt; (other logic omitted for brevity)&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;I2SStream i2sMic;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;I2SStream i2sSpeaker;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;UDPStream&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; udpSend&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;WIFI_SSID&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;WIFI_PASSWORD&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;UDPStream&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; udpReceive&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;WIFI_SSID&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;WIFI_PASSWORD&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;StreamCopy&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; transmitCopier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;throttle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;i2sMic&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;StreamCopy&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; receiveCopier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;i2sSpeaker&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;udpReceive&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1024&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; debounceCounter = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; isTransmitting = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  bool&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; buttonPressed = (&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;digitalRead&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(BTN_PTT1) == LOW || &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;digitalRead&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(BTN_PTT2) == LOW);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (buttonPressed) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    debounceCounter++;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (debounceCounter &gt;= DEBOUNCE_THRESHOLD) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      isTransmitting = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    debounceCounter--;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (debounceCounter &amp;#x3C;= -DEBOUNCE_THRESHOLD) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      isTransmitting = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (isTransmitting) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    transmitCopier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  receiveCopier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the server side, I implemented the logic to handle OpenAI&#39;s real-time API for transcription and response generation, then stream the synthesized speech back to the ESP32.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Server&lt;/strong&gt; (other logic omitted for brevity):&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; STATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  SILENT:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;silent&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  SPEAKING:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;speaking&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; currentState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;STATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SILENT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; lastPacketTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; detectSilence&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;currentState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;STATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SPEAKING&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;lastPacketTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; timeSinceLastPacket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() - &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;lastPacketTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;timeSinceLastPacket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &gt; &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SILENCE_TIMEOUT_MS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      transitionToSilentAndProcessAudio&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; streamAudioChunkToRealtime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioChunk&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; base64Audio&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioChunk&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;base64&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; event&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    type:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;input_audio_buffer.append&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    audio:&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; base64Audio&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  realtimeWs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; commitAudioAndRequestResponse&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  realtimeWs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;type:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;input_audio_buffer.commit&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; }));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  realtimeWs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;type:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;response.create&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;response:&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;modalities:&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;] } }));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  realtimeWs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;type:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;input_audio_buffer.clear&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; }));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Convert TTS to PCM and stream to ESP32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; synthesizeAndStreamSpeech&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;https://api.openai.com/v1/audio/speech&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    method:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;POST&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    headers:&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      Authorization:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; `Bearer &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;process&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;env&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;      &quot;Content-Type&quot;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    body:&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; JSON&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      model:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;gpt-4o-mini-tts&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      voice:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;ash&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      input:&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      instructions:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;Low coarse seasoned veteran from war time, military radio operator voice with no emotion. Speak fast with urgency.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      response_format:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;wav&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; wavBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;arrayBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; pcmBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; convertWavToPCM16&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;wavBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; streamAudioToUDP&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pcmBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Send audio packets with timing control&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; streamAudioToUDP&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pcmBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; totalPackets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Math&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ceil&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pcmBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;PACKET_SIZE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; i&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;totalPackets&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; packet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pcmBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;slice&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; * &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;PACKET_SIZE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) * &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;PACKET_SIZE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; sendAudioPacketToESP32&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;packet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Wait to match playback speed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; delayMs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = (&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;PACKET_SIZE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SAMPLE_RATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) * &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; sleep&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;delayMs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This completes the full voice interaction loop. The system can now receive spoken input from the ESP32, process it with OpenAI&#39;s API, and stream the synthesized voice response back to play through the speaker.&lt;/p&gt;
&lt;h2 id=&quot;sonic-fidget-spinner&quot;&gt;Sonic Fidget Spinner&lt;/h2&gt;
&lt;p&gt;I&#39;ve always been fascinated by &lt;a href=&quot;https://en.wikipedia.org/wiki/Musical_road&quot;&gt;Musical Roads&lt;/a&gt; that let drivers play music by driving over specially designed rumble strips. There are many demos like &lt;a href=&quot;https://www.youtube.com/watch?v=beGiU1EpGKI&quot;&gt;this one&lt;/a&gt; on the internet showing how the vibrations create melodies. I wanted to build a miniature version controlled by a rotary encoder, offering a similar tactile experience. This would be something I could fidget with during boring Zoom calls.&lt;/p&gt;
&lt;h3 id=&quot;rotary-encoder&quot;&gt;Rotary Encoder&lt;/h3&gt;
&lt;p&gt;I received feedback that I did too much networking work during Input Device week, so this was an opportunity to add more input device exploration. I picked up a rotary encoder from my lab&#39;s spare parts bin and started studying its mechanism by watching a &lt;a href=&quot;https://www.youtube.com/watch?v=fgOfSHTYeio&quot;&gt;YouTube tutorial&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The rotary encoder works by generating signals as you rotate the shaft. You can determine both the direction and speed of rotation. I implemented a basic test program using the Rotary Encoder library. My version is simplified from the &lt;a href=&quot;https://github.com/mo-thunderz/RotaryEncoder/blob/main/Arduino/ArduinoRotaryEncoder/ArduinoRotaryEncoder.ino&quot;&gt;full example code&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;RotaryEncoder.h&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Define the pins connected to the encoder&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#define&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; PIN_ENCODER_A D6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#define&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; PIN_ENCODER_B D7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;RotaryEncoder&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; encoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;PIN_ENCODER_A&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;PIN_ENCODER_B&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; checkPosition&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  encoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;tick&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; // Call tick() to check the state&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; setup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;115200&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Rotary Encoder Example&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Attach interrupts for encoder pins&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  attachInterrupt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;digitalPinToInterrupt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(PIN_ENCODER_A), checkPosition, CHANGE);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  attachInterrupt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;digitalPinToInterrupt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(PIN_ENCODER_B), checkPosition, CHANGE);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Read encoder position&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; newPosition = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;encoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getPosition&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; lastPosition = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (newPosition != lastPosition) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Encoder Position: &quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(newPosition);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    lastPosition = newPosition;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  delay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; // Debounce or prevent overwhelming serial&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This simple program confirmed that the encoder was working correctly and gave me real-time position feedback through the serial monitor.&lt;/p&gt;
&lt;h3 id=&quot;trigger-mechanism&quot;&gt;Trigger Mechanism&lt;/h3&gt;
&lt;p&gt;With the encoder working, I needed to decide how to map rotation to musical notes. I considered two approaches:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Note per click&lt;/td&gt;
&lt;td&gt;Simple implementation, precise triggering&lt;/td&gt;
&lt;td&gt;No control over note duration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consecutive clicks to start and stop a note&lt;/td&gt;
&lt;td&gt;More control over note duration&lt;/td&gt;
&lt;td&gt;More complex, especially timing logic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I chose the second approach because it would allow for more expressive playing. With debouncing logic, I could track when a group of consecutive clicks starts and stops:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// ... setup and encoder initialization ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; counter = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;unsigned&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; lastChangeTime = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; isChanging = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; groupStarted = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; newPosition = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;encoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getPosition&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; lastPosition = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Detect position change&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (newPosition != lastPosition) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    lastPosition = newPosition;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    lastChangeTime = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;millis&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    isChanging = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!groupStarted) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Position change group started&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      groupStarted = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Detect end of group (100ms timeout)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (isChanging &amp;#x26;&amp;#x26; (&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;millis&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() - lastChangeTime &gt; &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    counter++;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Position change group ended, Counter: &quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(counter);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    isChanging = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    groupStarted = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The 100ms timeout worked well for detecting when the user paused between rotation gestures. Each continuous rotation motion was now being counted as a single unit. Increasing the timeout would cause a lingering effect and makes the device feel lagging.&lt;/p&gt;
&lt;h3 id=&quot;audio-synthesis&quot;&gt;Audio Synthesis&lt;/h3&gt;
&lt;p&gt;With the rotation detection working, it was time to add sound. I started with the simple approach of playing the same tone for each note, merging the audio playback code from the walkie-talkie with the encoder logic:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;AudioTools.h&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// ... I2S pin definitions ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; sampleRate = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;22000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AudioInfo&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; info&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;sampleRate&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;SineWaveGenerator&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int16_t&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sineWave&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;16000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;GeneratedSoundStream&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int16_t&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sound&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;sineWave&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;I2SStream out;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;StreamCopy&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; copier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;out&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;sound&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; playing = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; setup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // ... encoder setup ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Setup I2S for audio output&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  auto&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; config = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;out&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;defaultConfig&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(TX_MODE);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  config&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copyFrom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(info);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  config&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_bck&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = I2S_BCLK;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  config&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_ws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = I2S_LRC;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  config&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = I2S_DIN;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  out&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(config);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  sineWave&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(info, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;440&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // 440 Hz (A4 note)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // ... encoder position detection ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (newPosition != lastPosition) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // ... debouncing logic ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!groupStarted) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      playing = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Start playing when group starts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      groupStarted = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (isChanging &amp;#x26;&amp;#x26; (&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;millis&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() - lastChangeTime &gt; &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    playing = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Stop playing when group ends&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // ... rest of debouncing ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Only copy audio when playing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (playing) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    copier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This worked well as a proof of concept. The speaker would play a tone when I started rotating and stop when I paused. Now it was time to make it musical.&lt;/p&gt;
&lt;h3 id=&quot;soundtrack&quot;&gt;Soundtrack&lt;/h3&gt;
&lt;p&gt;To create an actual melody, I mapped out &amp;quot;Ode to Joy&amp;quot; to musical notes and their frequencies. I chose this piece because it&#39;s simple, recognizable, and works well with discrete note triggers. I added a pointer to track the current position in the melody:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Note-to-frequency mapping&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;float&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; getFreq&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; note&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  switch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (note) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    case&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &#39;g&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt; 392.00&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    case&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt; 261.63&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    case&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt; 293.66&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    case&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt; 329.63&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    case&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &#39;F&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt; 349.23&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    case&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &#39;G&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt; 392.00&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    default&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Ode to Joy melody&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; song&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;62&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;] = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;  &#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;F&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;G&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;G&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;F&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;  &#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;F&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;G&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;G&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;F&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;  &#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;F&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;F&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;g&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;  &#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;F&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;G&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;G&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;F&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; currentNote = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // ... encoder position detection ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (newPosition != lastPosition) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // ... debouncing logic ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!groupStarted) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      playing = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;      // Play next note in sequence&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      char&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; note = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;song&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[currentNote % &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;63&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      float&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; freq = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getFreq&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(note);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      sineWave&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setFrequency&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(freq);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      currentNote++;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      groupStarted = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // ... rest of loop ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now each rotation gesture would play the next note in &amp;quot;Ode to Joy&amp;quot;, creating a tactile musical experience. The device was starting to feel like a real instrument.&lt;/p&gt;
&lt;h3 id=&quot;a-not-so-mary-b-side&quot;&gt;A Not-So-Mary B-side&lt;/h3&gt;
&lt;p&gt;As I tested the device, an idea struck me: what if rotating backwards would play a different melody, like the B-side of a vinyl record? I decided to add &amp;quot;Mary Had a Little Lamb&amp;quot; as the B-side melody. It&#39;s an equally joyful tune that would complement &amp;quot;Ode to Joy&amp;quot;.&lt;/p&gt;
&lt;p&gt;However, implementing bidirectional melodies revealed a problem. The encoder would occasionally bounce with an unexpected double flip: it changes direction for one click, and immediately changes back for the next click. I had to defer the direction detection after debouncing so as to ignore these spurious direction changes.&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Two melodies for bidirectional playback&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; odeToJoy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;62&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;] = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;  &#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;F&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;G&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;G&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;F&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;  &#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;F&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;G&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;G&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;F&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;  &#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;F&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;F&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;g&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;  &#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;F&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;G&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;G&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;F&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; littleLamb&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;26&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;] = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;  &#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;G&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;G&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;E&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;D&#39;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&#39;C&#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; currentNote = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; lastDirection = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Track rotation direction: 1 = forward, -1 = backward&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; lastPosition = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; newPosition = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;encoder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getPosition&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (newPosition != lastPosition) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Determine rotation direction&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; dir = (newPosition &gt; lastPosition) ? &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; : -&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    lastPosition = newPosition;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    lastChangeTime = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;millis&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Detect direction change (only when not currently changing to avoid bounce)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!isChanging &amp;#x26;&amp;#x26; dir != lastDirection &amp;#x26;&amp;#x26; lastDirection != &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      currentNote = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Reset counter on direction change&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      lastDirection = dir;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Direction changed, resetting counter&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Set initial direction&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (lastDirection == &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      lastDirection = dir;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Play appropriate melody based on direction&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!isChanging) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      char&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; note;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (lastDirection == &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        note = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;odeToJoy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[currentNote % &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;62&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (lastDirection == -&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        note = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;littleLamb&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[currentNote % &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;26&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      float&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; freq = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getFreq&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(note);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      sineWave&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setFrequency&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(freq);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      isChanging = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      playing = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Stop playing after debounce timeout&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (isChanging &amp;#x26;&amp;#x26; (&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;millis&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() - lastChangeTime &gt; DEBOUNCE_TIME)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    isChanging = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    playing = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    currentNote++;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Advance to next note&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (playing) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    copier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I was able to play both melodies. There were still occasional misfires of direction change, but it was good enough for a one-off performance.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-9-machine-melody/media/mary-had-a-little-lamb.mp4&quot; controls=&quot;&quot;&gt;&lt;/video&gt;
&lt;strong&gt;Playing Mary Had A Little Lamb on the B-side&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If I had more time, I would focus on single soundtrack but use the change of rotation direction to delimit tones. This would allow user to quickly play two notes without waiting for the debounce timeout.&lt;/p&gt;
&lt;h2 id=&quot;after-thought&quot;&gt;After Thought&lt;/h2&gt;
&lt;p&gt;My development board for the XIAO ESP32 was the unsung hero of this week. The breakout board I created in earlier weeks allowed me to explore both the wireless voice interaction system and the sonic fidget spinner without fabricating new PCBs. I was especially glad that I had mapped out all the pins to female headers during the initial design. This was a perfect example of upfront investment in modular design paying dividends later.&lt;/p&gt;
&lt;h2 id=&quot;appendix&quot;&gt;Appendix&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-9-machine-melody/code/walkie-talkie.zip&quot;&gt;Walkie Talkie Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-9-machine-melody/code/sonic-fidget.zip&quot;&gt;Sonic Fidget Spinner Code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Mon, 03 Nov 2025 00:00:00 GMT</pubDate>
      <dc:creator>Sun Chuanqi</dc:creator>
      <guid>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-9-machine-melody/</guid>
    </item>
    <item>
      <title>Week 8: First Contact</title>
      <link>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-8-first-contact/</link>
      <description>&lt;h2 id=&quot;group-assignment&quot;&gt;Group Assignment&lt;/h2&gt;
&lt;p&gt;I joined &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/TylerJensenHill/&quot;&gt;Typer&lt;/a&gt; and &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/JacquelineOrr/&quot;&gt;Jacqueline&lt;/a&gt; to characterize input devices in our lab. We started by &lt;a href=&quot;https://fab.cba.mit.edu/classes/MAS.863/CBA/group_assignments/week8/&quot;&gt;probing a photo transistor&lt;/a&gt; to understand its behavior. Our key finding was that the resting resistance is quite high, and covering the sensor with a hand has only a modest effect. However, shining a flashlight on the sensor causes a significant drop in resistance, confirming its sensitivity to light.&lt;/p&gt;
&lt;p&gt;To gain more hands-on experience, I decided to probe the microphone breakout board I fabricated in &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/&quot;&gt;Week 6&lt;/a&gt; with &lt;a href=&quot;https://www.saleae.com/products/saleae-logic-8&quot;&gt;Saleae Logic 8&lt;/a&gt;. Wiring up the probes proved tricky due to the small soldering joints on the board.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-8-first-contact/media/probing-01.webp&quot; alt=&quot;Probe setup&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Probe setup&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-8-first-contact/media/probing-00.webp&quot; alt=&quot;I2S setup&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Setup Analyzer for I2S protocol&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I couldn&#39;t find a clock signal generator in the lab, so I programmed the Xiao to initialize the I2S device and generate the necessary clock signals for testing.&lt;/p&gt;
&lt;p&gt;This is the minimum code to drive the I2S device:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Simple I2S microphone CSV output&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Custom pinout: D0 = BCLK, D1 = DOUT, D2 = LRC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;AudioTools.h&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AudioInfo&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; info&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;16000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // 16kHz, mono, 16-bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;I2SStream i2sStream;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;CsvOutput&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int16_t&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;csvOutput&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;StreamCopy&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; copier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;csvOutput&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;i2sStream&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; setup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;115200&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  AudioToolsLogger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(Serial, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AudioToolsLogLevel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::Info);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Configure I2S for custom pinout&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  auto&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; cfg = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i2sStream&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;defaultConfig&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(RX_MODE);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  cfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copyFrom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(info);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  cfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_bck&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = D0;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   // BCLK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  cfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = D1;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // DOUT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  cfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_ws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = D2;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // LRC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  cfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i2s_format&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = I2S_STD_FORMAT;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  i2sStream&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(cfg);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  csvOutput&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(info);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  copier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I confirmed from the serial plotter that the device was outputting data. The software supports both I2S and PCM protocols, but the decoding was not very useful for analysis purposes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-8-first-contact/media/probing-02.webp&quot; alt=&quot;I2S reading&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;making-a-wireless-microphone&quot;&gt;Making a Wireless Microphone&lt;/h2&gt;
&lt;p&gt;This week is built on top of boards made in &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/&quot;&gt;week 6&lt;/a&gt;. I planned to add speech recognition to the hand-held device with wireless data streaming to my computer for generative AI interaction.&lt;/p&gt;
&lt;p&gt;There is &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.15/section.Harvard/people/Garber/finalproject.html&quot;&gt;a similar project&lt;/a&gt; for a wireless microphone from a previous year. The project documentation seemed incomplete but it confirmed my intuition that separating the microphone PCB from the main board would give me the freedom to design the case more ergonomically.&lt;/p&gt;
&lt;h3 id=&quot;networking-test-with-sine-wave&quot;&gt;Networking Test with Sine Wave&lt;/h3&gt;
&lt;p&gt;I started with an off-the-shelf &lt;a href=&quot;https://www.adafruit.com/product/6049&quot;&gt;Adafruit ICS-43434&lt;/a&gt; microphone and Phil Schatzmann&#39;s examples in the &lt;a href=&quot;https://github.com/pschatzmann/arduino-audio-tools&quot;&gt;Arduino Audio Tools library&lt;/a&gt;. My first goal was to stream a basic sine wave over WiFi. I took Phil&#39;s sample code as is and confirmed that sound was working over WiFi. During testing, I found that the antenna seems necessary for a stable connection. I had success in previous tests without using an antenna, but for some reason, the antenna became necessary in this setup.&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; * &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;@file&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; streams-generator-server_wav.ino&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; *&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; * See: https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/examples-communication/http-server/streams-generator-webserver_wav/streams-generator-webserver_wav.ino&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; * This sketch generates a test sine wave. The result is provided as WAV stream which can be listened to in a Web Browser&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; *&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; * &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;@author&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; Phil Schatzmann&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; * &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;@copyright&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; GPLv3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; *&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;AudioTools.h&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;AudioTools/Communication/AudioHttp.h&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// WIFI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; *ssid = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;REPLACE_WITH_SSID&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; *password = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;REPLACE_WITH_REAL_PASSWORD&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AudioWAVServer&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ssid&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;password&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Sound Generation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; sample_rate = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;10000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; channels = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;SineWaveGenerator&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int16_t&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&gt; sineWave;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;            // Subclass of SoundGenerator with max amplitude of 32000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;GeneratedSoundStream&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int16_t&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;sineWave&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;     // Stream generated from sine wave&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; setup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;115200&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;  AudioLogger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;instance&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(Serial,&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AudioLogger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::Info);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // start server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(in, sample_rate, channels);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // start generation of sound&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  sineWave&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(channels, sample_rate, N_B4);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  in&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Will sleep&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // sleep for 5 seconds first&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  delay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;5000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Server URL: http://&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;localIP&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// copy the data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;http-streaming&quot;&gt;HTTP Streaming&lt;/h3&gt;
&lt;p&gt;In this setup, the microcontroller works as the server, exposing a WAV stream. My computer works as the client, receiving and playing the WAV stream. I connected the I2S microphone to the ESP32 and configured it to stream audio data over HTTP.&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;AudioTools.h&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;AudioTools/Communication/AudioHttp.h&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// WiFi credentials&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; *ssid = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;REPLACE_WITH_SSID&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; *password = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;REPLACE_WITH_PASSWORD&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// I2S and Audio&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AudioInfo&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; info&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;22000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // 22kHz, mono, 16-bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;I2SStream i2sStream;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;           // Access I2S as stream&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ConverterFillLeftAndRight&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int16_t&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;filler&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;LeftIsEmpty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AudioWAVServer&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ssid&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;password&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; setup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;115200&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  delay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;  AudioLogger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;instance&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(Serial, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AudioLogger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::Info);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Connect to WiFi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Connecting to WiFi...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(ssid, password);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; attempts = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  while&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() != WL_CONNECTED &amp;#x26;&amp;#x26; attempts &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    delay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;500&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    attempts++;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() != WL_CONNECTED) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Failed to connect to WiFi&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;WiFi connected!&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Device IP: &quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;localIP&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Starting I2S...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  auto&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; cfg = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i2sStream&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;defaultConfig&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(RX_MODE);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  cfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copyFrom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(info);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  cfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_bck&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = D0;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   // BCLK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  cfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = D1;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // DOUT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  cfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_ws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = D2;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // LRC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  cfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i2s_format&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = I2S_STD_FORMAT;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i2sStream&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(cfg)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Failed to initialize I2S&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;I2S initialized successfully&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Start WAV server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Starting WAV server...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(i2sStream, info, &amp;#x26;filler);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Server URL: http://&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;localIP&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After testing this approach, I observed several issues. The latency was inconsistent, ranging from 1 second to 5 seconds. The sound quality was also inconsistent, sometimes good, sometimes poor. My suspicion is that the network condition affected the streaming performance.&lt;/p&gt;
&lt;h3 id=&quot;exploring-mp3-encoding&quot;&gt;Exploring MP3 Encoding&lt;/h3&gt;
&lt;p&gt;I wanted to test MP3 encoding to reduce bandwidth usage and potentially improve robustness against network issues. The theory was that compressed audio would be more resilient to network fluctuations.&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;AudioTools.h&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;AudioTools/AudioCodecs/CodecMP3LAME.h&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;AudioTools/Communication/AudioHttp.h&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// WiFi credentials&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; *ssid = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;REPLACE_WITH_SSID&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; *password = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;REPLACE_WITH_PASSWORD&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// I2S and Audio&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AudioInfo&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; info&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;16000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // 16kHz, mono, 16-bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;I2SStream i2sStream;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;           // Access I2S as stream&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;MP3EncoderLAME mp3;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AudioEncoderServer&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;mp3&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ssid&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;password&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; setup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;115200&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  delay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;  AudioLogger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;instance&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(Serial, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AudioLogger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::Info);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Connect to WiFi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Connecting to WiFi...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(ssid, password);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; attempts = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  while&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() != WL_CONNECTED &amp;#x26;&amp;#x26; attempts &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    delay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;500&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    attempts++;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() != WL_CONNECTED) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Failed to connect to WiFi&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;WiFi connected!&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Device IP: &quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;localIP&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Configure I2S with custom pinout&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Starting I2S...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  auto&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; cfg = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i2sStream&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;defaultConfig&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(RX_MODE);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  cfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copyFrom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(info);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  cfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_bck&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = D0;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   // BCLK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  cfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = D1;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // DOUT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  cfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_ws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = D2;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // LRC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  cfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i2s_format&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = I2S_STD_FORMAT;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i2sStream&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(cfg)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Failed to initialize I2S&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;I2S initialized successfully&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Start MP3 server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Starting MP3 server...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(i2sStream, info);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Server URL: http://&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;localIP&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;doLoop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running this code caused a memory allocation error:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Error] lame.c : 2792 - calloc(1,85840) -&gt; 0x0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;available MALLOC_CAP_8BIT: 114676 / MALLOC_CAP_32BIT: 114676  / MALLOC_CAP_SPIRAM: 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It turns out MP3 encoding is quite memory intensive. While it may reduce network bandwidth usage, it does not fit the memory constraints of the ESP32-C3. This was a dead end for optimization.&lt;/p&gt;
&lt;h3 id=&quot;udp-streaming-experiment&quot;&gt;UDP Streaming Experiment&lt;/h3&gt;
&lt;p&gt;If I couldn&#39;t address the audio codec limitation, could I tackle the network issue itself? I knew that the HTTP protocol has built-in error correction and guarantees delivery through two-way communication, which may introduce significant overhead. UDP is a connectionless protocol that does not guarantee delivery, but it has lower latency and overhead. I don&#39;t worry about occasional packet loss in audio streaming, as it often goes unnoticed by the human ear. I decided to go back to basics and test whether I could stream a sine wave over UDP.&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;AudioTools.h&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;AudioTools/Communication/UDPStream.h&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// WiFi credentials&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; *ssid = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;REPLACE_WITH_SSID&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; *password = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;REPLACE_WITH_PASSWORD&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AudioInfo&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; info&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;22000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;SineWaveGenerator&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int16_t&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sineWave&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;32000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;GeneratedSoundStream&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int16_t&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sound&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;sineWave&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;UDPStream&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; udp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ssid&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;password&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Throttle&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; throttle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;udp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IPAddress&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; udpAddress&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;192&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;168&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;41&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;106&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; udpPort = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;8888&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;StreamCopy&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; copier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;throttle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;sound&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; setup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;115200&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  AudioToolsLogger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(Serial, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AudioToolsLogLevel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::Info);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  sineWave&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(info, N_B4);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Define udp address and port&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  udp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(udpAddress, udpPort);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  auto&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; cfg = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;throttle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;defaultConfig&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  cfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copyFrom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(info);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  throttle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(cfg);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;started...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Device IP: &quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;localIP&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Sending to: &quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(udpAddress);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(udpPort);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  copier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The challenge with UDP is that it cannot be directly played in a browser like HTTP streams. I needed to implement a simple UDP client. Having worked with JavaScript extensively, I chose to use Node.js for the client implementation. This program receives audio data over UDP and pipes it directly into FFmpeg for playback.&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; dgram&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;dgram&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;spawn&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;child_process&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; SAMPLE_RATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;22000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; CHANNELS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; BITS_PER_SAMPLE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; UDP_PORT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;8888&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;dgram&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;createSocket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;udp4&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// FFmpeg process to play audio&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Statistics&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; packetsReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; bytesReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; lastStatsTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; startAudioPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Starting audio player...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Use ffmpeg to play raw PCM audio&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;spawn&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;ffmpeg&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;-f&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;s16le&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// signed 16-bit little-endian&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;-ar&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;    SAMPLE_RATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// sample rate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;-ac&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;    CHANNELS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// channels&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;-i&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;pipe:0&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// input from stdin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;-f&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;alsa&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Use ALSA for Linux audio output&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;default&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Default audio output device&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  ]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;stderr&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // ffmpeg outputs info to stderr, only log errors&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;includes&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) || &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;includes&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Error&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;FFmpeg error:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;FFmpeg process error:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;close&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;code&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`FFmpeg process exited with code &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;code&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;✓ Audio player started&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Handle incoming UDP packets&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rinfo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    startAudioPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Write audio data to ffmpeg stdin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; !&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;killed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;stdin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Update statistics&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  packetsReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;++;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  bytesReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; += &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Log statistics every 5 seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;lastStatsTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &gt; &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;5000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; elapsed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;lastStatsTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) / &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; packetsPerSec&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;packetsReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;elapsed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toFixed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; kbytesPerSec&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;bytesReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;elapsed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1024&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toFixed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`Stats: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;packetsPerSec&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; packets/s, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;kbytesPerSec&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; KB/s from &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rinfo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rinfo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    packetsReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    bytesReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    lastStatsTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`Server error:&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;stack&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;close&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;listening&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; interfaces&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;os&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;networkInterfaces&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; addresses&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; Object&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;keys&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;interfaces&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; iface&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; interfaces&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;iface&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;family&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;IPv4&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; !&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;iface&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;internal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        addresses&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;iface&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;==============================================&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;ESP32 Audio UDP Receiver&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;==============================================&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`UDP Server listening on port &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`Sample Rate: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SAMPLE_RATE&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; Hz`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`Channels: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CHANNELS&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; (mono)`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`Bits per sample: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;BITS_PER_SAMPLE&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Listening on:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`  Local:   &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  addresses&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;addr&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`  Network: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;addr&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Waiting for ESP32 to send audio...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;==============================================&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Start UDP server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bind&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;UDP_PORT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Graceful shutdown&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;process&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;SIGINT&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Shutting down...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; !&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;killed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;stdin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;kill&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;SIGTERM&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;close&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Server closed&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    process&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;exit&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I confirmed the sine wave was working with this setup. The UDP approach showed promise for reducing latency.&lt;/p&gt;
&lt;h3 id=&quot;udp-microphone-streaming&quot;&gt;UDP Microphone Streaming&lt;/h3&gt;
&lt;p&gt;Next, I modified the microcontroller code to stream I2S data over UDP instead of generating a sine wave. UDP does not have flow control, so I needed to implement the throttle pattern recommended in the &lt;a href=&quot;https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/examples-communication/udp/communication-udp-send/communication-udp-send.ino&quot;&gt;AudioTools example&lt;/a&gt; to prevent overwhelming the network.&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;AudioTools.h&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;AudioTools/Communication/UDPStream.h&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// WiFi credentials&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; *ssid = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; *password = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AudioInfo&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; info&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;22000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // 22kHz, mono, 16-bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;I2SStream i2sStream;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;           // Access I2S as stream&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ConverterFillLeftAndRight&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int16_t&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;filler&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;LeftIsEmpty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; // fill both channels&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;UDPStream&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; udp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ssid&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;password&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Throttle&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; throttle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;udp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IPAddress&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; udpAddress&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;192&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;168&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;41&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;106&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Broadcast address&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; udpPort = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;8888&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;StreamCopy&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; copier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;throttle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;i2sStream&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // copies I2S microphone input into UDP&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; setup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;115200&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  delay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  AudioToolsLogger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(Serial, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AudioToolsLogLevel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::Info);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Connect to WiFi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Connecting to WiFi...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(ssid, password);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; attempts = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  while&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() != WL_CONNECTED &amp;#x26;&amp;#x26; attempts &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    delay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;500&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    attempts++;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() != WL_CONNECTED) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Failed to connect to WiFi&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;WiFi connected!&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Device IP: &quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WiFi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;localIP&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Starting I2S...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  auto&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; i2sCfg = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i2sStream&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;defaultConfig&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(RX_MODE);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  i2sCfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copyFrom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(info);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  i2sCfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_bck&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = D0;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;   // BCLK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  i2sCfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = D1;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // DOUT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  i2sCfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pin_ws&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = D2;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // LRC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  i2sCfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i2s_format&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = I2S_STD_FORMAT;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;i2sStream&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(i2sCfg)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Failed to initialize I2S&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;I2S initialized successfully&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Define udp address and port&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  udp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(udpAddress, udpPort);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  auto&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; throttleCfg = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;throttle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;defaultConfig&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  throttleCfg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copyFrom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(info);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  throttle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(throttleCfg);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Started streaming...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Sending to: &quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(udpAddress);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(udpPort);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  copier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Node.js client remained unchanged from the sine wave test, which allowed me to evaluate the latency and compare it with the HTTP protocol version. I was able to achieve much more consistent latency with 1-2 seconds delay. On special occasions, I achieved near real-time performance, but it was unclear how to reproduce it consistently. I suspect it still depends on network conditions.&lt;/p&gt;
&lt;p&gt;&lt;video controls=&quot;&quot; src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-8-first-contact/media/latency.mp4&quot;&gt;&lt;/video&gt;
&lt;strong&gt;Latency test with UDP&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;openai-transcription&quot;&gt;OpenAI Transcription&lt;/h3&gt;
&lt;p&gt;Next, I modified the Node.js code to transcribe the audio input using OpenAI&#39;s Whisper API. The FFmpeg playback was kept for debugging purposes and also provided an audible reference on where the latency occurs—whether it&#39;s before or during the transcription.&lt;/p&gt;
&lt;p&gt;The key design decision was to use multi-part form data to stream audio chunks as soon as they are available. Since we don&#39;t have a way to delimit the audio stream automatically, I used an arbitrary 5-second interval to send chunks. I verified that OpenAI was able to respond with the transcribed text.&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; dgram&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;dgram&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;spawn&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;child_process&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; SAMPLE_RATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;22000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; CHANNELS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; BITS_PER_SAMPLE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; UDP_PORT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;8888&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; OPENAI_API_KEY&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;process&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;env&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Make you set the environment variable with your own key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; TRANSCRIPTION_INTERVAL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;5000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// ms&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;dgram&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;createSocket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;udp4&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; lastTranscriptionTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; isTranscribing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; packetsReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; bytesReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; lastStatsTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; startAudioPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Starting audio player...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;spawn&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;ffmpeg&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, [&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;-f&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;s16le&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;-ar&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SAMPLE_RATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;-ac&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CHANNELS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;-i&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;pipe:0&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;-f&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;alsa&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;stderr&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;includes&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) || &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;includes&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Error&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;FFmpeg error:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;FFmpeg process error:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;close&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;code&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`FFmpeg process exited with code &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;code&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;✓ Audio player started&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; createWavFromPCM&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pcmData&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; dataSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pcmData&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; fileSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;44&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;dataSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; header&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;alloc&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;44&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  header&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;RIFF&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  header&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;writeUInt32LE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;fileSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  header&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;WAVE&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  header&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;fmt &quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;12&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  header&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;writeUInt32LE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// fmt chunk size&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  header&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;writeUInt16LE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// audio format (1 = PCM)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  header&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;writeUInt16LE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CHANNELS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;22&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  header&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;writeUInt32LE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SAMPLE_RATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;24&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  header&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;writeUInt32LE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SAMPLE_RATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; * &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CHANNELS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; * &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;BITS_PER_SAMPLE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) / &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;28&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// byte rate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  header&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;writeUInt16LE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CHANNELS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; * &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;BITS_PER_SAMPLE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) / &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;32&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// block align&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  header&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;writeUInt16LE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;BITS_PER_SAMPLE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;34&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // data chunk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  header&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;36&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  header&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;writeUInt32LE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;dataSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;40&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; Buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;concat&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;header&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;pcmData&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; transcribeAudio&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioData&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;⚠️  OPENAI_API_KEY not set. Skipping transcription.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioData&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;⚠️  No audio data to transcribe&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`🎤 Transcribing &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioData&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1024&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toFixed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; KB of audio...`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Convert PCM to WAV&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; wavData&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; createWavFromPCM&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioData&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Build multipart/form-data with boundary (similar to web implementation)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; boundary&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;----WebKitFormBoundary&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Math&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;random&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;36&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;slice&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; CRLF&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;r&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Build the multipart form data manually&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; preamble&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;      `--&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;boundary&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CRLF&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;      `Content-Disposition: form-data; name=&quot;model&quot;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CRLF&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CRLF&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;      `whisper-1&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CRLF&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;      `--&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;boundary&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CRLF&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;      `Content-Disposition: form-data; name=&quot;language&quot;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CRLF&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CRLF&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;      `en&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CRLF&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;      `--&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;boundary&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CRLF&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;      `Content-Disposition: form-data; name=&quot;file&quot;; filename=&quot;audio.wav&quot;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CRLF&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;      `Content-Type: audio/wav&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CRLF&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CRLF&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; epilogue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CRLF&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;boundary&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CRLF&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Create a ReadableStream from the data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;Readable&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;stream&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; bodyStream&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Readable&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; function*&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;        yield&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; Buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;preamble&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;        yield&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; wavData&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;        yield&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; Buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;epilogue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      })()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Make request using fetch&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;https://api.openai.com/v1/audio/transcriptions&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      method:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;POST&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      headers:&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        Authorization:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; `Bearer &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;        &quot;Content-Type&quot;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; `multipart/form-data; boundary=&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;boundary&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      body:&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; bodyStream&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      duplex:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;half&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ok&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; errorText&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      throw&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; Error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`HTTP &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;errorText&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;📝 Transcription: &quot;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;❌ Transcription error:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; processTranscriptionQueue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;isTranscribing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; || &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;lastTranscriptionTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;TRANSCRIPTION_INTERVAL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  isTranscribing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  lastTranscriptionTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Get accumulated audio data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; audioData&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;concat&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Transcribe in background&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  transcribeAudio&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioData&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;finally&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    isTranscribing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Handle incoming UDP packets&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rinfo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    startAudioPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Write audio data to ffmpeg stdin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; !&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;killed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;stdin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Add to transcription buffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Update statistics&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  packetsReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;++;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  bytesReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; += &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Check if it&#39;s time to transcribe&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  processTranscriptionQueue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Log statistics every 5 seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;lastStatsTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &gt; &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;5000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; elapsed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;lastStatsTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) / &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; packetsPerSec&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;packetsReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;elapsed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toFixed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; kbytesPerSec&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;bytesReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;elapsed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1024&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toFixed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; bufferSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;reduce&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;sum&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;buf&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; sum&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;buf&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) / &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1024&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toFixed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`📊 Stats: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;packetsPerSec&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; packets/s, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;kbytesPerSec&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; KB/s, buffer: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;bufferSize&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; KB`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    packetsReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    bytesReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    lastStatsTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`Server error:&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;stack&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;close&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;listening&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; interfaces&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;os&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;networkInterfaces&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; addresses&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; Object&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;keys&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;interfaces&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; iface&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; interfaces&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;iface&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;family&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;IPv4&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; !&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;iface&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;internal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        addresses&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;iface&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;==============================================&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;ESP32 Audio UDP Receiver with Transcription&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;==============================================&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`UDP Server listening on port &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`Sample Rate: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SAMPLE_RATE&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; Hz`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`Channels: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;CHANNELS&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; (mono)`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`Bits per sample: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;BITS_PER_SAMPLE&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`Transcription interval: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;TRANSCRIPTION_INTERVAL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;s`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`OpenAI API Key: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ? &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;✓ Set&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;✗ Not set&quot;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Listening on:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`  Local:   &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  addresses&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;addr&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`  Network: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;addr&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Waiting for ESP32 to send audio...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;==============================================&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Start UDP server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bind&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;UDP_PORT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Graceful shutdown&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;process&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;SIGINT&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Shutting down...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; !&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;killed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;stdin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    ffmpegPlayer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;kill&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;SIGTERM&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;close&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Server closed&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    process&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;exit&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;push-to-talk&quot;&gt;Push to Talk&lt;/h3&gt;
&lt;p&gt;The 5-second interval auto-send was a temporary solution for testing transcription. For a more practical implementation, I used one of the buttons on my Operator Board to signal the beginning and ending of speech. When the button is pressed, the microcontroller starts recording audio. When the button is released, it stops recording and sends the audio for transcription. This push-to-talk approach is similar to how walkie-talkies work.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-8-first-contact/media/device-01.webp&quot; alt=&quot;Push button&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;I found this button in my lab&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here are the key sections in the code related to the push-to-talk functionality.&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Debounce settings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; DEBOUNCE_THRESHOLD = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; buttonCounter = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; buttonState = HIGH;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; lastButtonState = HIGH;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; setup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Configure D8 and D9 as input with pull-up resistors&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  pinMode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(D8, INPUT_PULLUP);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  pinMode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(D9, INPUT_PULLUP);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Read combined button state (LOW if either button is pressed)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; buttonReading = (&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;digitalRead&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(D8) == LOW || &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;digitalRead&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(D9) == LOW) ? LOW : HIGH;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Debounce combined button&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (buttonReading == LOW) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    buttonCounter++;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (buttonCounter &gt;= DEBOUNCE_THRESHOLD) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      buttonState = LOW;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      buttonCounter = DEBOUNCE_THRESHOLD;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; // Cap the counter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    buttonCounter--;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (buttonCounter &amp;#x3C;= -DEBOUNCE_THRESHOLD) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      buttonState = HIGH;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      buttonCounter = -DEBOUNCE_THRESHOLD;&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; // Cap the counter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Log state changes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (buttonState != lastButtonState) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (buttonState == LOW) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Speaking...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      Serial&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;println&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Sent&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    lastButtonState = buttonState;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Transmit audio only if button is pressed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (buttonState == LOW) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    copier&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;copy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the Node.js side, I implemented a state machine to handle the audio stream more intelligently. The system starts in a silent state. Upon receiving audio packets, it transitions to a speaking state and begins streaming audio to OpenAI. After detecting sustained silence, it transitions back to the silent state and wraps up the transcription request.&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// State machine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; STATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  SILENT:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;silent&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  SPEAKING:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;speaking&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; currentState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;STATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SILENT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; lastPacketTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; silenceCheckInterval&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; isTranscribing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; transitionToSpeaking&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;currentState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; !== &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;STATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SPEAKING&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;🎤 Speaking...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    currentState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;STATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SPEAKING&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; transitionToSilent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;currentState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; !== &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;STATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SILENT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;📤 Sent&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    currentState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;STATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SILENT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Transcribe the accumulated audio&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &gt; &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; !&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;isTranscribing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; audioData&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;concat&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; transcribeAudio&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioData&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; checkForSilence&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;currentState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;STATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SPEAKING&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;lastPacketTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; timeSinceLastPacket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() - &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;lastPacketTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;timeSinceLastPacket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &gt; &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SILENCE_TIMEOUT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      transitionToSilent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Handle incoming UDP packets&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rinfo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Transition to speaking state on first packet&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  transitionToSpeaking&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Add to transcription buffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;listening&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Start silence checker&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  silenceCheckInterval&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setInterval&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;checkForSilence&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;realtime-voice-synthesis&quot;&gt;Realtime Voice Synthesis&lt;/h3&gt;
&lt;p&gt;In this final version, I jumped ahead slightly and implemented response synthesis. Instead of going through transcription separately, I directly prompt the model with the audio stream and manually trigger a response upon detecting silence. The playback currently happens on the computer and will be streamed to the microcontroller in next week&#39;s Output Device assignment.&lt;/p&gt;
&lt;p&gt;The microcontroller side required a minor change. According to OpenAI&#39;s &lt;a href=&quot;https://platform.openai.com/docs/guides/realtime-transcription#session-fields&quot;&gt;documentation&lt;/a&gt;, the Realtime API requires 24kHz sampling rate.&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;- AudioInfo info(22000, 1, 16);  // 22kHz, mono, 16-bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;+ AudioInfo info(24000, 1, 16);  // 24kHz, mono, 16-bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Node.js server code required a substantial overhaul. The architecture changed from a simple HTTP transcription flow to a WebSocket-based real-time conversation system.&lt;/p&gt;
&lt;p&gt;Before:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;UDP packet -&gt; FFmpeg -&gt; STT -&gt; TTS -&gt; Audio playback&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;UDP packet -&gt; OpenAI WebSocket -&gt; TTS -&gt; Audio playback&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I engineered a &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-8-first-contact/code/final-prompt.txt&quot;&gt;prompt&lt;/a&gt; for AI to migrate my previous implementation to generate voice responses. The prompt referenced a markdown file that contains the full content of the &lt;a href=&quot;https://platform.openai.com/docs/guides/realtime-models-prompting&quot;&gt;Realtime Models Prompting guide&lt;/a&gt; and &lt;a href=&quot;https://platform.openai.com/docs/guides/realtime-conversations&quot;&gt;Realtime conversations guide&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; dgram&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;dgram&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;spawn&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; } = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;child_process&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; WebSocket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;ws&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; SAMPLE_RATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;24000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; CHANNELS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; BITS_PER_SAMPLE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; UDP_PORT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;8888&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; SILENCE_TIMEOUT_MS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; STATS_INTERVAL_MS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;5000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; SILENCE_CHECK_INTERVAL_MS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; STATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  SILENT:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;silent&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  SPEAKING:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;speaking&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;dgram&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;createSocket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;udp4&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; currentState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;STATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SILENT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; lastPacketTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; silenceCheckInterval&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; isProcessing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; packetsReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; bytesReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; lastStatsTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; realtimeWs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; sessionReady&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;startServer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; startServer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  connectToRealtimeAPI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;bind&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;UDP_PORT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;listening&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;handleServerListening&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;handleIncomingAudioPacket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;handleServerError&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  process&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;SIGINT&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;handleGracefulShutdown&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; handleServerListening&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  logServerStartup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  silenceCheckInterval&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setInterval&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;detectSilence&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SILENCE_CHECK_INTERVAL_MS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; handleIncomingAudioPacket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rinfo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  beginSpeakingStateIfNeeded&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  lastPacketTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Stream audio to Realtime API immediately if session is ready&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;sessionReady&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;realtimeWs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;realtimeWs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;readyState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WebSocket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;OPEN&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    streamAudioChunk&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  updateStatistics&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  logStatisticsIfIntervalElapsed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; handleServerError&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`Server error:&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;stack&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;close&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; handleGracefulShutdown&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Shutting down...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;silenceCheckInterval&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    clearInterval&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;silenceCheckInterval&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;realtimeWs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    realtimeWs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;close&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  server&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;close&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Server closed&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    process&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;exit&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; connectToRealtimeAPI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;process&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;env&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;⚠️  OPENAI_API_KEY not set. Cannot connect to Realtime API.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    process&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;exit&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; url&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;wss://api.openai.com/v1/realtime?model=gpt-realtime&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; headers&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Authorization:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; `Bearer &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;process&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;env&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;OpenAI-Beta&quot;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;realtime=v1&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;🔌 Connecting to OpenAI Realtime API...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  realtimeWs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; WebSocket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;headers&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  realtimeWs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;open&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;handleRealtimeOpen&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  realtimeWs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;handleRealtimeMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  realtimeWs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;close&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;handleRealtimeClose&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; handleRealtimeOpen&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;✓ Connected to Realtime API&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; handleRealtimeMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; event&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    switch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      case&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;session.created&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;✓ Session created&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        configureSession&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      case&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;session.updated&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;✓ Session configured&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        sessionReady&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      case&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;response.output_text.delta&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;        // Text being generated in chunks (optional logging)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        process&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;stdout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;delta&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      case&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;response.done&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;✓ Response complete&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;        handleResponseComplete&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;❌ Error parsing Realtime message:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; handleRealtimeClose&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;🔌 Realtime connection closed. Reconnecting...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  sessionReady&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;  setTimeout&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;connectToRealtimeAPI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; configureSession&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; sessionConfig&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    type:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;session.update&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    session:&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      modalities:&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;], &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Only text output, no audio&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      instructions:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;Respond to user speech in the voice of a HAM radio operator. One short spoken phrase response only.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      voice:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;ash&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      input_audio_format:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;pcm16&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      output_audio_format:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;pcm16&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      turn_detection:&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Disable VAD - we handle silence detection manually&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  realtimeWs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;sessionConfig&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; beginSpeakingStateIfNeeded&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;currentState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; !== &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;STATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SPEAKING&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;🎤 Speaking...&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    currentState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;STATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SPEAKING&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; detectSilence&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;currentState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;STATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SPEAKING&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;lastPacketTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; timeSinceLastPacket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() - &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;lastPacketTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;timeSinceLastPacket&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &gt; &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SILENCE_TIMEOUT_MS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;      transitionToSilentAndProcessAudio&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; transitionToSilentAndProcessAudio&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;currentState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; !== &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;STATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SILENT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;📤 Sent&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    currentState&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;STATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;SILENT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &gt; &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; !&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;isProcessing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;sessionReady&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      isProcessing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; commitAudioAndRequestResponse&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; streamAudioChunk&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioChunk&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Convert PCM16 buffer to base64 and send to Realtime API&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; base64Audio&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioChunk&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;base64&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; event&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    type:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;input_audio_buffer.append&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    audio:&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; base64Audio&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  realtimeWs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; commitAudioAndRequestResponse&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`🔄 Committing audio buffer and requesting response...`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    realtimeWs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;type:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;input_audio_buffer.commit&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; }));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    realtimeWs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;type:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;response.create&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;response:&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;modalities:&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;] } }));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    realtimeWs&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;type:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;input_audio_buffer.clear&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; }));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;❌ Error requesting response:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    isProcessing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; handleResponseComplete&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  // Extract text from response&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; responseText&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;output&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; item&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;output&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;item&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;item&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; content&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; item&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;          if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            responseText&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;            break&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;          }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;responseText&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;break&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;responseText&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`💬 Final response: &quot;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;responseText&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    speakTextAloud&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;responseText&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;⚠️  No text response received&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  isProcessing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; speakTextAloud&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`🔊 Playing TTS for: &quot;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  try&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Use OpenAI REST API for TTS since Realtime API is text-only mode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;https://api.openai.com/v1/audio/speech&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      method:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;POST&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      headers:&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        Authorization:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; `Bearer &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;process&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;env&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;        &quot;Content-Type&quot;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      body:&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; JSON&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        model:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;gpt-4o-mini-tts&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        voice:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;ash&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        input:&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        instructions:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;Low coarse seasoned veteran from war time, military radio operator voice with no emotion. Speak fast with urgency.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        response_format:&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;wav&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ok&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      throw&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; Error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`TTS API error: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;arrayBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; playAudioBufferThroughSpeakers&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;❌ TTS error:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; playAudioBufferThroughSpeakers&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; ffplay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;spawn&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;ffplay&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, [&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;-nodisp&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;-autoexit&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;-loglevel&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;quiet&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;-i&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;pipe:0&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  ffplay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;❌ ffplay error:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  ffplay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;close&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;code&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;code&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;✓ TTS playback completed&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`❌ ffplay exited with code &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;code&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  ffplay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;stdin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;buffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  ffplay&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;stdin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; updateStatistics&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;messageLength&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  packetsReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;++;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  bytesReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; += &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;messageLength&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; logStatisticsIfIntervalElapsed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;lastStatsTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &gt; &lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt;STATS_INTERVAL_MS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; elapsed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;lastStatsTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) / &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; packetsPerSec&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;packetsReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;elapsed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toFixed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; kbytesPerSec&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;bytesReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;elapsed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1024&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toFixed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; bufferSize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;audioBuffer&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;reduce&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;sum&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;buf&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; sum&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;buf&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) / &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1024&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;toFixed&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`📊 Stats: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;packetsPerSec&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; packets/s, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;kbytesPerSec&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; KB/s, buffer: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;bufferSize&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; KB`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    packetsReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    bytesReceived&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    lastStatsTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; logServerStartup&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; networkAddresses&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getNetworkAddresses&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`UDP Server listening on port &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;&#92;n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Listening on:&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`  Local:   &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  networkAddresses&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;addr&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    console&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`  Network: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;addr&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; getNetworkAddresses&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; interfaces&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;os&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;networkInterfaces&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; addresses&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; Object&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;keys&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;interfaces&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#4FC1FF&quot;&gt; iface&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; interfaces&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;iface&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;family&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; === &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;IPv4&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;#x26;&amp;#x26; !&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;iface&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;internal&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        addresses&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;iface&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE92A4&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; addresses&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final implementation cuts down response latency to about 3 seconds. The speech synthesis sounds quite natural, creating a convincing conversational experience. This prototype successfully demonstrates the core functionality needed for my final project&#39;s speech input.&lt;/p&gt;
&lt;p&gt;&lt;video controls=&quot;&quot; src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-8-first-contact/media/final-demo.mp4&quot;&gt;&lt;/video&gt;
&lt;strong&gt;Wrap up with an immersive demo. USB-C is only for power-supply.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;pcb-fabrication-revisited&quot;&gt;PCB Fabrication Revisited&lt;/h3&gt;
&lt;p&gt;In addition to the Adafruit ICS-43434 I2S breakout board, I reattempted fabricating my own microphone PCB after burning my first one in &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/#bonus-laser-cutting-my-own-ics-43434-breakout-board&quot;&gt;week 6&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I redesigned the PCB with several improvements. I rerouted the traces to match the pintout of the &lt;a href=&quot;https://www.adafruit.com/product/6049&quot;&gt;Adafruit ICS-43434 breakout board&lt;/a&gt; so it can be a drop-in replacement. I switched from Through-Hole to SMD components for easier soldering, planning to bend the legs of the TH pins to make them SMD-like. I also added rounded edges and mounting holes to make the board consistent with other components in my system.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-8-first-contact/media/cad-01.webp&quot; alt=&quot;Microphone PCB design&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Redesigned microphone PCB&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-8-first-contact/media/cad-02.webp&quot; alt=&quot;Microphone PCB 3D&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;3D view of the redesigned microphone PCB&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I decided to try a simpler process for laser cutting the PCB:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;First mill the holes and edge cut, with tabs to hold the PCB in place.&lt;/li&gt;
&lt;li&gt;Laser cut the traces. Then remove the tabs.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-8-first-contact/media/milling-01.webp&quot; alt=&quot;Milled PCB&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Milling was successful&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Unfortunately, the vacuum system for all the laser cutters in the shop was broken. I had to postpone the cutting. I realized my board needs another iteration anyway:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I increased the dimension of the board to fit the M3 mounting holes, but this caused the board to extend beyond the footprint of the main Switchboard. I need to shrink it back.&lt;/li&gt;
&lt;li&gt;I found rivets for making vias. I can switch to real PTH mounted headers instead of bending the legs to make them surface mount.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;appedix&quot;&gt;Appedix&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-8-first-contact/code/talkie-only.zip&quot;&gt;Project code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-8-first-contact/code/openai-transcription-streaming.md&quot;&gt;GitHub Copilot instructions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-8-first-contact/code/audio-breakout-mk2.zip&quot;&gt;Audio PCB design files&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Mon, 27 Oct 2025 00:00:00 GMT</pubDate>
      <dc:creator>Sun Chuanqi</dc:creator>
      <guid>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-8-first-contact/</guid>
    </item>
    <item>
      <title>Week 7: A Very Dangerous Machine</title>
      <link>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/</link>
      <description>&lt;p&gt;This week&#39;s schedule was a bit special. Due to limited machine time for the large-format CNC, I was only able to characterize the machine on the same day as my cutting session. This meant I essentially did the group assignment with my TA &lt;a href=&quot;https://dangilbert.pages.cba.mit.edu/home/&quot;&gt;Dan&lt;/a&gt;, as we iterated on our designs together right by the machine.&lt;/p&gt;
&lt;h2 id=&quot;project-context&quot;&gt;Project Context&lt;/h2&gt;
&lt;p&gt;I&#39;ve been racing mountain bikes since 2015, but I&#39;ve never quite mastered the technique of manualing. Manualing is the skill of lifting the front wheel off the ground and balancing on the rear wheel while riding, which is crucial for clearing obstacles on the trail as well as for the occasional show-off. A great reference video is &lt;a href=&quot;https://www.youtube.com/watch?v=NkWnV4RDzkU&quot;&gt;How To Manual Like A Pro – MTB Skills&lt;/a&gt;. To help riders learn this skill in a controlled environment, people have created &amp;quot;&lt;a href=&quot;https://www.google.com/search?udm=2&amp;amp;q=manual+machine+for+bikes&quot;&gt;Manual Machines&lt;/a&gt;&amp;quot;. I decided to build my own using the skills from this week.&lt;/p&gt;
&lt;h2 id=&quot;gathering-data&quot;&gt;Gathering Data&lt;/h2&gt;
&lt;p&gt;The first step was to get the critical dimensions of my bike. I found a geometry chart on the &lt;a href=&quot;https://otsocycles.com/collections/fenrir-ti&quot;&gt;manufacturer&#39;s website&lt;/a&gt;, which gave me the wheelbase (distance between the front and rear axles) and tire diameter. I also manually measured the tire width, which came out to be 55 mm, slightly narrower than the 56-57 mm stated in the &lt;a href=&quot;https://www.renehersecycles.com/shop/components/tires/700c/700c-x-55-fleecer-ridge/&quot;&gt;official spec&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/init-02.webp&quot; alt=&quot;Bike Geometry&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Bike Geometry&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;paper-prototyping&quot;&gt;Paper Prototyping&lt;/h2&gt;
&lt;p&gt;I started with a quick hand sketch to get a feel for the form and how the 3D parts would relate to each other.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/init-03.webp&quot; alt=&quot;Sketch&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Sketching the Design&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;With the practice from &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-2-making-the-cut/&quot;&gt;Week 2&lt;/a&gt;, I was shocked at how quickly I could move from a sketch to a physical prototype. I used the laser cutter to make a small-scale model. After a quick test, I found that 85% power at 70 mm/s speed with a 0.12 mm kerf worked well for the cardboard.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/paper-01c.webp&quot; alt=&quot;Characterize and cut&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Characterize and Cut&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The initial assembly felt wobbly. I realized I could tilt the joints, using gravity to create more stable connections. This small-scale model was invaluable for identifying weak points in the design.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/paper-01d.webp&quot; alt=&quot;Paper Prototype&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Initial Paper Prototype&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I iterated on the fastening mechanism to improve stability. In the final version, I introduced through-holes, slanted joints, and a press-fit &amp;quot;tail bone&amp;quot;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/paper-02b.webp&quot; alt=&quot;Paper Prototype 2&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Final Paper Prototype&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;scale-up&quot;&gt;Scale Up&lt;/h2&gt;
&lt;p&gt;When I started the paper prototype, I didn&#39;t know what material we would be using for the final build. I had assumed 2x4 lumber, but later found out we would be using 4x8 ft sheets of Oriented Strand Board (OSB). This change in material forced me to rethink the design to work with sheet stock.&lt;/p&gt;
&lt;p&gt;I scaled up the model in Onshape, using a picture of my bike as an underlay to trace its geometry and ensure a perfect fit.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/full-scale-cad.webp&quot; alt=&quot;Designing the full scale model&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Designing the Full Scale Model&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Special thanks to Dan, who helped me set up the machine and run the job. I did all my CAD work in Onshape, which unfortunately does not have a free CAM solution for students. Dan generously let me use his Fusion 360 license to generate the toolpaths.&lt;/p&gt;
&lt;p&gt;During the first CAM simulation, I realized I had misunderstood how dogbones work. I had placed the center of the dogbone circle at the corner of the part, creating a bottleneck that the drill bit couldn&#39;t navigate.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/dogbone-v1.webp&quot; alt=&quot;Dogbone Incorrect&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Dogbone Incorrect&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The key insight was that the goal is to create the smallest possible cut that allows the mating part to fit perfectly, which doesn&#39;t require the circle&#39;s center to be at the corner.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/dogbone-v4.webp&quot; alt=&quot;Dogbone Workaround&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Identifying the bottleneck&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The correct placement of the dogbone is to make the corner coincide with the circle.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/dogbone-v3.webp&quot; alt=&quot;Dogbone Corrected&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Dogbone Corrected&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Due to limited machine time, I went with the practical decision of simply increasing my circle&#39;s radius until the bottleneck was wide enough.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/dogbone-v5.webp&quot; alt=&quot;Dogbone parameter&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Parametric Design Saved My Day&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Thanks to Parametric Design, I could iterate quickly. This resulted in a 9 mm circle at each right corner angle of my shapes. This approach is less efficient and slightly undermines the structure.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/dogbone-v6.mp4&quot; controls=&quot;&quot; muted=&quot;&quot;&gt;&lt;/video&gt;
&lt;strong&gt;Verifying Toolpath After Dogbone Fix&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;With the dogbones issue addressed, I generated the toolpaths using 2D Contour cuts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Job 1:&lt;/strong&gt; A single pass down to -0.2 inches.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Job 2:&lt;/strong&gt; A multi-pass cut from -0.2 inches down to -0.45 inches, intended to cut all the way through.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/cutting-01.webp&quot; alt=&quot;Milling in progress&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Milling in Progress&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;However, during the milling, Job 2 didn&#39;t cut all the way through the material.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/cutting-03.webp&quot; alt=&quot;Milling failure&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;It should have cut through here...&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After the job, I manually measured and calculated the remaining thickness, and created a third job to cut through the rest of the material.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/cutting-02.webp&quot; alt=&quot;Measuring remaining thickness&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Measuring Remaining Thickness&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This final pass successfully cut the parts out. Comparing my parameters with others, I realized my mistake: I had set the stock height to 11.2 mm (thinnest measurement) while others used 12 mm (thickest measurement). I believe this discrepancy is why the machine didn&#39;t cut through on the first attempt.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/cutting-04.webp&quot; alt=&quot;Job 3 milled through&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Job 3 Milled Through&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Job 1&lt;/th&gt;
&lt;th&gt;Job 2&lt;/th&gt;
&lt;th&gt;Job 3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Clearance Height&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;From&lt;/td&gt;
&lt;td&gt;Retract height&lt;/td&gt;
&lt;td&gt;Retract height&lt;/td&gt;
&lt;td&gt;Retract height&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offset&lt;/td&gt;
&lt;td&gt;0.4 in&lt;/td&gt;
&lt;td&gt;0.4 in&lt;/td&gt;
&lt;td&gt;0.4 in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Retract Height&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;From&lt;/td&gt;
&lt;td&gt;Stock top&lt;/td&gt;
&lt;td&gt;Stock top&lt;/td&gt;
&lt;td&gt;Stock top&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offset&lt;/td&gt;
&lt;td&gt;0.2 in&lt;/td&gt;
&lt;td&gt;0.2 in&lt;/td&gt;
&lt;td&gt;0.2 in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Feed Height&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;From&lt;/td&gt;
&lt;td&gt;Top height&lt;/td&gt;
&lt;td&gt;Top height&lt;/td&gt;
&lt;td&gt;Top height&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offset&lt;/td&gt;
&lt;td&gt;0.2 in&lt;/td&gt;
&lt;td&gt;0.2 in&lt;/td&gt;
&lt;td&gt;0.2 in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Top Height&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;From&lt;/td&gt;
&lt;td&gt;Stock top&lt;/td&gt;
&lt;td&gt;Stock top&lt;/td&gt;
&lt;td&gt;Stock top&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offset&lt;/td&gt;
&lt;td&gt;0 in&lt;/td&gt;
&lt;td&gt;-0.2 in&lt;/td&gt;
&lt;td&gt;-0.45 in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bottom Height&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;From&lt;/td&gt;
&lt;td&gt;Stock top&lt;/td&gt;
&lt;td&gt;Stock top&lt;/td&gt;
&lt;td&gt;Stock top&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offset&lt;/td&gt;
&lt;td&gt;-0.2 in&lt;/td&gt;
&lt;td&gt;-0.45 in&lt;/td&gt;
&lt;td&gt;-0.56 in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multiple Depths&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enabled&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maximum Roughing Stepdown&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.2 in&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;assemble-and-enhance&quot;&gt;Assemble and Enhance&lt;/h2&gt;
&lt;p&gt;I used tabs to keep the parts in place during milling, and removing them wasn&#39;t trivial. I used a &lt;a href=&quot;https://www.milwaukeetool.com/products/power-tools/woodworking/oscillating-multi-tool&quot;&gt;Milwaukee oscillating multi-tool&lt;/a&gt; with a bi-metal blade to cut the tabs and flatten the edges.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/cutting-05.webp&quot; alt=&quot;Removing tabs&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Removing Tabs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A quick safety note: wearing gloves is essential when working with OSB. A wood splinter cut straight through my glove and stabbed my thumb. Had I not been wearing the glove, the injury could have been much worse.&lt;/p&gt;
&lt;p&gt;The first assembly on the floor went smoothly. My calculations for the material thickness were perfect, and all the joints had a snug fit without any glue or fasteners.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/testing-00.webp&quot; alt=&quot;First Assembly on Floor&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;First Assembly on Floor&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;However, adding the bike revealed two problems: I had miscalculated the tire diameter, and the middle section felt a bit wobbly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/testing-01.webp&quot; alt=&quot;First Assembly with Bike&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;First Assembly with Bike&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The tire fit can be addressed with spacers. The weak middle can be reinforced with brackets. I measured the gap between the tire and the wood to determine the size of the needed spacers.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/testing-02.webp&quot; alt=&quot;Measuring Gap&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Measuring Gap&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Due to limited machine time, I designed reinforcements that I could cut manually on a band saw.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/testing-03.webp&quot; alt=&quot;Handle calculating the redesign&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Handle Calculating the Redesign&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I learned how to use the band saw by observing and asking a classmate. Special shout-out to &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/CharlesLu/&quot;&gt;Charlie&lt;/a&gt; who happened to be using the band saw in the shop and generously demoed his technique to me. The key is to cut from the side with loose wood chips, so the saw&#39;s downward motion holds the OSB against the table and minimizes splintering.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/enhancing-01.webp&quot; alt=&quot;Cutting reinforcements&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Cutting Reinforcements with Band Saw&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I used a &lt;a href=&quot;https://www.milwaukeetool.com/products/2486-20&quot;&gt;Milwaukee Straight Die Grinder&lt;/a&gt; to smooth the corners that the band saw left rough.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/enhancing-02.webp&quot; alt=&quot;Detailing&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Detailing with Die Grinder&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;One of the reinforcements was a bit loose, a result of a slight error in my design. This taught me that it&#39;s better to design for a tight fit and then iteratively sand or file it down until it fits perfectly. Adding the spacers and reinforcements significantly improved the structure&#39;s rigidity.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/enhancing-03.webp&quot; alt=&quot;Final Assembly&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Final Assembly with Spacers and Reinforcements (Colored Blue)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For the safety tether, I tied a rope using a flexible knot, anchored by a washer. The rope was from an unknown source and seemed weak. A proper design would use rated cargo &lt;a href=&quot;https://en.wikipedia.org/wiki/Tie_down_strap&quot;&gt;tie-down straps&lt;/a&gt; with a secure buckle mechanism.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/enhancing-04.webp&quot; alt=&quot;Rope Anchor&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Rope Anchor, with Fraying Core&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;testing&quot;&gt;Testing&lt;/h2&gt;
&lt;p&gt;With the machine fully assembled, it was time to test. Thank you &lt;a href=&quot;https://www.quincykuang.com/&quot;&gt;Quincy&lt;/a&gt; and &lt;a href=&quot;https://yuhanwang.net/&quot;&gt;Yuhan&lt;/a&gt; for photography, video, and physical support.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Preparation:&lt;/strong&gt; Helmet and gloves on. Removing seat post for extra clearance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Static Test:&lt;/strong&gt; I started by placing one foot on the ground, rotating the bike backward, holding the brake, and mounting. The structure felt solid.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/demo-01.webp&quot; alt=&quot;Demo 1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Functional Test&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Limit Test:&lt;/strong&gt; I asked my friend to support me while I rotated as far back as possible to test the strength of the rope tether.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/demo-02.webp&quot; alt=&quot;Demo 2&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Limit Test&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Full Test:&lt;/strong&gt; Finally, I attempted to bring the bike up from the ground into a manual without any assistance. I noticed that the device was jerking forward each time I brought the bike up. Future improvements should include rubber feet to increase friction with the ground.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;video src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/media/demo-03.mp4&quot; controls=&quot;&quot;&gt;&lt;/video&gt;
&lt;strong&gt;Final Test&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The machine was a big success, but my demo needs practice. I promptly landed on my butt, learning the lesson the &amp;quot;hard way&amp;quot;. Sometimes, the tools you create can be more dangerous than the tools that created them.&lt;/p&gt;
&lt;h2 id=&quot;key-learnings&quot;&gt;Key Learnings&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Parametric Design for Joints:&lt;/strong&gt; Separate parameters for material thickness and slot size. Use the thickest measurement for material, but make slots slightly smaller for press-fit joints.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Measure Physical Objects:&lt;/strong&gt; Always measure the actual object directly rather than relying on images or specifications to avoid dimensional errors.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Understand Materials Before Design:&lt;/strong&gt; Survey available materials before starting your design to avoid unnecessary redesigns.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stay Cautious During Testing:&lt;/strong&gt; Maintain caution during physical testing, even when things work well. Don&#39;t let excitement lead to unnecessary risks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use Integrated CAD/CAM Software:&lt;/strong&gt; Using separate software for CAD (Onshape) and CAM (Fusion 360) added significant overhead in exporting and importing files. An integrated solution would streamline the workflow and reduce potential errors in translation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;appendix&quot;&gt;Appendix&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/model/manual-machine-mini-laser-cut.dxf&quot;&gt;Paper prototype model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/model/manual-machine-full-size.step&quot;&gt;Full scale CNC model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/code/shopbot-job-code.sbp&quot;&gt;CNC Job code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Sat, 18 Oct 2025 00:00:00 GMT</pubDate>
      <dc:creator>Sun Chuanqi</dc:creator>
      <guid>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-7-a-very-dangerous-machine/</guid>
    </item>
    <item>
      <title>Week 6: Wish I Were The Steves</title>
      <link>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/</link>
      <description>&lt;p&gt;The legend goes that Steve Jobs and Steve Wozniak hand-built the first Apple-1 computers in their garage. This week, I learned how to hand-build something simpler, but in the same spirit: a custom microcontroller board.&lt;/p&gt;
&lt;h2 id=&quot;group-assignment&quot;&gt;Group assignment&lt;/h2&gt;
&lt;p&gt;Our group assignment was to characterize the design rules for our Carvera PCB milling machine. We started by reading the excellent &lt;a href=&quot;https://quentinbolsee.pages.cba.mit.edu/carvera-pcb-tutorial/&quot;&gt;tutorial by our TA Quentin&lt;/a&gt;, then started producing a test PCB to understand the machine&#39;s capabilities.
&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/group-01.webp&quot; alt=&quot;Characterizing the Carvera PCB milling machine&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Characterizing the Carvera PCB milling machine&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Our first run resulted in large amounts of burrs along the edges of the traces. We learned this was due to a damaged endmill and subsequently learned how to replace the bit. The machine is able to consistently handle traces down to 0.2mm.
&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/group-02.webp&quot; alt=&quot;Burrs everywhere&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Burrs everywhere&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Later, I attended a second session with Kristof where we debugged the milling machine after it entered a halted state. We documented our debugging process and findings in our &lt;a href=&quot;https://fab.cba.mit.edu/classes/MAS.863/CBA/group_assignments/week6/&quot;&gt;group note&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;fabricating&quot;&gt;Fabricating&lt;/h2&gt;
&lt;p&gt;For my final project, I designed two main components: a hand-held unit I call &amp;quot;the Operator&amp;quot; and a main body unit called &amp;quot;the Switchboard.&amp;quot; Having completed the circuit simulations in the previous week, I already had initial designs for both boards. This week, my focus shifted to the physical milling and fabrication process.&lt;/p&gt;
&lt;h3 id=&quot;the-operator&quot;&gt;The Operator&lt;/h3&gt;
&lt;p&gt;The Operator is a dev board for the Xiao-ESP32-C3 that connects a Microphone, DAC/Amp, TRRS jack, and a couple of switches. Milling the first version of the Operator board, a single-sided design, was straightforward.&lt;/p&gt;
&lt;p&gt;In KiCad, export the PCB design as Gerber files and drill files.
&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/operator-01.webp&quot; alt=&quot;Export Gerber files&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Export Gerber files&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/operator-02.webp&quot; alt=&quot;Export drill files&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Export drill files&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Upload both files at the same time to Quentin&#39;s &lt;a href=&quot;https://quentinbolsee.pages.cba.mit.edu/gerber2img/&quot;&gt;gerber2img&lt;/a&gt; tool to convert the copper layer, edge cut layer, and drill files into PNG images.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For the exterior cut, I used the &amp;quot;Black and white&amp;quot; setting and checked &amp;quot;Fill edge cut&amp;quot; to create a solid outline including the drill holes.&lt;/li&gt;
&lt;li&gt;For the traces, I unchecked this option.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use Neil&#39;s &lt;a href=&quot;https://modsproject.org/&quot;&gt;modsproject&lt;/a&gt; with the Carvera mill 2D PCB program to generate the final G-code for the milling machine. The tool is self-explanatory. Just follow the pipeline to proceed.&lt;/p&gt;
&lt;p&gt;The initial milling produced some burrs, but after a bit of sanding, the results were very good.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/operator-03-A.webp&quot; alt=&quot;Milled PCB&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Before sanding&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/operator-03-B.webp&quot; alt=&quot;Milled PCB&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;After sanding&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;However, as I gathered components for stuffing the board, I saw my mistake: we didn&#39;t have any surface-mount pin connector sockets in the lab. I could either bend the legs of the through-hole connectors or redesign the board. This was a key lesson: &lt;strong&gt;check component availability and stay flexible during the design process.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Bending the legs of the through-hole connectors was not ideal: they added extra height and also undermined the reliability of the connection. I also took TA feedback into account and decided to redesign the board as a two-sided PCB with a ground plane on the back, which would also simplify routing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/operator-04.webp&quot; alt=&quot;Two-sided PCB design&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Two-sided PCB design&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Thanks to this change, I learned how to fill a ground plane in KiCad and produced my new design.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/operator-05.webp&quot; alt=&quot;Ground plane filling&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Ground plane filling&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This led to my second mistake, which I thankfully caught before milling. When I switched from surface-mount to through-hole components, I didn&#39;t consider the soldering process. I had assumed we would have plated through-holes (PTH), allowing me to solder on either side. Without PTH, I would have to solder components on the opposite side of the board and use vias to connect traces between the front and back. This required another complete redesign to account for these manufacturing constraints. &lt;strong&gt;Lesson learned: think about the manufacturing process during design.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/operator-05-B.webp&quot; alt=&quot;Socket solder issue&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;You cannot solder under the plastic&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In the final redesign, I added vias, M2 mounting holes, and rounded edges for a more polished board.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/operator-07.webp&quot; alt=&quot;Final two-sided PCB design&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Final two-sided PCB design&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Milling a two-sided PCB proved to be a challenge. While there is &lt;a href=&quot;https://sibusaman.fabcloud.io/doublepcb/&quot;&gt;a clever trick&lt;/a&gt; using symmetry to create a fixture for perfect alignment, I opted for a manual calibration method. I milled the first side, measured the machine&#39;s offset, and calculated the new origin for the second side.&lt;/p&gt;
&lt;p&gt;My first attempt failed because I forgot to mirror the backside image and didn&#39;t include the tab offset for both sides, resulting in total misalignment.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/operator-06.webp&quot; alt=&quot;Misaligned holes&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Everything that could have gone wrong did go wrong&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The second attempt, however, was a success. The holes aligned perfectly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/operator-08.webp&quot; alt=&quot;Good alignment from manually calculating the offset&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Good alignment from manually calculating the offset&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here are the calculations I used to align the origins:&lt;/p&gt;
&lt;pre class=&quot;shiki dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Backside milled first&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;bottom_offset: 5 mm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;entered_left_offset: 3.9265 mm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;expected_right_offset: 3.9265 mm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;actual_left_offset: 5.51 mm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;actual_right_offset: 4.74 mm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Frontside calculation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;bottom_offset: 5 mm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;entered_left_offset: 3.156 mm = 4.74 - (5.51 - 3.9265)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Soldering this board was much harder than expected. The non-plated through-holes had limited grip on the solder, even with flux. I had to reflow several connections multiple times to ensure a solid connection.&lt;/p&gt;
&lt;p&gt;Checking with the TAs, I received the advice to use a higher temperature or to preheat the pins longer. They diagnosed me with a &amp;quot;cold solder joint&amp;quot; issue.&lt;/p&gt;
&lt;h3 id=&quot;the-switchboard&quot;&gt;The Switchboard&lt;/h3&gt;
&lt;p&gt;The Switchboard is an array of TRRS jacks, each with an LED to indicate status.&lt;/p&gt;
&lt;p&gt;For the Switchboard PCB, I made almost all the same mistakes as with the Operator. My original design also assumed plated through-holes. Luckily, the Switchboard is a single-sided board, so I could move all the components to the other side and adjust the circuit without needing vias.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/switchboard-00.webp&quot; alt=&quot;Original Design&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Original design without considering soldering&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;However, I then made two more errors:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I forgot to mirror the component footprints after flipping them to the other side.&lt;/li&gt;
&lt;li&gt;I used the wrong pins for the connectors after flipping the design and only realized it after milling.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/switchboard-02.webp&quot; alt=&quot;Corrected Design&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Corrected design with mirrored footprints and correct pins&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I didn&#39;t notice the mirroring problem until I had finished soldering all the resistors for the LEDs. At that point, I decided to capitalize on the mistake and use the partially assembled board to test the LED circuit. I placed an LED on the pads without solder and used a multimeter to light it up. It worked!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/switchboard-03.webp&quot; alt=&quot;Testing LED circuit&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Testing LED circuit&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;bonus-laser-cutting-my-own-ics-43434-breakout-board&quot;&gt;Bonus: Laser cutting my own ICS-43434 breakout board&lt;/h3&gt;
&lt;p&gt;I needed a breakout board for the ICS-43434 microphone, similar to &lt;a href=&quot;https://www.digikey.com/en/products/detail/adafruit-industries-llc/6049/25589349&quot;&gt;this one from Adafruit&lt;/a&gt;. I decided to try fabricating it with a laser cutter.&lt;/p&gt;
&lt;p&gt;I followed a &lt;a href=&quot;https://www.youtube.com/watch?v=8peIFpolsmk&quot;&gt;YouTube tutorial&lt;/a&gt; to set up the machine, but his settings UI was different from mine. Instead of speed (mm/s), I had dot duration (microseconds). I proceeded with a test cut with varying duration values. The results were terrible: some parts of the copper were burned away, but the surface wasn&#39;t fully removed. The laser also seemed to have reduced power towards the edges of the cutting area.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/audio-01.webp&quot; alt=&quot;Failed laser cutting&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Failed test&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I soon realized the problem was the image format. Switching to SVG files exposed the correct settings. The &lt;code&gt;gerber2img&lt;/code&gt; tool didn&#39;t export SVGs, and KiCad&#39;s native SVG export produced the wrong shapes when opened in xTool. I ended up using the &lt;a href=&quot;https://www.adobe.com/express/feature/image/convert/png-to-svg&quot;&gt;Adobe PNG to Vector converter&lt;/a&gt; and then manually cleaned up the SVG in &lt;a href=&quot;https://www.figma.com/&quot;&gt;Figma&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I settled on the following workflow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Drill alignment holes with the PCB mill first.&lt;/li&gt;
&lt;li&gt;Use the laser cutter to ablate the copper for the traces, using the holes for alignment.&lt;/li&gt;
&lt;li&gt;Use the mill again to cut the board&#39;s outline.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I scaled the SVG to match the measured dimension in KiCad before applying it on top of the drill holes.
&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/audio-02.webp&quot; alt=&quot;Alignment&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Alignment&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I first made the mistake that the image was outdated after I moved the stock. This caused the laser to engrave on the bed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/mistake-01.webp&quot; alt=&quot;Engraving on the bed&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Engraving on the bed&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The xTool V1 Ultra&#39;s camera alignment was slightly off. Even with careful alignment in the software, the first few results were shifted.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/audio-03.webp&quot; alt=&quot;Misalignment&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;The perfect alignment turned out to be offset by 0.3mm on both axes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I also dialed in the machine settings. It took a total of 20 passes to completely remove the copper.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/audio-07.webp&quot; alt=&quot;Perfect settings&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;10 passes each, processed twice&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After applying a manual offset, my fourth attempt produced a very good result and was ready for the edge cut. The tolerance for edge cutting is wide. I visually aligned the outline using the Carvera&#39;s laser scan. It worked perfectly.
&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/audio-04.webp&quot; alt=&quot;Laser scan for edge cut&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Laser scan for edge cut&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/audio-06.webp&quot; alt=&quot;Multiple attempts&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;The fourth attempt was good enough for milling&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In retrospect, I could have drilled the holes and cut the outline in one go before laser cutting. That would save a trip to the mill.&lt;/p&gt;
&lt;p&gt;I also checked with TA Quentin that the xTool software has its own vectorize tool. I planned to use that next time instead of using Adobe and Figma.&lt;/p&gt;
&lt;p&gt;Soldering the ICS-43434 was nightmarish. The component has tiny pads on its underside, which are unreachable with a soldering iron. The laser-etched traces were so thin they could barely hold any solder.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/audio-05.webp&quot; alt=&quot;How to solder this?&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;How to solder this?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I used a heat gun, hoping surface tension would do the work, but the heat began to damage the microphone&#39;s plastic casing. An attempt to heat the board from the bottom nearly burned the PCB before the solder melted. There was a warning during the lecture that FR1 was a poor heat conductor so I should have avoided this in the first place. After many tries, I managed to get it positioned with no shorts between the pads, but I have yet to test if the microphone actually works.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/audio-09.webp&quot; alt=&quot;Damaged PCB&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Heating up the PCB from the bottom resulted in damage&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I tested with a multimeter and confirmed there was no short. But it was unclear whether the microphone survived the heat damage.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/audio-08.webp&quot; alt=&quot;Front view&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Microphone breakout, soldered&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At this point, I realized that the pinout on my board is not the same as the Adafruit breakout board which my Operator board assumes. The only way for it to be compatible is to use jumper wires to sort out the connections. This was not ideal, but I had no choice now as it was already 3 AM.&lt;/p&gt;
&lt;p&gt;Later, I received advice from TA Quentin that I could use solder paste. The process looks like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apply solder paste to the pads&lt;/li&gt;
&lt;li&gt;Heat from above to let the solder paste flow into the trace&lt;/li&gt;
&lt;li&gt;Place the component and heat again from above and let surface tension do the work&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This could be something to try for v2.&lt;/p&gt;
&lt;h3 id=&quot;integration-test&quot;&gt;Integration Test&lt;/h3&gt;
&lt;p&gt;These were all the components I made this week. From top, clockwise:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ICS-43434 breakout board (laser cut)&lt;/li&gt;
&lt;li&gt;Switchboard (milled, single-sided)&lt;/li&gt;
&lt;li&gt;Operator (milled, two-sided)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/test-00.webp&quot; alt=&quot;Components&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;All the components fabricated this week&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let&#39;s assemble and test. Since we plan to have dedicated I/O and networking weeks, I focused only on basic connectivity.&lt;/p&gt;
&lt;p&gt;I programmed the Switchboard to supply digital signals to each TRRS socket, and programmed the Operator to read the digital signal and determine which pins have high voltage. To simulate the final project scenario, I used a real TRRS male and female connector pair.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/test-01.webp&quot; alt=&quot;Assembly&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Assembly with real TRRS connectors&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I was able to confirm from the serial output that all the TRRS sockets are giving out distinct high/low voltage patterns.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/media/test-02.webp&quot; alt=&quot;Digital readings&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Digital readings from TRRS sockets&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Key observations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unplugging/replugging caused the reading to be high momentarily&lt;/li&gt;
&lt;li&gt;It took 1-2 seconds for the reading to stabilize after plugging in&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I switched the digital pins to use &lt;code&gt;PULL_UP&lt;/code&gt; mode, and the floating HIGH issue disappeared. I also realized that I need to reserve the &lt;code&gt;LOW LOW LOW&lt;/code&gt; reading for unplugged state, therefore, I only have 7 distinct states for 8 TRRS sockets.&lt;/p&gt;
&lt;h2 id=&quot;key-lessons&quot;&gt;Key lessons&lt;/h2&gt;
&lt;p&gt;This week was a gauntlet of trial and error, but the lessons were invaluable.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Heeding the instructions in class.&lt;/strong&gt; could have prevented the burning of the ICS-43434 breakout board.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check component availability before designing.&lt;/strong&gt; A simple stock check can save a complete redesign.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Think about the manufacturing process during design.&lt;/strong&gt; Constraints like non-plated through-holes fundamentally change how a board must be laid out.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Double-check orientations,&lt;/strong&gt; especially when two-sided PCBs are involved. Mirroring is easy to forget and fatal to a design.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Homemade through-holes do not solder as easily as commercial PTH.&lt;/strong&gt; If possible, consider using surface-mount components for easier assembly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&#39;t rely on intuition.&lt;/strong&gt; I found myself inclined to make the same mistake over and over. A checklist or a more rigorous verification process is essential.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;appendix&quot;&gt;Appendix&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/code/operator-20251013.zip&quot;&gt;Operator PCB design files&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/code/switchboard-20251013.zip&quot;&gt;Switchboard PCB design files&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/code/ics-43434-breakout-20251013.zip&quot;&gt;ICS-43434 breakout board design files&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/code/integration-test-20251013.zip&quot;&gt;Integration test code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Mon, 13 Oct 2025 00:00:00 GMT</pubDate>
      <dc:creator>Sun Chuanqi</dc:creator>
      <guid>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-6-wish-i-were-the-steves/</guid>
    </item>
    <item>
      <title>Week 5: Into the Matrix</title>
      <link>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/</link>
      <description>&lt;h2 id=&quot;group-assignment&quot;&gt;Group assignment&lt;/h2&gt;
&lt;p&gt;For the group assignment, &lt;a href=&quot;https://fab.cba.mit.edu/classes/863.23/CBA/people/Alan/&quot;&gt;Alan Han&lt;/a&gt; explained power supply, multimeter, oscilloscope, function generator, and logic analyzer. I used my own project to study the logic analyzer. The device used MAX98357A as DAC and amp but had an issue producing sound. During the debugging session with the TAs (Nikhil and Alan), we ruled out power issue by observing stable power voltage. We also ruled out connection issue as all the lines: clock, channel, and data were normal. We found low voltage on speaker. Eventually, we realized the bug was software. The diagnostic tool was helping in eliminating hardware issues.&lt;/p&gt;
&lt;p&gt;During the testing, we noticed that the plugging in/out of probes can interfere with the I2S signal. Presumably because of the motion of the probes causing inductance change, which in turn, distorted the clock or data line.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/debug.webp&quot; alt=&quot;Hooking up logic analyzer probes&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Hooking up logic analyzer probes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We used saleae Logic 2 analyzer with the &lt;a href=&quot;https://www.saleae.com/pages/downloads?srsltid=AfmBOootFb68Y2L5odb06p_WkZ1gnm-TIDW_Hhu8xv7w9I_agw8oQwBw&quot;&gt;companion software&lt;/a&gt;. The tool can visualize both digital and analog signals and allowed us to choose a protocol and decode the data. Very helpful.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/logic-analyzer.webp&quot; alt=&quot;Visualize data&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Visualize data&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;simulation&quot;&gt;Simulation&lt;/h2&gt;
&lt;p&gt;I wanted to simulate the hand-held device of my final project. For simulation, I wanted to study the idea of addressing unique 3.5mm audio jacks using binary encoding.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/trrs-jack.webp&quot; alt=&quot;TRRS jack&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;TRRS jack&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;One pin for ground, 3 pins for 2^3 = 8 unique addresses. I used an 8-pin DIP switch to simulate the 3-bit binary input. In production, the input will be determined by the jack that the user plugs in. The jack&#39;s identity only matters in the software, but to make the simulation fun, I added an LCD display to show which jack is plugged in. Since it&#39;s a walkie talkie, I added a push button to show TTS, and a slide switch to toggle between modes: programming vs. interaction. I used wokwi, which provided the Xiao-ESP32-C3 board that my project uses. For production, there won&#39;t be an LCD, but will be an I2C DAC/amp and a I2C ADC for audio input/output. I will prototype them for the actual PCB design. If there are enough I/O pins, I will consider a second button so user can squeeze both to enable broadcast mode.&lt;/p&gt;
&lt;p&gt;&lt;video controls=&quot;&quot; muted=&quot;&quot; src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/simulation-1.mp4&quot;&gt;&lt;/video&gt;
&lt;strong&gt;Simulate the hand-held device&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I also wanted to simulate the main body device. It has two roles: lighting up an LED for a specific jack, where the computer will control which jack is active and use wifi to activate it, and setting the 3-bit address for each jack, which can be hardwired directly from Xiao&#39;s 3.3v output. To make the simulation more interesting, I cycled the LEDs in order, just to show I can address them by software.&lt;/p&gt;
&lt;p&gt;&lt;video controls=&quot;&quot; muted=&quot;&quot; src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/simulation-2.mp4&quot;&gt;&lt;/video&gt;
&lt;strong&gt;Simulate the main device&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;designing-the-operator&quot;&gt;Designing the Operator&lt;/h2&gt;
&lt;p&gt;To prepare KiCad with the symbols, footprints, and 3D models, I followed the &lt;a href=&quot;https://gitlab.fabcloud.org/pub/libraries/electronics/kicad&quot;&gt;documentation&lt;/a&gt; to install our course library. In addition, depending on the installation method, you may need to manually add additional libraries from the &lt;a href=&quot;https://www.kicad.org/libraries/download/&quot;&gt;official repo&lt;/a&gt;. When installing with Flatpak, the official 3D models were already included.&lt;/p&gt;
&lt;p&gt;I wanted to start the PCB design towards my final project, using KiCad. The schematic design is very simple: layout the Xiao-ESP32-C3, I2S Microphone, I2S DAC/Amp, a TRRS jack, and a couple of switches.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/schematic-01.webp&quot; alt=&quot;Schematic&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Schematic design&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As soon as I transitioned to the PCB design, I realized the problem: I hoped to use the MAX98357A and ICS-43434 both on their breakout boards, not as bare ICs.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/pcb-01.webp&quot; alt=&quot;PCB design&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;PCB design missing breakout boards&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Also, I don&#39;t want to solder them onto the same board as the Xiao. As a consumer, I enjoy electronics that come with user repairable parts. So I decided to pivot: design a development board that helps me route the pins from the Xiao to the headers where I can plug in the breakout boards. If I accidentally damage any component, I want to make it easy to swap out the broken part.&lt;/p&gt;
&lt;p&gt;Starting over, I put down the schematic design using pin sockets and loaded them into PCB editor. When I opened the 3D view, I realized that I chose the wrong connector.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/schematic-02.webp&quot; alt=&quot;Schematic design&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Schematic design with the wrong sockets&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/pcb-02.webp&quot; alt=&quot;PCB design&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;PCB design with the wrong sockets&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I wanted female socket, but got the male pin instead. Switching to female socket, the symbol exists, but they are missing footprint. Luckily, I was able to find a similar footprint. Assigning the footprint to the symbol allowed me to open the PCB editor based on schematic design.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/missing-footprint.webp&quot; alt=&quot;Missing footprint&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;The symbol is missing footprint&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/assign-footprint.webp&quot; alt=&quot;Assign footprint&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Manually assign footprint&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/schematic-02-fixed.webp&quot; alt=&quot;Schematic design fixed&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Schematic design with the right sockets&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/3d-01.webp&quot; alt=&quot;3D view confirmed&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;3D view confirmed socket type&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Just as I thought I was approaching the finish line, I recalled to run the constraint checker. I realized I had chosen the wrong track width. Instead of 0.2mm, I should have chosen 0.4mm.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/track-width.webp&quot; alt=&quot;Selecting track width&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;I should have selected track width sooner&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/minimum-clearance.webp&quot; alt=&quot;Setting minimum clearance&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Set minimum clearance&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It took me a while to manually fix all the wires. I also marked up the area for the speaker, just to get a sense of scale.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/pcb-03.webp&quot; alt=&quot;PCB design&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;PCB design with the correct sockets&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Feeling a bit uncertain, I went to the office of our TA Alfonso and got the immediate bad news: I didn&#39;t run constraint checker after changing the track width. Running the constraint checker again, more problems appeared. Because I laid out the wires using 0.2mm track, now the wires are touching the keepout area. After several rounds of manual fixing, I felt that it was impossible to clear the errors without crossing at least one wire. Alfonso agreed and encouraged me to try using 0 ohm resistors as a jumper.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/schematic-03.webp&quot; alt=&quot;Crossing the wire&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Crossing the wire&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Adding a jumper was not as straightforward as I thought. I had to go back to add the symbol, then redo almost the entire PCB design due to position shifting. The resistor jumper requires additional clearance, forcing me to push out the speaker area.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/jumper.webp&quot; alt=&quot;Jumper in schematic&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Jumper in schematic&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/3d-02.webp&quot; alt=&quot;Final 3D view&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;3D view with the jumper&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;designing-the-switchboard&quot;&gt;Designing the Switchboard&lt;/h2&gt;
&lt;p&gt;After designing the hand-held device PCB, I felt more comfortable with the tool and quickly designed the main body PCB. For each TRRS jack, I added an LED as status indicator. I &amp;quot;hardwired&amp;quot; the 3-bit address from Xiao to each jack. I used resistors to protect the LEDs from high voltage.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/schematic-04.webp&quot; alt=&quot;Main body schematic&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Main body schematic&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/pcb-04.webp&quot; alt=&quot;Main body PCB design&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Main body PCB design&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/media/3d-03.webp&quot; alt=&quot;Final 3D view&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;strong&gt;Main body 3D view&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Key lessons learned: Apply design rules before starting PCB layout. Check constraints often. Do not lay the wires too early. They are not easy to change.&lt;/p&gt;
&lt;h2 id=&quot;appendix&quot;&gt;Appendix&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/code/operator-202510062100.zip&quot;&gt;Hand-held device KiCad project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/code/switchboard-202510062144.zip&quot;&gt;Switchboard KiCad project&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Sat, 04 Oct 2025 00:00:00 GMT</pubDate>
      <dc:creator>Sun Chuanqi</dc:creator>
      <guid>https://fab.cba.mit.edu/classes/863.25/people/SunChuanqi/posts/week-5-into-the-matrix/</guid>
    </item>
  </channel>
</rss>