<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.2.1">Jekyll</generator><link href="https://goq2q.net/feed.xml" rel="self" type="application/atom+xml" /><link href="https://goq2q.net/" rel="alternate" type="text/html" /><updated>2024-08-26T22:59:36-06:00</updated><id>https://goq2q.net/feed.xml</id><title type="html">Q2Q</title><subtitle>Cross-platform sound cueing software for live theatre</subtitle><author><name>John Wostenberg</name></author><entry><title type="html">Launching Q2Q 1.0.0</title><link href="https://goq2q.net/blog/announcements/announcing-q2q-1_0_0" rel="alternate" type="text/html" title="Launching Q2Q 1.0.0" /><published>2024-06-20T00:00:00-06:00</published><updated>2024-06-20T00:00:00-06:00</updated><id>https://goq2q.net/blog/announcements/announcing-q2q-1_0_0</id><content type="html" xml:base="https://goq2q.net/blog/announcements/announcing-q2q-1_0_0">&lt;p&gt;Lights… Places… GO Cue 1!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/2024-06-29-announcing-q2q-1_0_0/PROMOTIONAL_Q2Q_LAUNCH_1.0_SREENSHOT.jpg&quot; alt=&quot;Announcing Q2Q Screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We are beyond excited to announce the official launch of Q2Q™, a next
generation sound cueing software for live performance for both macOS and
Windows. As we state on our website, “Q2Q™ is the tool for creating
professional sound designs from Windows or MacOS devices!…Enjoy the
flexibility of designing wherever and whenever you want” …but really this
launch means so much more than that. For us, this represents a move in the 
theatre industry towards making art more accessible. This is the first 
professional program available for both Mac and PC. Q2Q 1.0 is available now.
With a purchase of Version 1, all future minor version updates (Q2Q 1.x) 
will be available free of charge. Subsequent major versions (i.e. Q2Q 2.0) will
be available at a reduced upgrade price.&lt;/p&gt;

&lt;p&gt;With the goal of availability in mind, we have priced this product with our 
users in mind. We offer a variety of discounts. The full Version 1 product 
is available for a $249 “buy it now” price. Coming soon, we will be 
implementing a “rent-to-own” price which includes a daily fee for the 
periods of time the product is being used. And all payments made while 
renting will count towards the full purchase price. Once that amount is 
reached, the product license will be owned in full by the user.&lt;/p&gt;

&lt;p&gt;With this being the first official release of Q2Q, we wanted to provide some 
resources to help users. If you are just getting started, 
&lt;a href=&quot;/tutorials/getting-started&quot;&gt;there are tutorials here&lt;/a&gt;. 
For more advanced use cases, we have created a user manual 
&lt;a href=&quot;/assets/Q2Q%201.0.0%20User%20Guide.pdf&quot;&gt;here&lt;/a&gt;. 
For other updates and to get more connected please follow our socials! We 
are &lt;a href=&quot;https://www.facebook.com/GoQ2Q&quot;&gt;GoQ2Q on Facebook&lt;/a&gt;, 
&lt;a href=&quot;https://www.instagram.com/goq2q/&quot;&gt;GoQ2Q on Instagram&lt;/a&gt;, 
&lt;a href=&quot;https://x.com/Go_Q2Q&quot;&gt;Go_Q2Q on X (Formerly known as Twitter)&lt;/a&gt;,
https://goq2q.net/blog/ for our blog, and https://goq2q.net for our website.
There is also a Reddit community at &lt;a href=&quot;https://www.reddit.com/r/goq2q/&quot;&gt;r/goq2q&lt;/a&gt;.
If you want to see more from John Wostenberg, Q2Q founder, and his team at
Wosterware, please follow along with our journey. These resources will be a
guide for users as well as a window into the behind the scenes (literally) of
the development and progress.&lt;/p&gt;

&lt;p&gt;As a program and group that is still growing and learning in an industry 
that is forever evolving, we want to continue to improve and change as the 
artists who create change. With this focus, we encourage any and all 
feedback. Please provide feedback directly to our team at 
&lt;a href=&quot;mailto:info@goq2q.net&quot;&gt;info@goq2q.net&lt;/a&gt; and on social media! We are 
dedicated to building a cross platform program that will not remain stagnant.
Feedback from you, our users, is vital to ensure the continued evolution of 
this program. We are always open for notes.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/2024-06-29-announcing-q2q-1_0_0/Q2Q_Logo.jpg&quot; alt=&quot;Q2Q Logo&quot; title=&quot;Q2Q Logo&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Thank you all for the support. We hope this program will allow for the 
creation of art that inspires.&lt;/p&gt;

&lt;p&gt;- The Q2Q Team&lt;/p&gt;</content><author><name>Tim Osborn</name></author><category term="announcements" /><summary type="html">Lights… Places… GO Cue 1!</summary></entry><entry><title type="html">Automatic cue naming</title><link href="https://goq2q.net/blog/tech/auto-cue-naming" rel="alternate" type="text/html" title="Automatic cue naming" /><published>2022-04-30T00:00:00-06:00</published><updated>2022-04-30T00:00:00-06:00</updated><id>https://goq2q.net/blog/tech/auto-cue-naming</id><content type="html" xml:base="https://goq2q.net/blog/tech/auto-cue-naming">&lt;p&gt;Starting with version 0.6.0, Q2Q will automatically name cues for you now! This was a very fun feature to implement, and there’s a lot of customization you can leverage.&lt;/p&gt;

&lt;p&gt;By default, new projects will have their cues named with a single alphabetical letter, starting at the beginning–&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt;. Adding and deleting cues will follow this rule. This is because these new cues are actually all named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$&lt;/code&gt; - which Q2Q substitutes with a letter, one greater than the previous. So when you add a new cue, it doesn’t change the other cues’ names; it just affects how Q2Q interprets the names. You can see this for yourself by opening your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.q2q&lt;/code&gt; file in a text editor and seeing that all the cues really are named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;But some people don’t want letters; they want numbers. Got you covered there, too–use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#&lt;/code&gt; for your cue names (you can set a project-default cue name in the top toolbar), and it will auto-increment starting from 0.&lt;/p&gt;

&lt;p&gt;There’s a lot more to this cue-naming grammar however; this is where it starts to get interesting. You can add multiples of a substitution symbol to get Q2Q to count with more digits: for example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$$$&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$$$&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$$$&lt;/code&gt; becomes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AAA&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BBB&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CCC&lt;/code&gt;; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;###&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;###&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;###&lt;/code&gt; becomes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;000&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;001&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;002&lt;/code&gt;. Q2Q can increment more than one at a time if you add a number to the end of the substitution, like so: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;###5&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;###5&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;###5&lt;/code&gt; becomes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;000&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;005&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;010&lt;/code&gt; (works similarly with letters). There’s still more stuff you can do–for the full spec, take a look at the &lt;em&gt;Automatic Cue Naming&lt;/em&gt; chapter in &lt;a href=&quot;/assets/user-guide-current-redirect&quot;&gt;the user guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;my-preferred-naming-strategy&quot;&gt;My preferred naming strategy&lt;/h2&gt;

&lt;p&gt;When designing a show, I prefer sounds to be named with letters, and lights with numbers, so there’s less ambiguity, letting the stage manager call out “Ready 30BD… GO!” (meaning “light cue 30 and sound cue BD”). If you use numbers for both, the SM has to specify which are which (unless you’re doing a Chicago-style one-operator-to-rule-them-all show, in which case, you have no need of this distinction).&lt;/p&gt;

&lt;p&gt;I also like my cues spaced out, so I have the flexibility to add stuff in between last-minute without causing a rename of half the show and pissing off the SM.&lt;/p&gt;

&lt;p&gt;So I would usually go with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$$5&lt;/code&gt; naming scheme (or perhaps &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$$$5&lt;/code&gt; if I really need the deep organization, like for shows with many cues per scene). I usually use the first letter correspond to the scene, and the second letter is incrementing inside the scene. Say we have &lt;em&gt;Scene 1&lt;/em&gt;, &lt;em&gt;Scene 2&lt;/em&gt;, &lt;em&gt;Scene 3&lt;/em&gt;, each with 3 sound cues inside them. I would name my cues like so:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$$5&lt;/code&gt; &lt;em&gt;-&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AA&lt;/code&gt;&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$$5&lt;/code&gt; &lt;em&gt;-&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AF&lt;/code&gt;&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$$5&lt;/code&gt; &lt;em&gt;-&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AK&lt;/code&gt;&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{BA}&lt;/code&gt; &lt;em&gt;-&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BA&lt;/code&gt;&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$$5&lt;/code&gt; &lt;em&gt;-&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BF&lt;/code&gt;&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$$5&lt;/code&gt; &lt;em&gt;-&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BK&lt;/code&gt;&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{CA}&lt;/code&gt; &lt;em&gt;-&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CA&lt;/code&gt;&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$$5&lt;/code&gt; &lt;em&gt;-&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AF&lt;/code&gt;&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$$5&lt;/code&gt; &lt;em&gt;-&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AK&lt;/code&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;During the design phase, it’s very easy to just insert cues here and there. But, after the cues have been taken down by the SM, and therefore shouldn’t change names anymore, we can start “locking down” things as we insert new cues. For example, say we need to add two cues between AF and AK without affecting the existing names. We can just give each intermediate cue its desired name &lt;em&gt;without curly braces&lt;/em&gt;, and Q2Q will ignore it when counting:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$$5&lt;/code&gt; &lt;em&gt;-&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AA&lt;/code&gt;&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$$5&lt;/code&gt; &lt;em&gt;-&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AF&lt;/code&gt;&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AH&lt;/code&gt; &lt;em&gt;-&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AH&lt;/code&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AJ&lt;/code&gt; &lt;em&gt;-&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AJ&lt;/code&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$$5&lt;/code&gt; &lt;em&gt;-&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AK&lt;/code&gt;&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{BA}&lt;/code&gt; &lt;em&gt;-&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BA&lt;/code&gt;&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$$5&lt;/code&gt; &lt;em&gt;-&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BF&lt;/code&gt;&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;…etc&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This feature also pairs well with the cuesheet CSV export, which uses cue names as displayed by Q2Q, as expected!&lt;/p&gt;

&lt;p&gt;I’m excited to see what people do with this feature. What naming scheme will YOU choose?&lt;/p&gt;</content><author><name>John Wostenberg</name></author><category term="tech" /><summary type="html">Starting with version 0.6.0, Q2Q will automatically name cues for you now! This was a very fun feature to implement, and there’s a lot of customization you can leverage.</summary></entry><entry><title type="html">I reinvented the wheel last week, and here’s why</title><link href="https://goq2q.net/blog/tech/i-reinvented-the-wheel-last-week-heres-why" rel="alternate" type="text/html" title="I reinvented the wheel last week, and here’s why" /><published>2021-10-26T00:00:00-06:00</published><updated>2021-10-26T00:00:00-06:00</updated><id>https://goq2q.net/blog/tech/i-reinvented-the-wheel-last-week-heres-why</id><content type="html" xml:base="https://goq2q.net/blog/tech/i-reinvented-the-wheel-last-week-heres-why">&lt;p&gt;Common wisdom tells you that many problems worth solving have already been solved for you (probably more than once), and that you should leverage this fact. In other words: don’t reinvent the wheel; stand on the shoulders of giants. I very often follow this rule of thumb. But last week, I broke that rule and wrote &lt;a href=&quot;https://github.com/jwosty/FSharp.Osc&quot;&gt;an F# implementation&lt;/a&gt; of the &lt;a href=&quot;http://opensoundcontrol.org/&quot;&gt;OSC (Open Sound Control) protocol&lt;/a&gt;&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/blog/2021-10-16-i-reinvented-the-wheel-last-week-heres-why/caveman-square-wheels-m.jpg&quot; alt=&quot;Caveman pushing cart with square wheels&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But first, what is OSC? Here’s a quick and incomplete overview (there’s plenty of information elsewhere on the intertubes): OSC is a simple but very flexible message format, used to allow devices or applications to talk to each other in ad-hoc and arbitrary ways. An OSC message consists of an address string, such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;/oscillators/1/frequency&quot;&lt;/code&gt;, and a list of arguments (which can be things like strings, integers, floats, etc). An OSC client sends messages to a server, which will attempt to &lt;em&gt;dispatch&lt;/em&gt; the message based on the functionality it decides to expose. OSC also has pattern matching, which allows, for example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;/oscillators/*/frequency&quot;&lt;/code&gt; to simultaneously dispatch to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;/oscillators/1/frequency&quot;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;oscillators/2/frequency&quot;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;oscillators/foobar/frequency&quot;&lt;/code&gt;, etc. It is quite powerful and flexible, and &lt;a href=&quot;http://opensoundcontrol.org/page-list.html#implementations&quot;&gt;lots of things speak OSC&lt;/a&gt;. The spec defines simple encodings for these messages&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;. It was originally intended for use in music- and audio-based applications (such as synthesizers) as a potential alternative to MIDI, but has enjoyed wider multimedia applicability in things like lighting, animatronics, and robotics (to name a few).&lt;/p&gt;

&lt;p&gt;Q2Q support for OSC (both sending a receiving) is the latest feature I’ve been working on, and it’s been a rabbit hole, but boy, has it been a fun rabbit hole. Since Q2Q is written in &lt;a href=&quot;https://fsharp.org/&quot;&gt;F#&lt;/a&gt;, I was looking for ways to send/receive OSC messages in the language. Though F# has &lt;a href=&quot;fsprojects&quot;&gt;a strong open-source community&lt;/a&gt;, it’s not too surprising that nobody has published a first-class OSC library. There are C# OSC libraries (such as &lt;a href=&quot;https://github.com/ValdemarOrn/SharpOSC&quot;&gt;SharpOsc&lt;/a&gt;), but I didn’t consider them an option because I knew I really wanted all the goodies that well-designed F#-oriented code awards you, such as &lt;a href=&quot;https://fsharpforfunandprofit.com/posts/correctness-immutability/#reasons-why-immutability-is-important&quot;&gt;immutability&lt;/a&gt;, &lt;a href=&quot;https://fsharpforfunandprofit.com/posts/designing-with-types-making-illegal-states-unrepresentable/&quot;&gt;making illegal states unrepresentable&lt;/a&gt;, and &lt;a href=&quot;https://fsharpforfunandprofit.com/posts/recipe-part2/&quot;&gt;railway-oriented programming (ROP)&lt;/a&gt;, &lt;a href=&quot;http://learnyouahaskell.com/making-our-own-types-and-typeclasses#algebraic-data-types&quot;&gt;algebraic data types&lt;/a&gt;—the list goes on. So I decided to reinvent the wheel and build an OSC library in F#. Besides, &lt;a href=&quot;https://blog.codinghorror.com/dont-reinvent-the-wheel-unless-you-plan-on-learning-more-about-wheels/&quot;&gt;this was a wheel I wanted to learn about&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The specs for &lt;a href=&quot;http://opensoundcontrol.org/spec-1_0.html&quot;&gt;OSC 1.0&lt;/a&gt; and &lt;a href=&quot;http://opensoundcontrol.org/files/2009-NIME-OSC-1.1.pdf&quot;&gt;OSC 1.1&lt;/a&gt; are pretty short as far as specs go; only a couple pages each—you could read them over lunch. This makes it pretty straightforward to just drive out the whole implementation using &lt;a href=&quot;https://martinfowler.com/bliki/TestDrivenDevelopment.html&quot;&gt;TDD (Test Drive Development)&lt;/a&gt;. The entire OSC 1.1 AST (excluding bundles and timetags, which I just didn’t get around to) consists of the following:&lt;/p&gt;

&lt;div class=&quot;language-fsharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OscAtom&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OscInt32&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;intValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;int32&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OscFloat32&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;floatValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;float32&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OscString&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stringValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OscBlob&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blobData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OscBool&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OscNone&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OscImpulse&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OscMessage&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;addressPattern&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OscAtom&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Boom, &lt;a href=&quot;https://fsharpforfunandprofit.com/ddd/&quot;&gt;Domain Designed&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Next step: Implementing each of the encoding/decoding functions for the atoms and messages, according to the spec. After that: the message dispatching / matching logic (which F# &lt;a href=&quot;https://github.com/jwosty/FSharp.Osc/blob/master/src/FSharp.Osc.fs#L253&quot;&gt;is particularly good at&lt;/a&gt;). Then, finally: the code that fires these messages over the wire (I wrote both UDP and TCP clients), as well as the code that can listen for OSC messages coming in from the network (UDP only for now). The whole thing is just shy of 600 lines of code. Sure, I could have spent that time doing other Q2Q features. But in this case, the advantages outweigh the time spent: this implementation will fit right in with the rest of Q2Q’s idiomatic F# codebase, and I know for a fact that it is correct to the spec&lt;sup id=&quot;fnref:3&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; since it has &lt;a href=&quot;https://github.com/jwosty/FSharp.Osc/blob/master/Tests/Program.fs&quot;&gt;complete test coverage&lt;/a&gt;. There’s almost 2x the lines of test code than actual code. Sometimes that could be a code smell, but I would argue that it’s not when you’re implementing a protocol from its spec.&lt;/p&gt;

&lt;p&gt;Do I always recommend reinventing the wheel? Definitely not. Would I recommend trying it from time to time? Absolutely.&lt;/p&gt;

&lt;p&gt;Coming soon to theaters near you: OSC support in Q2Q 0.5.0! In the mean time, for geeks like me, you can &lt;a href=&quot;https://github.com/jwosty/FSharp.Osc&quot;&gt;browse the source code&lt;/a&gt;, or &lt;a href=&quot;https://www.nuget.org/packages/FSharp.Osc/&quot;&gt;use the NuGet package&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;em&gt;Um, ackshually, it’s a content format, not a protocol…&lt;/em&gt; Yes, I know, dear reader, and they spell this out in &lt;a href=&quot;http://opensoundcontrol.org/files/2009-NIME-OSC-1.1.pdf&quot;&gt;the OSC 1.1 spec&lt;/a&gt;. I called it that for sake of simplicity. Calling it a “content format” may be more correct, but I think “protocol” gives a better connotation in that opening paragraph. You may notice that I more correctly use the “content format” description later on. Whatever, go ahead and crucify me for that if you’re feeling particularly pedantic today. I’ll still love you. You—yes, you, beloved reader. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;There’s also bundles, which I don’t get into this post. You can read more about those elsewhere. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Well, as long as the messages coming in are well-formed. It doesn’t have particularly good handling of error cases for now, but I can always circle back later. The client also doesn’t do a particularly good job of sanitizing the messages before they’re sent (i.e. preventing you from using an address without a slash at the beginning). &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>John Wostenberg</name></author><category term="tech" /><summary type="html">Common wisdom tells you that many problems worth solving have already been solved for you (probably more than once), and that you should leverage this fact. In other words: don’t reinvent the wheel; stand on the shoulders of giants. I very often follow this rule of thumb. But last week, I broke that rule and wrote an F# implementation of the OSC (Open Sound Control) protocol1. Um, ackshually, it’s a content format, not a protocol… Yes, I know, dear reader, and they spell this out in [the OSC 1.1 spec][osc-1.1]. I called it that for sake of simplicity. Calling it a “content format” may be more correct, but I think “protocol” gives a better connotation in that opening paragraph. You may notice that I more correctly use the “content format” description later on. Whatever, go ahead and crucify me for that if you’re feeling particularly pedantic today. I’ll still love you. You—yes, you, beloved reader. &amp;#8617;</summary></entry><entry><title type="html">Using ASCII waveforms to test real-time audio code</title><link href="https://goq2q.net/blog/tech/using-ascii-waveforms-to-test-real-time-audio-code" rel="alternate" type="text/html" title="Using ASCII waveforms to test real-time audio code" /><published>2021-10-12T00:00:00-06:00</published><updated>2021-10-13T00:00:00-06:00</updated><id>https://goq2q.net/blog/tech/using-ascii-waveforms-to-test-real-time-audio-code</id><content type="html" xml:base="https://goq2q.net/blog/tech/using-ascii-waveforms-to-test-real-time-audio-code">&lt;p&gt;I draw sound wave ASCII art in Q2Q’s source code. These ASCII art waveforms ensure that the real-time audio engine at the heart of Q2Q stays bug-free.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;Software development best-practices dictate that if you want your software to be high-quality (who doesn’t?), you test your code, you &lt;a href=&quot;https://martinfowler.com/bliki/SelfTestingCode.html&quot;&gt;test it automatically&lt;/a&gt;, and you test it often (as part of a &lt;a href=&quot;https://martinfowler.com/articles/continuousIntegration.html&quot;&gt;continuous integration&lt;/a&gt; build process). In other words, you should make accidentally publishing bugs as difficult as possible. Q2Q, of course, has test suites to prevent regressions, and a CI system that makes sure all tests pass (just like any other good software project).&lt;/p&gt;

&lt;p&gt;Q2Q is a &lt;a href=&quot;http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing&quot;&gt;real-time audo&lt;/a&gt; application. It does things like starting/stopping sounds, fading/panning sounds, and looping/devamping sounds, and these kinds of features are mission-critical. They cannot fail or have bugs. Here’s the catch: audio programming is extremely tricky to get right. When I was first writing Q2Q, I spent days trying to get anything coherent out of the speakers &lt;em&gt;even at all&lt;/em&gt;. It’s very easy to get some buffer index wrong, or do a time conversion incorrectly, or forget to handle more than just mono and stereo signals, or even to just be off by a small number of samples without noticing.&lt;/p&gt;

&lt;p&gt;This is hard to write automated tests for – how are you supposed to write a test that asserts, “a one-second fade-out should work”? My initial thought was to take a sliding average (approximating loudness), and assert that it constantly decreases, for example. But then how would you test looping functionality? Or mixing different sounds together? Or crossfading? Trying to come up with how to test those invariants is very hard.&lt;/p&gt;

&lt;p&gt;I stumbled across &lt;a href=&quot;https://blog.janestreet.com/using-ascii-waveforms-to-test-hardware-designs/&quot;&gt;a post from Jane Street’s tech blog&lt;/a&gt; that describes a technique for testing hardware oscillators (which generate waveforms), where they render the oscillator’s output to text, which can be very easilly diffed. Additionally, the F# compiler &lt;a href=&quot;https://github.com/dotnet/fsharp/tree/dbf9a625d3188184ecb787a536ddb85a4ea7a587/tests/fsharp/typecheck/sigs&quot;&gt;has some tests&lt;/a&gt; (which it calls “baseline tests”) that test the new compiler’s output against output from an earlier known-good version of itself. A combination of these two approaches sounded very good to me.&lt;/p&gt;

&lt;p&gt;Q2Q employs this ASCII-waveform baseline testing technique to great success. Every time I work on a new feature, I manually create some test cases for the new feature. For example, if I were writing the component that performs fading and panning, I would first create a test case to exercise a simple fade (perhaps a short fade-out), and output to a simple array, instead of an actual output device (this is pretty easy as I use &lt;a href=&quot;https://github.com/naudio/NAudio&quot;&gt;NAudio&lt;/a&gt; for audio processing). Then, I would attempt to implement the feature. Then, I would use my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ASCIIWaveformRenderer&lt;/code&gt; to turn the output into an array of strings, acting as a visual representation of the data that would have been outputted to the audio device. I would run this in &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/fsharp/tools/fsharp-interactive/&quot;&gt;F# interactive&lt;/a&gt;, so that if it looks good, I can copy-paste the result right back into the test code as the baseline to compare against from now on. If it didn’t look right, I would tweak the code until it does. Rinse and repeat for all the corner cases.&lt;/p&gt;

&lt;figure&gt;
  
&lt;a href=&quot;/assets/images/blog/2021-10-06-using-ascii-waveforms-to-test-real-time-audio-code/simple-fade-in-baseline.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2021-10-06-using-ascii-waveforms-to-test-real-time-audio-code/simple-fade-in-baseline.png&quot; alt=&quot;Foo&quot; /&gt;&lt;/a&gt;

  &lt;figcaption&gt;
Fade-in baseline test using an ASCII waveform.
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  
&lt;a href=&quot;/assets/images/blog/2021-10-06-using-ascii-waveforms-to-test-real-time-audio-code/simple-looping-baseline.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2021-10-06-using-ascii-waveforms-to-test-real-time-audio-code/simple-looping-baseline.png&quot; alt=&quot;Foo&quot; /&gt;&lt;/a&gt;

  &lt;figcaption&gt;
A baseline test for a looping using slices. Notice how the slicer first plays straight through a signal, then loops the last part of the waveform.
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This provides a great regression testing experience. Recently, while implementing &lt;a href=&quot;https://en.wikipedia.org/wiki/Pan_law&quot;&gt;pan laws&lt;/a&gt;, I noticed that the CI had started failing. I opened up the error log and was greeted with this:&lt;/p&gt;

&lt;figure&gt;
  
&lt;a href=&quot;/assets/images/blog/2021-10-06-using-ascii-waveforms-to-test-real-time-audio-code/fade-provider-baseline-failure.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2021-10-06-using-ascii-waveforms-to-test-real-time-audio-code/fade-provider-baseline-failure.png&quot; alt=&quot;Foo&quot; /&gt;&lt;/a&gt;

  &lt;figcaption&gt;
ASCII waveform baseline test failure.
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This is telling us that a test is failing because the system’s output did not match the expected ASCII waveform baseline.&lt;/p&gt;

&lt;p&gt;The tests for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FadeSampleProvider&lt;/code&gt; (the component that handles fading and panning), were catching a legitimate mistake. As you can see, storing the baseline as an ASCII waveform makes this much easier to debug than if it were to be an opaque array of raw sample data. &lt;a href=&quot;https://github.com/haf/expecto&quot;&gt;Expecto&lt;/a&gt;, the test framework I use, even gives a good enough visual diff there. You can kind of tell what is going wrong just by the picture – it’s supposed to be a fade out, as indicated by the first (green) waveform (so gradually decreasing in overall volume), and it kind of does this at first, but then the signal starts fading back in! Why was this happening? After some digging, it turns out that I had accidentally removed the clamping logic in the interpolation functions, thinking they were do-nothing code. These interpolations are simply mathematical functions&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, so you can put in numbers &lt;em&gt;you&lt;/em&gt; would consider invalid, and they will happily start doing funny things – like causing a fade-out to start fading back in. Oops!&lt;/p&gt;

&lt;p&gt;The interesting thing is that if these tests weren’t around, this bug probably would have gone unidentified, causing subtle glitches, for quite a while. Since the audio is streamed in real time, it is not processed all at once; rather, it is processed in buffered chunks. When the audio device is ready for more sound to play, it gives Q2Q an empty buffer, which Q2Q then fills with samples from the processing chain. This happens many time per second (as determined by the size of the buffer). &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FadeSampleProvider&lt;/code&gt; only calls the interpolation function when a fade is actually in progress – but it only re-decides this on the next buffer. Therefore, when a fade ends before the end of the buffer (which it likely would), it would exhibit this problem for the rest of that audio buffer. Buffer sizes are small enough (as measure in seconds) that it likely would have sounded like a millisecond-long click or pop, which would have been extremely difficult to attribute to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FadeSampleProvider&lt;/code&gt; in particular. I would have probably chalked it up to slow file reading, or inefficient code causing the device to drop some frames.&lt;/p&gt;

&lt;p&gt;ASCII-waveform baseline tests are great, but what about the cases I don’t know to write tests for? Can we take this even further? Check back for a future blog post – &lt;a href=&quot;/blog&quot;&gt;Property-based fuzz testing&lt;/a&gt; to the rescue!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;EDIT: since people were interested, I’ve posted the waveform-to-ASCII renderer as a snippet free to use: &lt;a href=&quot;http://www.fssnip.net/85g&quot;&gt;http://www.fssnip.net/85g&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;!-- TODO: write something about pan laws then link to it --&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;em&gt;I use different kinds of interpolation functions to implement the different fade shapes you can use, such as linear fades, constant-power fades, and compromise fades. &lt;a href=&quot;https://www.wolframalpha.com/input/?i=plot+%28sin%280.5x*pi%29%29%2C+%28x%29%2C+%280.5%28x%2Bsin%280.5x*pi%29%29%29+from+x%3D0+to+1&quot;&gt;Here’s a sample graph&lt;/a&gt; of some of these functions plotted together, and &lt;a href=&quot;https://www.wolframalpha.com/input/?i=plot+%28sin%280.5x*pi%29%29%2C+%28x%29%2C+%280.5%28x%2Bsin%280.5x*pi%29%29%29&quot;&gt;here’s that same graph&lt;/a&gt; without restricting the x axis to a specific range. Observe that the output makes sense as a fade volume for x values from 0 to 1, but start to get weird outside of that range. Hence, we need to clamp the x value between 0 and 1 before we pass it into the interpolation formula.&lt;/em&gt; &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>John Wostenberg</name></author><category term="tech" /><summary type="html">I draw sound wave ASCII art in Q2Q’s source code. These ASCII art waveforms ensure that the real-time audio engine at the heart of Q2Q stays bug-free.</summary></entry></feed>