tag:blogger.com,1999:blog-40767388081982793812018-10-31T23:40:19.514-07:00Ornament and Crime: ...But Mostly CrimeReflections on one developer's experiences with navigating the ins and outs of creating new apps for the Ornament and Crime Eurorack module. And perhaps some helpful tutorials along the way.JasonJustiannoreply@blogger.comBlogger18125tag:blogger.com,1999:blog-4076738808198279381.post-60705464937947682642018-09-23T14:34:00.000-07:002018-09-23T16:35:42.769-07:00Pitch Calculation and Output<blockquote class="tr_bq"><i>"If pitches were horses, we'd all be eatin' steak." --Jayne Cobb</i></blockquote>And where have I been for almost two months? I was busy. For one thing, I was busy at my actual job where they pay me actual money to write actual code. And when I wasn't doing that, I was working on the last couple releases of Hemisphere Suite.<br /><br />For each release of Hemisphere Suite, I like to focus on a theme that explores some aspect of electronic music. 1.3 was the MIDI release, 1.5 (coming Sept. 28) will be the Shift Register release, 1.6 (coming Oct. 26) will be the <span style="background-color: black;"><i>&lt;no spoilers!&gt;</i></span> release. 1.4 was the scales release. It had several projects that required me to really learn how O_C handles pitch output through its DAC functions. For anybody who wants to do Ornament and Crime development, this is an important topic. I mean, I did okay without this knowledge for a while; but then, when I figured some things out, I had to go back and redo some of my work, to make pitch output more accurate. So best to pass that info along.<br /><br /><h3>The DAC Function</h3><br />Way back in June, I talked a bit about how the ADC class had several different functions for getting ADC input. Well, it's sort of the same with the DAC. If you take a look at OC_DAC.cpp and OC_DAC.h, there are a few functions to choose from.<br /><br />For my work, I've settled on one function to use for everything (see OC_DAC.h):<br /><br /><span style="font-size: x-small;"><i><span style="font-family: &quot;verdana&quot; , sans-serif;">static void set_pitch(DAC_CHANNEL channel, int32_t pitch, int32_t octave_offset)</span></i></span><br /><br />I'm sure the others have their uses. Actually, I'm not sure about that at all. I never met a job that set_pitch() couldn't do. Anyway, within your code, a call looks like this:<br /><span style="font-size: small;"><br /></span><span style="font-family: &quot;verdana&quot; , sans-serif; font-size: x-small;"><i>OC::DAC::set_pitch(channel, pitch, octave);</i></span><br /><br />The parameters are as follows:<br /><ul><li><i>channel</i> is an enum with the values <i>DAC_CHANNEL_A</i> through <i>DAC_CHANNEL_D</i>. It specifies the O_C output whose CV output is being changed.</li><li><i>pitch</i> is a value that corresponds to voltage. Exactly how it corresponds to voltage is something we'll talk about under the next header.</li><li><i>octave_offset</i> raises and lowers the octave by this many octaves. It's also basically a voltage offset.</li></ul>I've got a couple notes on set_pitch() for you:<br /><br />It can only be called outside of the menu function. If you're drawing the screen, you can't access the I/O, and vice versa. Doing either type of operation in the wrong place will crash the module.<br /><br />The channel must have the type DAC_CHANNEL. If you want to use an integer (0-3) to access a specific output, you'll need to use a cast. I've just used C-type casts for this purpose:<br /><br /><span style="font-size: x-small;"><i><span style="font-family: &quot;verdana&quot; , sans-serif;">int ch = 1; // Channel B </span></i></span><br /><span style="font-size: x-small;"><i><span style="font-family: &quot;verdana&quot; , sans-serif;">DAC_CHANNEL channel = (DAC_CHANNEL)(ch);</span></i></span><br /><span style="font-size: x-small;"><i><span style="font-family: &quot;verdana&quot; , sans-serif;">OC::DAC::set_pitch(channel, value, octave);</span></i></span><br /><br />Also, note that when set_pitch() sets the pitch, the output stays at that level until it's changed. You don't need to keep refreshing the output during every ISR cycle.<br /><br /><h3>What is "pitch" for OC::DAC::set_pitch()? </h3><br /><i>Pitch</i> is a bipolar value, where 0 means 0 volts. Negative numbers represent negative voltages, and positive numbers represent positive voltages.<br /><br />One octave is equal to <b>12 &lt;&lt; 7</b>. That's 12 semitones times 128 (or, 2^7), or 1536. You can also read that as "one volt is equal to 12 &lt;&lt; 7."<br /><br />One semitone is equal to <b>128</b>. 128 is therefore 1/12 of a volt (or about .0833V). This means that each increment of <i>pitch</i> is 1/128 semitones (or, .78125 of a cent) or 1/1536 of a volt (or, about .00065V).<br /><br />The output range of O_C is said to be between -3V and 6V. This means that <i>pitch</i> has a practical range of -4608 ~ 9216.<br /><br />Given a CV value (perhaps from an ADC, or sequencer, etc.), you can get octaves and semitones like this:<br /><span style="font-size: x-small;"><br /></span><span style="font-size: x-small;"><i><span style="font-family: &quot;verdana&quot; , sans-serif;">int cv = &lt;some CV source&gt;;</span></i></span><br /><span style="font-size: x-small;"><i><span style="font-family: &quot;verdana&quot; , sans-serif;">int octave = cv / 1536;</span></i></span><br /><span style="font-size: x-small;"><i><span style="font-family: &quot;verdana&quot; , sans-serif;">int semitone = (cv % 1536) / 128;</span></i></span><br /><br />You can likewise convert a CV value to voltage like this (<i>note that the O_C's M4 doesn't have a hardware floating point unit, so do whatever conversions you need to do</i>):<br /><br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">int cv = &lt;some CV source&gt;;</span></span></i><br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">float volts = cv / (12 &lt;&lt; 7);</span></span></i><br /><br />I know that's a lot of stuff to remember. The good news is, you don't need to remember it. Everything I just told you about the value of <i>pitch</i> can be derived from a single piece of information:<br /><br /><div style="text-align: center;"><span style="font-family: &quot;arial&quot; , &quot;helvetica&quot; , sans-serif;"><span style="font-size: x-large;">one volt = 12 &lt;&lt; 7</span></span></div>JasonJustiannoreply@blogger.com0tag:blogger.com,1999:blog-4076738808198279381.post-30629315194656245722018-08-02T16:24:00.002-07:002018-08-05T18:43:32.000-07:00MIDI Part One: MIDI OutIt's easy to forget that the USB jack on the back of an Ornament and Crime can be used for things other than updating firmware. Teensy 3.2 has a capable, if poorly-documented, MIDI library. With the right software, O_C can be a modular system's link to the outside world.<br /><br />I'm going to walk through sending MIDI messages in this post. In the next post, I'm going to introduce Captain MIDI, a full-scale modular MIDI interface in the classic Ornament and Crime style.<br /><br /><h3>Enabling MIDI</h3><br />MIDI support is enabled in Arduino IDE as one of the compile options. From the Tools menu, go down to "USB Type" and choose "MIDI" instead of the usual "No USB."<br /><br /><i>Note #1: MIDI support adds about 3% to the size of the compiled binary, so if you're working with a normal O_C codebase, MIDI support might take you over the limit.</i><br /><br /><i>Note #2: I haven't tested the default O_C apps with MIDI support enabled. I think they'll continue to work as normal, but I haven't tried it.</i><br /><br />Note #3: Arduino IDE remembers your last settings. This is usually a good thing, but it means that you'll need to keep track of whether you want MIDI support enabled or not if you have multiple codebases.<br /><br /><i>Note #4: When I compile with MIDI, I don't have to push the Program button on the Teensy; programming starts automatically as soon as the build is done. I don't know why it works this way, but I suspect that it's normal USB behavior that is suppressed when compilation is done with "No USB."</i><br /><br /><h3>The Teensy USB MIDI Library</h3><br />See the Teensyduino USB MIDI documentation here:<br /><br /><a href="https://www.pjrc.com/teensy/td_midi.html">https://www.pjrc.com/teensy/td_midi.html</a><br /><br />The information on that page can be characterized as <i>somewhat accurate</i>. Hopefully, I can save you from some of the frustration that I experienced when I counted on it being right. Still, it's a decent starting point, and it's worth knowing.<br /><br />If you go through the library code that's part of Teensyduino, there's more than one MIDI library, so I suspect that the documentation just wasn't updated. At very least, it's not specific to the Teensy version that we care about, 3.2.<br /><br />But the documentation's gaps won't vex us too much for Midi out. It'll become more relevant when we look at MIDI in. <br /><br /><h3>Basic MIDI Out</h3><br />I'm starting with MIDI out because it's a bit easier than MIDI in. Don't worry, MIDI in is still pretty easy. But with MIDI out, you're just using the documented methods to send messages.<br /><br />Every MIDI operation (out and in) uses the global object <b>usbMIDI</b>. For example, to send a Note On message, you do<br /><br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">usbMIDI.sendNoteOn(midi_note, velocity, channel);</span></span></i><br /><br />midi_note is an integer from 0-127, velocity is an integer from 0-127, and channel is an integer from 1 to 16.<b> Take note that MIDI channels in this library are 1-indexed.</b><br /><br />The MIDI library puts each message in a buffer, and holds onto it until 16 messages have been buffered, or until 1ms (or so) has elapsed, whichever comes first. If you want to send buffered messages earlier than that, you can call<br /><br /><i>usbMIDI.send_now();</i><br /><br />Note Off is similar to Note On, including velocity:<br /><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">usbMIDI.sendNoteOff(midi_note, 0, channel);</span></span></i><br /><br />MIDI control change looks like this:<br /><br /><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;"><i>usbMIDI.sendControlChange(controller_number, value, channel);</i></span></span><br /><br />Note that <i>value</i> has a seven-bit range from 0-127, per the MIDI specification.<br /><br /><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">usbMIDI.sendAfterTouch(value, channel);</span></span><br /><br />This is channel aftertouch, and the value here is also from 0-127.<br /><br />And pitch bend:<br /><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">usbMIDI.sendPitchBend(bend, channel);</span></span></i><br /><br />The bend range here is from 0-16383. Values from 0 to 8191 are negative bend, 8192 is 0 bend, and values from 8193 to 16383 are positive. For logging purposes, I'd just display (<i>bend - 8192</i>).<br /><br /><h3>Other MIDI Out</h3><br />I've been working with system exclusive messages, and I'll probably deal with that separately if anyone is interested. I haven't really figured out clock yet. The documentation says this:<br /><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">usbMIDI.sendRealTime(usbMIDI.Clock);</span></span></i><br /><br />but there is no usbMIDI.Clock, at least for Teensy 3.2, and there doesn't seem to be specific support for clock in the library code. So this is sort of on my "pending" list. The library lets us construct MIDI messages a byte at a time, and that might be necessary if we want clock.JasonJustiannoreply@blogger.com4tag:blogger.com,1999:blog-4076738808198279381.post-63002920542850778812018-07-25T14:21:00.001-07:002018-07-25T14:25:35.714-07:00Spoiler Alert: A Hemisphere Suite 1.2 PreviewHemisphere Suite 1.2 will be released on Friday, July 27th. My plan is to add new applets fairly regularly through the end of the summer; but Friday's release goes a little beyond that, with some fairly big changes to the framework itself. So I wanted to introduce those changes a little before the release.<br /><br />As always, the hex file will be available here: <a href="https://github.com/Chysn/O_C-HemisphereSuite/releases">https://github.com/Chysn/O_C-HemisphereSuite/releases</a>.<br /><br /><h3>The Help Screens Are Moving</h3><br />I didn't like the way the Help Screens worked. You had to long-press the left encoder, then long-press it again to go to the next hemisphere, then long-press it again to get back to the main screen. All this long-pressing was inelegant.<br /><br />I've introduced a double-click action to the Up and Down buttons. It's pretty much like double-clicking a mouse button, expecting about a half second between the first press and the second press. The Help Screens are now activated with a double-click of the corresponding button (Up for left hemisphere and Down for right). Once you're at a Help Screen, you can simply click same button again to get out of Help, or click the opposite button to see the other Help Screen. You can switch back and forth this way as many times as you like.<br /><br />The Help Screens still have no effect on an applet's operation, which was an important consideration for me. Applets still function, and you can still control them in the blind.<br /><br /><a href="https://www.youtube.com/watch?v=3I734jXkrvA" target="_blank">Help Screen Video (YouTube)</a><br /><br /><h3>Clock Forwarding Is Moving</h3><br />I wanted the Up/Down buttons to centralize operations having to do with applet management, so I moved the Clock Forwarding control to the left encoder, where the Help Screen control used to be. In all other respects, it's exactly the same.<br /><br /><a href="https://www.youtube.com/watch?v=WgqHVCxjSXc">Clock Forwarding Video (YouTube)</a> <br /><br /><h3>Category Filtering Is Coming</h3><br />At about 30 applets, it started becoming kind of annoying to flip through them all. So Hemisphere Suite 1.2 introduces a category filtering system, which allows you to restrict the types of applets you see.<br /><br />To enter the Categories Screen, first activate Selection Mode by clicking on the Up or Down button. Then, long-press the Down button. When you release it, the Categories Screen will be displayed in the selected hemisphere. Use the corresponding encoder to choose a category (they are, off the top of my head, Modulator, Sequencer, Quantizer, Clocking, Utility, MIDI, Audio, and Other) or ALL. Then press the corresponding Up or Down button again.<br /><br />Now, when you scroll through available applets in that hemisphere, you'll see only applets assigned to the chosen category. Each hemisphere can have a different category selected. These categories are not remembered when state is saved.<br /><br /><a href="https://www.youtube.com/watch?v=6545D9zRVIQ">Category Filtering Video (YouTube)</a><br /><br />I hope you all enjoy the new features! The dust should be settling on the interface after this. Hemisphere 1.2 has some new applets, too, including a Burst Generator and Comparator, JasonJustiannoreply@blogger.com0tag:blogger.com,1999:blog-4076738808198279381.post-54776797615793052392018-07-20T15:04:00.001-07:002018-07-20T15:07:09.730-07:00Hemisphere Suite 1.0I'm happy to announce the release of Hemisphere Suite 1.0. It's available as a hex file at<br /><br /><a href="https://github.com/Chysn/O_C-HemisphereSuite/releases">https://github.com/Chysn/O_C-HemisphereSuite/releases</a><br /><br />I'm consolidating everything to GitHub, so the documentation is also there, with detailed information about all 24 of Hemisphere Suite's applets, and The Darkest Timeline sequencer app. And Pong. I had to throw in Pong.<br /><br /><a href="https://github.com/Chysn/O_C-HemisphereSuite/wiki">https://github.com/Chysn/O_C-HemisphereSuite/wiki</a><br /><br />I spoke before of releasing Hemisphere Suite with a limited number of original O_C apps, but I decided to call an audible on that idea and remove them all. So Hemisphere Suite is sort of aimed at people with multiple O_Cs.JasonJustiannoreply@blogger.com2tag:blogger.com,1999:blog-4076738808198279381.post-24739900428128559662018-07-12T19:58:00.001-07:002018-07-12T19:58:23.178-07:00A Small Hardware Project: The O_C Programming PanelFor the first time since I bought my Ornament and Crime, it is fully-racked in my case with four screws. It will no longer spend most of its time propped up by a USB cable at an awkward 45-degree angle.<br /><br />I cut into my case this evening and added an Ornament and Crime programming panel. It consists of a Switchcraft USB jack, a momentary push button, and a short USB cable for inside the case. Here's the panel in progress:<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-EvwV7ZYPC84/W0gRR1kRWgI/AAAAAAAAPKg/WaZC8mDfhRIJVUFZBg_BN5t81VYupHO2wCLcBGAs/s1600/IMG_2395.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="480" data-original-width="640" height="240" src="https://1.bp.blogspot.com/-EvwV7ZYPC84/W0gRR1kRWgI/AAAAAAAAPKg/WaZC8mDfhRIJVUFZBg_BN5t81VYupHO2wCLcBGAs/s320/IMG_2395.JPG" width="320" /></a></div>The Switchcraft jack has a USB Type B jack on one side, and a Type A jack on the other side. So all I had to do was connect a six-inch USB cable into my O_C's Teensy. To maximize its life span in there, I cracked open a brand new Teensy.<br /><br />The button is to put the Teensy into programming mode, and it just connects to Program and Ground <br />terminals near the Teensy's on-board button. Here's the O_C hooked up:<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-0S4zKOVA8zA/W0gTuyNFJTI/AAAAAAAAPKs/NguTpJxn_pACcnHJIAZPy4xWA6xK15lHgCLcBGAs/s1600/IMG_2396.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="480" data-original-width="640" height="240" src="https://1.bp.blogspot.com/-0S4zKOVA8zA/W0gTuyNFJTI/AAAAAAAAPKs/NguTpJxn_pACcnHJIAZPy4xWA6xK15lHgCLcBGAs/s320/IMG_2396.JPG" width="320" /></a></div>I had to swap the positions of my O_C and my Varigate 4+, because the internal USB cable takes a lot of space to the left of the module, and it can no longer go up against the edge of the case. Fortunately, Varigate 4+ is really slim, and the cable has plenty of clearance below the Varigate 4+.<br /><br />I'd prefer to have the O_C on the left edge, but I'll learn to like it this way. I'll keep my eyes open for a short right-angle USB cable. I've seen them right-angle, and I've seen them short, but....<br /><br />Anyway, here's what the programming panel looks like from the back, once everything was back together:<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://2.bp.blogspot.com/-dYv0VIULl_8/W0gUpXEGR-I/AAAAAAAAPK4/cojo4ETrE-cp-iuX-aT781vMVzRJ9gh0gCLcBGAs/s1600/IMG_2400.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="480" data-original-width="640" height="240" src="https://2.bp.blogspot.com/-dYv0VIULl_8/W0gUpXEGR-I/AAAAAAAAPK4/cojo4ETrE-cp-iuX-aT781vMVzRJ9gh0gCLcBGAs/s320/IMG_2400.JPG" width="320" /></a></div>This simple thing is going to save me lots of time that I've been spending hooking up the stupid USB cable and fishing around for the stupid tiny little button.JasonJustiannoreply@blogger.com0tag:blogger.com,1999:blog-4076738808198279381.post-10943063612302733972018-07-11T09:21:00.002-07:002018-07-11T09:23:20.054-07:00Checking In, and a Slight Course CorrectionI've been busy with the Hemisphere beta releases, and really busy at my actual day job, and haven't had time for the blog. Sorry about that. So for those readers among you out there, I wanted to quickly bring you up to speed on upcoming projects.<br /><br /><h3>This Blog</h3><br />This blog was originally intended to be a journal about how to develop O_C apps. But I think I'm pretty much at the end of the line there. First of all, it doesn't seem like that's something that other people are really that interested in. I knew it would be a small audience, with the hobby of developing applications for a specific eurorack module buried under several nested layers of "niche." But second, I've pretty much covered the topic. Anybody that wants to write an app for O_C can get a pretty good start with the information here, and I don't have that much more to say about it.<br /><br />On the other hand, the topic of the software that I'm producing is up one or two levels of "niche," and there's a relatively strong level of interest in it.<br /><br />So, this blog will shift a bit toward my own O_C projects, of which there are many. I'll discuss code where it's appropriate or (mildly) interesting. And I might wind up doing more development tutorials if there are any requests, or I find a topic I've neglected.<br /><br /><h3>Hemisphere</h3><br />The first version of Hemisphere is in what I hope is its final beta release. If no more bugs are found, it'll be released with a version number on July 16, 2018. After this, I plan to merge any new O_C operating system updates into the Hemisphere release.<br /><br />This release keeps Ornament and Crime apps, with the exception of Meta-Q. So it's basically O_C + Hemisphere and 13 applets. This configuration takes up <i>all</i> of the O_C's program storage, so the plan is to go into a stable release that won't change much over time.<br /><br /><h3>Hemisphere Suite</h3><br />This is my current project. For the multi-O_C crowd, this removes the O_C apps to make room for many applets and new full apps. This will be rapidly-changing system and will contain everything that I do. There will be some weird stuff in there, but hopefully pretty interesting.<br /><br />At the moment, it includes a slew processor, a gated VCA, a port of an accent sequencer I wrote for Peaks called <i>Palimpsest</i>, a playable implementation of John Conway's Game of Life, and a dual Euclidean drummer called <i>Annular Fusion</i>. I'm also planning to update the Hemisphere framework with a master clock forwarding mode, so that both hemispheres can share a single clock source.<br /><br /><h3>Documentation</h3><br />This is important, and I can't neglect it. I have to document the applets' functions and keep the documentation somewhere. Right now, information is kind of fragmented between this blog, GitHub, MuffWiggler, and my website. I need to consolidate. So that's a thing, too.<br /><br /><h3>Neural Network</h3><br />The neural network full O_C app is also in active development, although right now it largely involves research. So more to come on this as it comes together.<br /><br /><h3>Messing With My Case</h3><br />I got this awesome Switchcraft flush-mount USB jack. When I get some time, I'm going tear my system down and install the jack and a button, so that I can program my O_C without it hanging out of the case. I'll post photos, at least!JasonJustiannoreply@blogger.com6tag:blogger.com,1999:blog-4076738808198279381.post-37926900590738266132018-07-03T11:24:00.001-07:002018-07-03T20:33:55.478-07:00Hemisphere Beta Release PFAQPFAQ? Potentially Frequently-Asked Questions, because I haven't been asked any questions yet. But I'm looking for synthesists who are interested in trying alternate firmware for Ornament and Crime, and providing bug reports and feedback.<br /><br /><h3>When is the release?</h3><br />July 4, 2018, at <a href="http://www.beigemaze.com/">www.beigemaze.com</a><br /><br /><h3>How will it be released?</h3><br />A Teensy hex file is available at <a href="http://www.beigemaze.com/hemisphere.html">http://www.beigemaze.com/hemisphere.html</a>.<br /><br />You can install with the instructions at <a href="http://ornament-and-cri.me/firmware/#method_a">http://ornament-and-cri.me/firmware/#method_a</a>.<br /><br /><i><b>This is a beta release</b></i>. I'm looking for people who'd like to try Hemisphere and provide feedback. You should be comfortable installing firmware, and optionally reverting back to the previous version if you want to return your O_C to its factory state. It's not a difficult process, but unfortunately I can't hold your hand very much.<br /><br /><h3>Will I lose any data saved in my O_C?</h3><br />Yeah, it's a pretty safe bet that you'll lose everything saved in your O_C. This isn't something you want to do just before a show.<br /><br /><h3>Will I lose any apps?</h3><br />The native Ornament and Crime firmware pretty much takes all of the Teensy's memory. So I had to free up about 20K by disabling <b>Meta-Q </b>in this release.<br /><br /><h3>Do I have to be part of the beta, or can I just download Hemisphere and install it?</h3><br />When the hex file is released, you can just download Hemisphere and install it without participating in the beta. But it's all going to be pretty informal anyway. I won't be asking beta testers to follow any particular procedures or use bug tracking software. The best place to talk about the beta will probably be right on this here blog page.<br /><h3>Is it finished?</h3><br />There's always room for improvement, but there are 14 applets available now. These are:<br /><ol><li>Dual Quantizer - Selectable scales, continuous or clocked operation</li><li>Dual Clock Divider/Multiplier - Two divider/multipliers share a single clock, from 1/8 to 8/1</li><li>5-Step Sequencer - 2 1/2 octaves, quantized to semitones</li><li>Dual 8-Step Trigger Sequencer - Edit steps in groups of four, with adjustable length</li><li>Sample and Hold with Random - One is a conventional S&amp;H implementation, the other side is a clocked random source, with the option for both sides share a single clock</li><li>Dual Calculator (arithmetical operations) - Sum, difference, min, max, average, and ranged random</li><li>Dual Logic (boolean operations) - AND, OR, XOR, NAND, NOR, XNOR, with the option to put the operator under CV control for both channels</li><li>Threshold Logic Neuron - Generate complex but deterministic clocks or states; this is how they thought the brain worked in the 1950s</li><li>Two-Channel Gated/Sequential Switch - Switch between two voltages with a gate signal and/or a clock</li><li>Probability Brancher - Switch between two voltages for each clock pulse, based on a set probability</li><li>Skewed LFO - From saw to triangle to ramp, add a little simple modulation</li><li>Dual ADSR - This</li><li>Lo-Fi Tape Looper - One second. 8 bits. 2kHz. Go!</li><li>Gated VCA - Sort of a two-level VCA, accepts a signal, modulated by CV, and unleashed with a gate... or not, your call</li></ol>There's a YouTube video going over the functions of each module at <a href="https://www.youtube.com/playlist?list=PLC5d4vp670NsuEUQybeCPYOvoDJb9fnBi">https://www.youtube.com/playlist?list=PLC5d4vp670NsuEUQybeCPYOvoDJb9fnBi.</a><br /><br />There will be more apps in the future, but to make space on the O_C for more, I'll have to do a full release that removes most of the native O_C apps.JasonJustiannoreply@blogger.com15tag:blogger.com,1999:blog-4076738808198279381.post-78330565618933130282018-07-02T11:41:00.003-07:002018-07-30T08:54:39.014-07:00Tutorial: Working with Menus, Buttons, EncodersWay back in the Hello World post, like three weeks ago, I promised that I'd cover menu generation. Since then, I've decided that my own apps wouldn't use menus. But, you know, a deal's a deal, and using O_C's native menu generation system is an important part of development.<br /><br />In this post, I'll be referring back to the last tutorial, about saving app settings. The reason for this is that the menu system dovetails very nicely with the setting system. The work that you do to declare data types, ranges, potential values, and names of settings also goes toward generating the menu pretty much automatically. If you need to whip some CV processing up quickly, but don't want to mess around with building UI from scratch, you're in luck.<br /><br />Please pull the Tutorial <a href="https://github.com/Chysn/O_C_Tutorial1.git" target="_blank">repo at the usual place</a>, and follow along in the new APP_LOGIC.ino file.<br /><br /><h3>The App </h3><br />The application is a simple 4-channel logic app. Each channel takes input from two digital inputs, processes it with one of six logic gates (and, or, xor, nand, nor, xnor), and outputs the result to the corresponding channel.<br /><br />For each output channel (A, B, C, D), the user chooses a logic gate operation and selects which digital inputs are used for the calculation. The selection follows conventional O_C functionality: the right button switches between selection of the parameter and editing, while the right encoder scrolls through the parameters or selects values. The underlying framework asks you to code the functions of the controls, but the data management and screen handling (switching, scrolling, etc.) is handled for you.<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-GYxjTkBxQp0/Wzpn2cuiIQI/AAAAAAAAPKQ/_oYbCAS5QfwwVdWAMS5D95lJ9ckkzd93wCLcBGAs/s1600/IMG_2347.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="640" data-original-width="480" height="320" src="https://1.bp.blogspot.com/-GYxjTkBxQp0/Wzpn2cuiIQI/AAAAAAAAPKQ/_oYbCAS5QfwwVdWAMS5D95lJ9ckkzd93wCLcBGAs/s320/IMG_2347.JPG" width="240" /></a></div><br /><br /><br /><br /><br /><h3>Step 1: Set up the enum containing the settings</h3>Don't want to dwell on the first step too much, because we've seen this stuff before. The enum is a list of labels for settings, with a last setting marker:<br /><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">enum LOGIC_SETTINGS {<br />&nbsp;&nbsp;&nbsp; LOGIC_SETTING_OPERATION_A,<br />&nbsp;&nbsp;&nbsp; LOGIC_SETTING_SOURCE_A,<br />&nbsp;&nbsp;&nbsp; LOGIC_SETTING_OPERATION_B,<br />&nbsp;&nbsp;&nbsp; LOGIC_SETTING_SOURCE_B,<br />&nbsp;&nbsp;&nbsp; LOGIC_SETTING_OPERATION_C,<br />&nbsp;&nbsp;&nbsp; LOGIC_SETTING_SOURCE_C,<br />&nbsp;&nbsp;&nbsp; LOGIC_SETTING_OPERATION_D,<br />&nbsp;&nbsp;&nbsp; LOGIC_SETTING_SOURCE_D,<br />&nbsp;&nbsp;&nbsp; LOGIC_SETTING_LAST<br />};</span></span></i><br /><br />See also the LOGIC class definition, which uses the SettingsBase parent class.<br /><br /><h3>Step 2: Set some value lists</h3><br />In the last tutorial, I used settings strings from OC::Strings. This time, I'm defining my own settings strings, which will be displayed on the screen and used in Step 3:<br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;"><br /></span></span></i><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">const char* const gates[6] = {<br />&nbsp; "AND", "OR", "XOR", "NAND", "NOR", "XNOR"<br />};<br /><br />const char* const input_pairs[3] = {<br />&nbsp; "1,2", "2, 3", "3,4"<br />};</span></span></i><br /><br /><h3>Step 3: Declare the settings</h3><br />In the last tutorial, the settings declaration was only used to identify what would be saved. This time, the declarations will actually be used for displaying and validating menu values, so it's important to set ranges and names up as you want them to function:<br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;"><br /></span></span></i><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">SETTINGS_DECLARE(LOGIC, LOGIC_SETTING_LAST) {<br />&nbsp;&nbsp;&nbsp; { 0, 0, 5, "Logic Gate A", gates, settings::STORAGE_TYPE_U8 },<br />&nbsp;&nbsp;&nbsp; { 0, 0, 2, "Source for A", input_pairs, settings::STORAGE_TYPE_U8 },<br />&nbsp;&nbsp;&nbsp; { 0, 0, 5, "Logic Gate B", gates, settings::STORAGE_TYPE_U8 },<br />&nbsp;&nbsp;&nbsp; { 0, 0, 2, "Source for B", input_pairs, settings::STORAGE_TYPE_U8 },<br />&nbsp;&nbsp;&nbsp; { 0, 0, 5, "Logic Gate C", gates, settings::STORAGE_TYPE_U8 },<br />&nbsp;&nbsp;&nbsp; { 0, 0, 2, "Source for C", input_pairs, settings::STORAGE_TYPE_U8 },<br />&nbsp;&nbsp;&nbsp; { 0, 0, 5, "Logic Gate D", gates, settings::STORAGE_TYPE_U8 },<br />&nbsp;&nbsp;&nbsp; { 0, 0, 2, "Source for D", input_pairs, settings::STORAGE_TYPE_U8 }<br />};</span></span></i><br /><br /><h3>Step 4: The Cursor</h3><br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">menu::ScreenCursor&lt;menu::kScreenLines&gt; cursor;</span></span></i><br /><br />You'll provide this as a public property in your app's class somewhere. Some native apps put it in a struct. The idea is that it needs to be available, because it handles lots of stuff, as we'll soon see.<br /><br />In this case, it's in the LOGIC class, and will be referred to via logic_instance as logic_instance.cursor. If you want to hide it in a private property and create a getter for it, knock yourself out.<br /><br /><h3>Step 5: Initialize the cursor in Init()</h3><br />This tells the cursor which parameter to start with (from the enum generated in Step 1), and which parameter to end with. For a simple menu, you can just provide the starting and ending points of the entire list of settings. But your app may have different needs.<br /><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">void LOGIC_init() {</span></span></i><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; logic_instance.cursor.Init(LOGIC_SETTING_OPERATION_A, LOGIC_SETTING_LAST - 1);<br />&nbsp;&nbsp;&nbsp; logic_instance.Init();<br />}</span></span></i><br /><br /><h3>Step 6: Drawing the menu</h3><br />To draw the menu, create a SettingsList and then iterate through its available() method until you've reached the end of the settings. The pattern below will get you a conventional O_C menu, and you really don't need to do much else, other than provide logic that might show or hide settings conditionally. But you don't need me for that.<br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;"><br /></span></span></i><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">void LOGIC_menu() {<br />&nbsp;&nbsp;&nbsp; menu::DefaultTitleBar::Draw();<br />&nbsp;&nbsp;&nbsp; graphics.print("Logic");<br /><br />&nbsp;&nbsp;&nbsp; <span style="background-color: yellow;">menu::SettingsList&lt;menu::kScreenLines, 0, menu::kDefaultValueX - 1&gt; settings_list(logic_instance.cursor);<br />&nbsp;&nbsp;&nbsp; menu::SettingsListItem list_item;<br />&nbsp;&nbsp;&nbsp; while (settings_list.available())<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const int current = settings_list.Next(list_item);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const int value = logic_instance.get_value(current);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; list_item.DrawDefault(value, LOGIC::value_attr(current));<br />&nbsp;&nbsp;&nbsp; }</span><br />}</span></span></i><br /><br /><h3>Step 7: Setting the button and encoder behaviors</h3><br />You should have some idea about how to handle button and encoder events, as this topic has been covered in the Pong Game tutorial. However, looking back, I sort of glossed over that, so we'll talk about it a bit here. First, here's the button event handler:<br /><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">void LOGIC_handleButtonEvent(const UI::Event &amp;event) {<br />&nbsp;&nbsp;&nbsp; if (event.control == OC::CONTROL_BUTTON_R) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (event.type == UI::EVENT_BUTTON_PRESS) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; logic_instance.cursor.toggle_editing();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; }<br />}</span></span></i><br /><br />Whenever a button is pressed, and the screensaver is not active, the APPNAME_handleButtonEvent() function is called, passing a UI::Event reference.<br /><br />An Event has a few readable properties, two of which are relevant to button presses:<br /><br /><b>event.control</b>: Indicates which button was pressed. OC::CONTROL_BUTTON_R and OC::CONTROL_BUTTON_L are the encoders, and OC::CONTROL_BUTTON_DOWN and OC::CONTROL_BUTTON_UP are the up/down buttons next to the screen.<br /><br /><b>event.type</b>: Indicates whether the button was pressed <span style="font-size: small;"><span style="font-family: inherit;">(UI::EVENT_BUTTON_PRESS<i>)</i></span></span>, or long-pressed (UI::EVENT_BUTTON_LONG_PRESS). Note that a long press of the up button activates the screensaver, and a long press of the right encoder goes to the app selection menu. These events will never be intercepted by an app because they are overridden by the framework.<br /><br />In the handler above, when the right button is pressed, it toggles the editing mode of the app instance's cursor, between parameter selection and editing. You don't need to do anything special to handle the new state.<br /><br />Encoder handling works in a similar way:<br /><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">void LOGIC_handleEncoderEvent(const UI::Event &amp;event) {<br />&nbsp;&nbsp;&nbsp; if (event.control == OC::CONTROL_ENCODER_R) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (logic_instance.cursor.editing()) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; logic_instance.change_value(logic_instance.cursor.cursor_pos(), event.value);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; logic_instance.cursor.Scroll(event.value);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; }<br />}</span></span></i><br /><br />Here, the event.control is set to either OC::CONTROL_ENCODER_R or OC::CONTROL_ENCODER_R, indicating which encoder was turned.<br /><br />Instead of event.type, an encoder event's behavior is read with<br /><br /><b>event.value</b>: Indicates whether the encoder was turned clockwise (1) or anti-clockwise (-1). The O_C system has the ability to fire multiple encoder events when the encoder is turned fast. Your code doesn't need to worry about this. You can just assume that you're handling changes one increment at a time.<br /><br />The handler above checks the cursor.editing() mode to decide what to do. In editing mode, it changes the value at the cursor position. This is nice and automatic: it checks the appropriate range (from SETTINGS_DECLARE() in Step 3) and updates the values in the app class.<br /><br />If editing mode is off, it scrolls through the menu items. Again, you don't need to specifically handle the scrolling. The cursor and SettingsList (Step 6) handle all that for you.<br /><br />That's it for today!<br /><br /><h3>A Word about Hemisphere</h3><br />It was nice to see the support of the Hemisphere project. The Hemisphere introduction dwarfed readership of the next most popular post by over 5,000 views. At this point, Hemisphere is ready for beta testing. So if you'd like to try it out, let me know by IM'ing me at MuffWiggler (I'm chysn) and I'll send you a link to the hex file.<br /><br />In the coming days, I'll be producing a series of one-minute videos, one for each applet. That rollout will be my next blog post.JasonJustiannoreply@blogger.com0tag:blogger.com,1999:blog-4076738808198279381.post-63826734263815845742018-06-24T12:41:00.003-07:002018-06-24T12:57:40.100-07:00Introducing HemispherePeople often ask about O_C whether it can run two apps at once. The logistics of how this would work--sharing the screen, negotiating the I/O, etc.--are mind-boggling, but it doesn't stop people from asking.<br /><br />Hemisphere is a dual-channel Applet framework for Ornament and Crime. But it is <i>not</i> a way to use multiple native Ornament and Crime apps in one unit. Instead, Hemisphere applets are smaller modular building blocks. It will have features that are handy if you don't have them anywhere else, or need more. In a way, it's taking the Ornament and Crime hardware and running in a totally different direction, toward minimalist design. The vision is sort of somewhere between Ornament and Crime and Disting, a system that can do many things, but threads the ease-of-use needle with the benefit of O_C's footprint and screen.<br /><br />The GitHub repo for Hemisphere is in a different place than the previous tutorials. If you're interested in checking out Hemisphere, see the video (at the bottom of the post), and then clone <a href="https://github.com/Chysn/O_C-Hemisphere">https://github.com/Chysn/O_C-Hemisphere</a>.<br /><br />Hemisphere development has consumed most of my free time this week, and will continue to do so for a while, as I build new applets. As I go, I'm finding that the framework needs something. Sometimes I totally rethink a design decision. But at some point, I'll have reached a point of near-quiescence. At this point, I'll start documenting the framework and describing the process of building Hemisphere applets.<br /><br />Since Hemisphere has much to juggle, it's a more opinionated framework than the O_C application framework. But I tried to keep it very, very similar. I've added boilerplate code, too, which makes it terribly easy to get started.<br /><br />More later, of course. For now, I plan to add a couple more applets, then I'm going to spend some time patching with them and looking for improvements.<br /><br /><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/JnGpu2jqeg8/0.jpg" frameborder="0" height="266" src="https://www.youtube.com/embed/JnGpu2jqeg8?feature=player_embedded" width="320"></iframe></div><br /><h3>Executive Summary (Or, tl;dw)</h3>Hemisphere splits O_C in half. Each applet gets half the screen, two digital inputs, two CV inputs, two outputs, one encoder, and one encoder button. Applets are selected by pressing one of the Up/Down buttons and turning an encoder to flip through available Applets, and then pushing an Up/Down button again to lock the selection.<br /><br />Because there will be a lot of applets, each one has a concise help screen, activated by holding down the left encoder button for a couple seconds. Go to the other hemisphere's help screen by long-pressing again. Exit help screens by long-pressing a third time. While in the help mode, the applets continue to function as normal.<br /><br />Hemisphere offers a forwarding mode, which forwards CV1 and CV2 (the left hemisphere's CV inputs) to the right hemisphere. This avoids insane use of multiples or stacked cables for chaining Hemisphere's input. Activate forwarding by long-pressing the Down button.JasonJustiannoreply@blogger.com32tag:blogger.com,1999:blog-4076738808198279381.post-90894820468903540982018-06-20T10:10:00.001-07:002018-06-20T10:44:42.625-07:00Introducing The Darkest TimelineIn my last post, I mentioned The Darkest Timeline. It's now ready to use, and the code is available at my usual repository, <a href="https://github.com/Chysn/O_C_Tutorial1">https://github.com/Chysn/O_C_Tutorial1</a><span style="font-size: small;"><span style="font-family: inherit;"><span style="font-family: &quot;arial&quot; , &quot;helvetica&quot; , sans-serif;"><span style="font-size: xx-small;"><i><b> </b></i></span></span></span></span><br /><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><br /><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/sMHv4XcWU8s/0.jpg" frameborder="0" height="266" src="https://www.youtube.com/embed/sMHv4XcWU8s?feature=player_embedded" width="320"></iframe></div><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/F1LWZ8wfUaI/0.jpg" frameborder="0" height="266" src="https://www.youtube.com/embed/F1LWZ8wfUaI?feature=player_embedded" width="320"></iframe></div><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><span style="font-size: small;"><span style="font-family: inherit;">I've got some near-future plans for O_C, which I'll get into in a later post; but for now I'm formulating a design philosophy, and The Darkest Timeline is a test case for some of these principles. You won't see, from me, apps that <i>look</i> or <i>feel</i> like Ornaments and Crime's native apps. Some of my principles are as follows:</span></span><span style="font-size: small;"><span style="font-family: inherit;"> </span></span><br /><ol><li><span style="font-size: small;"><span style="font-family: inherit;"><b>Minimize text, maximize graphics.</b> I won't do scrolling lists of options.</span></span></li><li><span style="font-size: small;"><span style="font-family: inherit;"><b>Use the screen for display, not for input.</b> The app should be at least somewhat usable with the screen covered.</span></span></li><li><span style="font-size: small;"><span style="font-family: inherit;"><b>Aim for one function per control. </b>An encoder button can toggle the function of <i>that</i> encoder, but the current function must be readily ascertained. Functions beyond basic functionality may be hidden behind long-press buttons.</span></span></li><li><span style="font-size: small;"><span style="font-family: inherit;"><b>Avoid multiple screens.</b> That is, no menu diving.</span></span></li><li><b><span style="font-size: small;"><span style="font-family: inherit;">The screensaver should be a skeletalized version of the main screen</span></span></b><span style="font-size: small;"><span style="font-family: inherit;"><b>.</b> Your workflow should not be interrupted by the screensaver.</span></span></li></ol><span style="font-size: small;"><span style="font-family: inherit;">The Darkest Timeline follows these principles pretty well, and it's a lot of fun to play. It has two features that I'm particularly proud of.</span></span><br /><br /><span style="font-size: small;"><span style="font-family: inherit;">The first is an adjustable index point. The "index point" is basically the start of the sequence. Regardless of the sequence length, the index point may be scrubbed through from the panel or via CV. This lets you warp a set of steps in a lot of different ways.</span></span><br /><br /><span style="font-size: small;"><span style="font-family: inherit;">The second feature is the concept of alternate timeline output. Output B and output D provide alternate versions of the CV and trigger probability, respectively. Output B plays the sequence directly AFTER the one played by output A. That is, if the sequence is 8 steps, output A plays steps 0-8, and output B plays steps 9-16 (assuming an index point of 0). Output D is a probability-based trigger like output C, except it uses the step's complementary probability. That is, if the step has a 70% chance of firing the trigger from output C, there's a 30% chance of output D's trigger firing. These probabilities are independent, so it's possible for C and D to both fire (or not fire) on the same step. </span></span><br /><h3><span style="font-size: small;"><span style="font-family: inherit;">The Controls</span></span></h3><br /><span style="font-size: small;"><span style="font-family: inherit;"><b>Digital Input 1</b>: Advance forward one step</span></span><br /><span style="font-size: small;"><span style="font-family: inherit;"><b>Digital Input 2:</b> Advance backward one step</span></span><br /><span style="font-size: small;"><span style="font-family: inherit;"><b>Digital Input 3:</b> Reset to the index point</span></span><br /><span style="font-size: small;"><span style="font-family: inherit;"><b>CV Input 1: </b>Record value (0-5V) for the CV Timeline</span></span><br /><span style="font-size: small;"><span style="font-family: inherit;"><b>CV Input 2:</b> Record value (0-5V) for the Probability Timeline</span></span><br /><span style="font-size: small;"><span style="font-family: inherit;"><b>CV Input 3:</b> Set the index point</span></span><br /><span style="font-size: small;"><span style="font-family: inherit;"><b>Up Button:</b> Toggle recording to the CV Timeline. The active step (the leftmost step) will display a blinking cursor when recording. Long hold: enter screensaver mode, per usual.</span></span><br /><span style="font-size: small;"><span style="font-family: inherit;"><b>Down Button:</b> Toggle recording to the Probability Timeline. The active step will display a blinking cursor when recording. Long hold: clear both timelines.</span></span><br /><span style="font-size: small;"><span style="font-family: inherit;"><b>Left Encoder:</b> Set sequence length (fewer steps to the right, like you're "zooming in")</span></span><br /><span style="font-size: small;"><span style="font-family: inherit;"><b>Left Button (Long hold)</b>: Randomize both timelines and set index point to 0 </span></span><br /><span style="font-size: small;"><span style="font-family: inherit;"><b>Right Button</b>: Toggle the function of the Right Encoder between changing the active step and editing the index point. The index point indicator (the vertical bar) will become a solid blinking line when editing the index point.</span></span><br /><span style="font-size: small;"><span style="font-family: inherit;"><b>Right Encoder:</b> Move within the sequence or enable index point editing</span></span><br /><span style="font-size: small;"><span style="font-family: inherit;"><b>Output A:</b> Normal Timeline CV, the values that you see on the screen.</span></span><br /><span style="font-size: small;"><span style="font-family: inherit;"><b>Output B:</b> Alternate Timeline CV, the values that come <i>after</i> the values that you see on the screen.</span></span><br /><span style="font-size: small;"><span style="font-family: inherit;"><b>Output C:</b> Normal Timeline Trigger, (maybe) a 6ms trigger based on the Probability Timeline. All the way up, the trigger will fire. All the way down, it won't. Everywhere else, it's more likely to fire if the bar is higher.</span></span><br /><span style="font-size: small;"><span style="font-family: inherit;"><b>Output D: </b>Alternate Timeline Trigger, (maybe) a 6ms trigger based on the <i>complement</i> of the value from the Probability Timeline. All the way, the trigger will not fire. All the way down, it will. Everywhere else, it's more likely to fire if the bar is lower.</span></span><br /><br /><span style="font-size: small;"><span style="font-family: inherit;">All right, feel free to leave a comment if you have any questions. I hope that some of you grow a goatee and enjoy the alternate timelines....</span></span><br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://4.bp.blogspot.com/-Zc3R72wUTEs/WyqKD-mADhI/AAAAAAAAPKA/O9-y5rKTwn44ErrtNWmOwRKI0d-D7LuogCLcBGAs/s1600/acb69a08264d486f7c134fe81a317790.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="550" data-original-width="550" height="320" src="https://4.bp.blogspot.com/-Zc3R72wUTEs/WyqKD-mADhI/AAAAAAAAPKA/O9-y5rKTwn44ErrtNWmOwRKI0d-D7LuogCLcBGAs/s320/acb69a08264d486f7c134fe81a317790.jpg" width="320" /></a></div>JasonJustiannoreply@blogger.com4tag:blogger.com,1999:blog-4076738808198279381.post-41666126009196399302018-06-18T21:06:00.001-07:002018-06-18T21:23:58.303-07:00Tutorial: Saving App SettingsOrnament and Crime is capable of storing your app settings to flash memory on the included Teensy 3.2 board. It's pretty simple storage, capable of storing only a flat list of integers, but there's not much that you absolutely can't do with enough integers.<br /><br />It's also easy enough to use. I'm going to walk you through that today. Thanks again to Patrick Dowling for helping to fill in several gaps.<br /><br /><h3>The App</h3><h3>&nbsp;</h3>I made this app for a reason. The O_C's ADC class has no fewer than <i>five</i> methods of getting CV input and I got sick and tired of experimenting with these, and I just wanted to use <i>science</i>. So today's app, CV Inspector, gives you a list of the current values of all five ADC methods from all four CV inputs.<br /><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-mFquNbCGtP4/Wyhvs8U_C6I/AAAAAAAAPJk/mYfU2ejEL4g5c4hULCkxkcASfy1n_beawCLcBGAs/s1600/IMG_2304.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="640" data-original-width="480" height="200" src="https://1.bp.blogspot.com/-mFquNbCGtP4/Wyhvs8U_C6I/AAAAAAAAPJk/mYfU2ejEL4g5c4hULCkxkcASfy1n_beawCLcBGAs/s200/IMG_2304.JPG" width="150" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">CV Inspector</td></tr></tbody></table>This app seems too simple to have any settings, and it really is, but I've been promising to talk about settings, so I kind of crammed it in there. There's only a single setting, and that's the selected channel.<br /><br />If you clone or update your working copy of my repo (<a href="https://github.com/Chysn/O_C_Tutorial1.git">https://github.com/Chysn/O_C_Tutorial1.git</a>) and install it on your O_C, you can try it out. Either encoder changes the CV input channel. And that's about it. To save the current channel, long-hold the right encoder button to go back to the menu, then long-hold the same button again to save the setting. You'll see a little rectangle become a big rectangle by way of confirmation.<br /><br />The app file is APP_CVINSP.ino. Please open it up and take a look at the tutorial sections one at a time. There are seven steps, each with TUTORIAL STEP <i>n</i> in the comment.<br /><br /><h3>Step 1: Add a settings enum</h3><br />Make an enum containing representations of your setting names. You can use these enum values to reference the settings later. This app has only one setting, but two enum values. Why? Because enum values are assigned as ints starting from 0, so the APPNAME_SETTING_LAST is used to represent the total number of settings:<br /><br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">enum CVINSP_SETTINGS {</span></span></i><br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">&nbsp;&nbsp;&nbsp; CVINSP_CHANNEL,<br />&nbsp;&nbsp;&nbsp; CVINSP_SETTING_LAST<br />};</span></span></i><br /><br /><h3>Step 2: Make your application class a subclass of SettingsBase</h3><br />Your app class should be a subclass of settings::SettingsBase. Include the class name, and the APPNAME_SETTING_LAST, as shown below. This lets the base class know the size of the settings array:<br /><br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">class CVInspector : public settings::SettingsBase&lt;CVInspector, CVINSP_SETTING_LAST&gt; {</span></span></i><br /><br /><h3>Step 3: Declare setting attributes</h3><br />Each setting needs a list of attributes, which are provided using the SETTINGS_DECLARE macro. Each setting is a six-element array that's used to initialize a settings::value_attr struct: <br /><br /><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;"><i>SETTINGS_DECLARE(CVInspector, CVINSP_SETTING_LAST) {<br />&nbsp;&nbsp;&nbsp; {0, 0, 3, "channel", OC::Strings::cv_input_names, settings::STORAGE_TYPE_U4}<br />};</i></span></span><br /><br />The value_attr struct is in util/util_settings.h, and looks like this:<br /><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">struct value_attr {</span></span></i><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; int default_;<br />&nbsp;&nbsp;&nbsp; int min_;<br />&nbsp; &nbsp; int max_;<br />&nbsp;&nbsp;&nbsp; const char *name;<br />&nbsp;&nbsp;&nbsp; const char * const *value_names;<br />&nbsp;&nbsp;&nbsp; StorageType storage_type;</span></span></i><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;"><br /></span></span></i><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; // and some methods...</span></span></i><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">}</span></span></i><br /><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;"><br /></span></span>The default, min, and max values are, of course, all integers. The name can be a string literal. The value_names is an array of strings, or NULL if you don't need string values for the setting. The StorageType options are also in util_settings.h:<br /><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">enum StorageType {<br />&nbsp; STORAGE_TYPE_U4, // nibbles are packed where possible, else aligned to next byte<br />&nbsp; STORAGE_TYPE_I8, STORAGE_TYPE_U8,<br />&nbsp; STORAGE_TYPE_I16, STORAGE_TYPE_U16,<br />&nbsp; STORAGE_TYPE_I32, STORAGE_TYPE_U32,<br />};</span></span></i><br /><br /><h3>Step 4: Implement required global functions</h3><br />Implement APPNAME_storageSize(), APPNAME_save(), and APPNAME_restore(). Don't miss that storageSize() operates on the class, while save() and restore() operate on the instance. All of these methods are from the base class, SettingsBase.<br /><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">size_t CVINSP_storageSize() {<br />&nbsp;&nbsp;&nbsp; return CVInspector::storageSize();<br />}<br /><br />size_t CVINSP_save(void *storage) {<br />&nbsp;&nbsp;&nbsp; return inspector_instance.Save(storage);<br />}<br /><br />size_t CVINSP_restore(const void *storage) {<br />&nbsp;&nbsp;&nbsp; return inspector_instance.Restore(storage);<br />}</span></span></i><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><br /></span></i><br /><h3>Step 5: Reading a setting value</h3><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"></span></i>Within your app class code, it's super-easy to use the settings. There's a values_ array that's part of the base class, and you just read it with the enum value (or integer index), like this:<br /><br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">int channel = values_[CVINSP_CHANNEL];</span></span></i><br /><br /><h3>Step 6: Writing a setting value</h3><br />There are a few ways to go about writing or changing values. You can just do<br /><br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">values_[CVINSP_CHANNEL] = channel;</span></span></i><br /><br />of course, but SettingsBase provides two methods for doing it a little better. First, there's apply_value(int index, int value), which simply sets the value. Second, we've got change_value(int index, int delta), which changes the value by the delta amount. In both cases, index refers to an enum value defined in Step 1.<br /><br />What both of these methods have over simply writing to values_ directly is that they do range clamping based on the min and max attributes that you set in Step 3.<br /><br />Note that when changing a setting--whether by manipulating values_, using change_value(), or using apply_value()--the settings are not saved to flash memory until you do the double long-hold thing that I described earlier.<br /><br /><h3>Step 7: Using the value names</h3><br />If you've set an option array in Step 3, you can grab the currently-assigned value by<br /><ol><li>Accessing the value_attr struct</li><li>Using the value as an index to reference the name</li></ol><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">const settings::value_attr &amp;attr = value_attr(CVINSP_CHANNEL);</span></span></i><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">graphics.print_right(attr.value_names[values_[CVINSP_CHANNEL]]);</span></span></i><br /><br />It's well worth taking a look at OC::Strings in the OC_strings.cpp file, where there are a bunch of pre-defined lists of options that apply to common O_C values.<br /><br /><h3>Introducing <i>The Darkest Timeline</i></h3><br />The next post, coming tomorrow or Wednesday, isn't really a tutorial, but puts into practice all of things we've gone over so far. It's a 32-step, 2 channel (CV and probability-based-trigger) indexed sequencer/CV recorder called <i>The Darkest Timeline</i>. This project shows the vision I have for my own future O_C apps. It's a lot of fun to play, and instead of using scrolling text option menus, its interface is entirely graphically-based, with strict function-per-control operation. I'm just finishing up a bit of QA, and I want to make a video, and then I'll put it out there.<br /><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="https://2.bp.blogspot.com/-cnGGBVCd0EA/WyiBAW3QmTI/AAAAAAAAPJw/X68-EeeWcPw5c8sdzVcajan-4UordFj9QCLcBGAs/s1600/IMG_2310.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="480" data-original-width="640" height="150" src="https://2.bp.blogspot.com/-cnGGBVCd0EA/WyiBAW3QmTI/AAAAAAAAPJw/X68-EeeWcPw5c8sdzVcajan-4UordFj9QCLcBGAs/s200/IMG_2310.jpg" width="200" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">The Darkest Timeline</td><td class="tr-caption" style="text-align: center;"><br /></td></tr></tbody></table><br /><h3>What's with the missing apps in the Tutorial repo?</h3><br />I wanted to sort of streamline the tutorial codebase, as the list was really starting to fill up. So I removed some things, and left the native apps that I thought most people would miss the most. But if you're here reading this thing, you know how to get them back.<br /><br />Until soon!JasonJustiannoreply@blogger.com0tag:blogger.com,1999:blog-4076738808198279381.post-58523855150657469102018-06-16T14:51:00.004-07:002018-06-16T23:05:51.892-07:00Tutorial: Pong<div class="separator" style="clear: both; text-align: left;"><span style="font-family: &quot;arial&quot; , &quot;helvetica&quot; , sans-serif;"><span style="font-size: x-small;"><i><b>Reminder: My repo URL is <a href="https://github.com/Chysn/O_C_Tutorial1" target="_blank">https://github.com/Chysn/O_C_Tutorial1</a></b></i></span></span></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">Like twenty years ago, I owned a Kurzweil K2000. It had what we call today--but didn't call back then--an Easter Egg. It was a Pong game that you could play from the panel, and it generated MIDI notes when the ball bounced off a wall. I thought about this game this week, and decided that it would be a nice project to demonstrate a <i>lot</i> of things about Ornaments and Crime development. </div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">Before I describe the game, I want to do a couple of shouts out. First, to my first two actual followers. You guys are the best! Second, to Patrick Dowling, one of the main O_C developers, who has been really responsive and thorough, and told me things about what's going on inside O_C that I couldn't have found by scouring the code. I'm kind of in a place where I feel like I can do just about anything I can think of, largely thanks to Patrick's generosity.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><h3 class="separator" style="clear: both; text-align: left;">The Game</h3><div class="separator" style="clear: both; text-align: left;"></div><div class="separator" style="clear: both; text-align: left;"><a href="https://2.bp.blogspot.com/-kQ4lX2cC22k/WyUVv-D8dKI/AAAAAAAAPJU/9k5AnsM3S50xYRTxUAl4yk_LQNS1wce5gCLcBGAs/s1600/IMG_2302.JPG" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="640" data-original-width="480" height="320" src="https://2.bp.blogspot.com/-kQ4lX2cC22k/WyUVv-D8dKI/AAAAAAAAPJU/9k5AnsM3S50xYRTxUAl4yk_LQNS1wce5gCLcBGAs/s320/IMG_2302.JPG" width="240" /></a></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">It's the Pong we all know and love, with a few twists. As a ball bounces its way across the screen, the player defends the left side of the screen with a "paddle," and the O_C defends the right side. It's an unfair game, though, because the O_C can't lose. As you return the ball and level up, the ball gets faster, and your paddle gets smaller and closer to your opponent. The odds are not in your favor!</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">Controls are as follows:</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><b>Up/Down Buttons: </b>Move the paddle up and down. This is really to illustrate the use of the buttons' event handler, and you really don't want to play the game with these things.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><b>Encoders:</b> Both encoders move the paddle up and down.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><b>CV Input 1:</b> Negative values move the paddle up, and positive values move the paddle down. There's a "center detent," a small range that doesn't move the paddle at all. This is to compensate for noise that gets into the ADC.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><b>Output A:</b> When the ball bounces off your paddle, a short 5V trigger is sent to Output A.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><b>Output B:</b> When the ball bounces off anything else, a short 5V trigger is sent to Output B.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><b>Output C:</b> Sends 0 to 4-ish volts, based on the Y position of the ball. 0V is the top of the screen.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><b>Output D:</b> Sends 0 to 4-ish volts, based on the Y position of the player paddle. 0V is the top of the screen.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><h3 class="separator" style="clear: both; text-align: left;">What I Learned to Do</h3><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">By studying the APP_PONGGAME.ino code and comments, as well as this post, the following skills can be learned:</div><ul><li>How to organize code: delegating graphics to the Menu function and CV processing to either the ISR or Loop function;</li><li>Use of what Patrick Dowling calls the "quick and dirty" Graphics library;</li><li>Use of button and encoder events;</li><li>Use of the Init function for setting initial states.</li></ul>Please pull a code update from GitHub (<a href="http://butmostlycrime.blogspot.com/2018/06/tutorial-hello-world.html">see this link if you need to catch up here</a>) and look at the APP_PONGGAME.ino file. I'm going to assume by now that you know how this app was added to the system, and we're going to be looking at only this file.<br /><br />I think the code itself is pretty well-documented, so I'm not going to repeat that information here, for the most part. Instead I want to focus on the organization of the code.<br /><br /><h3>PONGGAME_menu()</h3><br />menu() is called as part of the main loop in o_c_REV.ino. The screen is redrawn every time menu() is called; nothing persists between calls. According to Dowling, the Graphics calls write to a memory buffer, which is later transferred to the screen.<br /><br />The screen shares a SPI port with the DAC, and the access is interleaved. The Graphics functions are only valid in the menu() and the screensaver() scopes, and O_C will crash if you try to use the drawing tools anywhere else.<br /><br />In Pong, I'm drawing the frame and header in PONGGAME_menu(), and then drawing the pieces with class methods.<br /><br />Note that I'm using menu() <i>only</i> for displaying stuff. Anything that does CV processing or calculation is done elsewhere. Of course, you can arrange your menu() methods however you see fit; but I like to strictly divide presentation from other business logic and I/O.<br /><br />After a number of seconds specified in the calibration menu, O_C switches from calling menu() to calling screensaver(). This is to prevent burn-in of the OLED display. In this case, since this is a visual application, I don't really want a screensaver to happen, and I don't want the display to go dark. So I'm just calling menu() from within screensaver() like this:<br /><br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">void PONGGAME_screensaver() {<br />&nbsp;&nbsp;&nbsp; PONGGAME_menu();<br />}</span></span></i><br /><br />I wouldn't recommend this approach for actual music-related apps. My preference is for the screen to go blank, but you can create your own screensavers in the same way that you create your own menus.<br /><br /><h3>PONGGAME_loop()</h3><br />This might be where I go off the rails a bit. The loop() function was, according to Dowling, a vestige of older versions of the O_C extended firmware. If you look at the included apps, none of them use loop(), but instead use the ISR for handling all I/O and business logic.<br /><br />A major difference between loop() and the ISR is that the loop runs once for each main loop iteration, while the ISR, which is based on a timer, runs every 60 microseconds. For the purposes of Pong, using loop() is plenty fast. So here, loop() is the controller counterpart to menu(). It calculates (but does not display) the ball movement, and it handles the CV state with I/O functions.<br /><br /><h3>ISR</h3><br />In Pong, the ISR is used for timing. We don't want things to move with every invocation of menu() or loop(), or the game wouldn't be playable. So there are a few class properties that act as countdown values before something happens (or is allowed to happen).<br /><br />For example, when the ball hits the player's paddle to return the ball, 5V trigger is sent from Out A. The following code determines if the ball is being returned, and sets return_countdown:<br /><br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">// All these conditions are just asking "Did the ball hit the paddle while traveling left?"</span></span></i><br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">if ((ball_x &lt;= paddle_x + PADDLE_WIDTH) &amp;&amp; (ball_x &gt;= paddle_x)</span></span></i><br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">&nbsp; &amp;&amp; (ball_y &lt;= paddle_y + paddle_h) &amp;&amp; (ball_y &gt;= paddle_y) &amp;&amp; dir_x &lt; 0) {&nbsp;&nbsp;&nbsp;&nbsp;</span></span></i><br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">&nbsp;&nbsp;&nbsp; // If so, bounce the ball, increase the score, and set the hit trigger to fire the reward<br />&nbsp;&nbsp;&nbsp; // CV trigger at the next loop() call.<br />&nbsp;&nbsp;&nbsp; dir_x = -dir_x;<br />&nbsp;&nbsp;&nbsp; score++;<br />&nbsp;&nbsp;&nbsp; <span style="background-color: yellow;">return_countdown = TRIGGER_CYCLE_LENGTH;</span><br /><br />&nbsp;&nbsp;&nbsp; // Level up!!<br />&nbsp;&nbsp;&nbsp; if (!(score % 5)) LevelUp();</span></span></i><br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">}</span></span></i><br /><br />The next time loop() is executed, it checks for a nonzero value of return_countdown, and sets the Out A voltage accordingly with a ternary operator:<br /><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">// Ball return trigger (when the ball hits the player paddle)</span></span></i><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;"><span style="background-color: yellow;">uint32_t out_A = return_countdown &gt;= 0 ? 5 : 0;</span></span></span></i><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">...</span></span></i><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">OC::DAC::set_pitch(DAC_CHANNEL_A, 1, out_A);</span></span></i><br /><br />It's the ISR() method that counts down these countdown values, at the rate of -1 every 60 microseconds.<br /><br /><h3>TL;DR Overview</h3>So the basic structure here is<br /><ul><b></b><li><b>PONGGAME_loop(</b>) determines how the ball moves and updates CV states using Pong::MoveBall() and Pong::UpdateCVState()</li><li><b>PONGGAME_menu() </b>draws the game board and pieces using game-specific draw methods in the Pong class</li><li><b>PONGGAME_screensaver() </b>spits in the face of safety and does the same thing as PONGGAME_menu()</li><li><b>PONGGAME_isr() </b>calls Pong::ISR(), which decrements countdown timers for the ball movement, the player paddle, and two trigger states</li><li><b>PONGGAME_handleButtonEvent()</b> and <b>_handleEncoderEvent()</b> respond to onboard control events by moving the player paddle up and down&nbsp;&nbsp;&nbsp;</li></ul><h3>Graphics</h3><br />I don't think I need to get too much into the weeds on the graphics library. Your app classes will have access to a global object called graphics, which can be used in any app's menu() or screensaver() function, or class methods or functions called by those.<br /><br />The available methods are in software/src/drivers/weegfx.h. The methods here are so straightforward that they're essentially self-documenting. I'll certainly talk about graphics in later posts; but if you spend a bit of time looking at weegfx.h and weegfx.cpp, I think you'll get it.<br /><br /><h3>Exercises</h3><ol><li>Create a patch that can be played by this game</li><li>Create a patch that can play this game</li></ol>I might circle back to this app to talk about more things later, as I realize that I need to clarify more concepts. For now, install it and have some fun with it.<br /><br />JasonJustiannoreply@blogger.com0tag:blogger.com,1999:blog-4076738808198279381.post-56573190797789067182018-06-14T05:17:00.001-07:002018-06-14T13:53:40.398-07:00Tutorial: Sample and HoldIn the last tutorial, we looked at the very basic skeleton of an app, the minimally-sufficient things that you need for O_C to recognize something as an app and put something on the screen. This time, we're going to do something useful.<br /><br />Sample and Hold seemed like a good candidate for an early project because it covers many of the basic input/output functions: It listens for a clock, it takes input from the analog-to-digital converter (ADC), and it sends output to a digital-to-analog converter (DAC).<br /><br />Again, the code for this tutorial is on GitHub at<br /><br /><span style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace;">https://github.com/Chysn/O_C_Tutorial1.git</span><br /><br />Please either update your repo or, if you're just joining us, clone a new working copy. If you don't know what any of this means, go back a couple posts to catch up.<br /><br /><h3>Modified OC_apps.ino&nbsp;</h3><h3>&nbsp;</h3>This tutorial starts out like the last one. First, I'm adding two lines to OC_apps.ino, which do the same thing in two available_apps arrays:<br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;"><br /></span></span></i><span style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace;"><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">DECLARE_APP('S','H', "Sample and Hold", SANDH, SANDH_isr)</span></span></i></span><br /><br />The fourth argument specifies that the app's main code will be in APP_SANDH.ino, and the fifth argument specifies the name of the interrupt service routine. The ISR will be relevant this time.<br /><br /><h3>Created APP_SANDH.ino</h3><h3>&nbsp;</h3>I started with the APP_HELLO.ino file, which is kind of a stripped-down app, and made some changes to it. Step 0 here is to change names from HELLO to SANDH. It's largely a matter of find-and-replace here.<br /><br />Please open APP_SANDH.ino at this point, and we'll go through the things that differentiate this app from the simpler Hello, World app. These steps are documented in the file as "TUTORIAL STEP N".<br /><br /><b>Step 1: Include the IO files at the top</b><br /><br /><span style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace;"><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">#include "OC_DAC.h"<br />#include "OC_ADC.h"<br />#include "OC_digital_inputs.h"</span></span></i></span><br /><br />We'll be using the DAC, the ADC, and the Digital Inputs. Open up and review these files to get an idea of how they work and what functions are available.<br /><br /><b>Step 2: Specify an instance of SANDH</b><br /><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;"><br /></span></span><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">SANDH sandh_instance;</span></span></i><br /><br />The main point of this will be to reference the main SANDH class within the SANDH_ functions that are required in OC_apps.ino. We'll see a concrete example of this in Step 3...<br /><b><br /></b><b>Step 3: Set up the Interrupt Service Routine (ISR)</b><br /><br />An ISR handles one or more interrupt conditions. This might be a clock-driven interrupt, or an event-driven interrupt. I'm not sure yet. In O_C apps, the ISR is often used to scan for changes in the values of the inputs, and that's what we'll be using it for here.<br /><br />The macro in OC_apps.ino specifies that the ISR function is SANDH_isr(), and that is implemented like this:<br /><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">void SANDH_isr() {</span></span></i><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp; return sandh_instance.ISR();<br />}</span></span></i><br /><br />This is something you'll often see in the O_C apps. The specified (global) SANDH_isr() function is simply passing execution along to the class ISR() method. The SANDH::ISR() method is where the actual work starts to happen:<br /><br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">void ISR() {</span></span></i><br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">&nbsp;&nbsp;&nbsp; bool update = OC::DigitalInputs::clocked&lt;OC::DIGITAL_INPUT_1&gt;();</span></span></i><br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">&nbsp;&nbsp;&nbsp; if (update) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this-&gt;TriggerAndSetSample();<br />&nbsp;&nbsp;&nbsp; }</span></span></i><br /><i><span style="font-size: x-small;"><span style="font-family: &quot;verdana&quot; , sans-serif;">}</span></span></i><br /><br />OC::DigitalInputs is a class defined in OC_digital_inputs.h. clocked() is a static method that masks off a specific input's bit in a clock status bit field to see if that input has seen a clock signal go high since the last reset. This will trigger our sample and hold. When this update value is non-zero, SANDH::TriggerAndSetSample() is called....<br /><br /><b>Step 4: Do the Sample and Hold</b><br /><br />Please note that this is a naive implementation of Sample and Hold. I'll point out why after showing you the code:<br /><br /><span style="font-size: x-small;"><i><span style="font-family: &quot;verdana&quot; , sans-serif;">void ReadAndSetSample() {</span></i></span><br /><span style="font-size: x-small;"><i><span style="font-family: &quot;verdana&quot; , sans-serif;">&nbsp;&nbsp;&nbsp; int32_t current_value;</span></i></span><br /><span style="font-size: x-small;"><i><span style="font-family: &quot;verdana&quot; , sans-serif;">&nbsp;&nbsp;&nbsp; current_value = OC::ADC::raw_pitch_value(ADC_CHANNEL_1);</span></i></span><br /><span style="font-size: x-small;"><i><span style="font-family: &quot;verdana&quot; , sans-serif;">&nbsp;&nbsp;&nbsp; OC::DAC::set_pitch(DAC_CHANNEL_A, current_value, 0);</span></i></span><br /><span style="font-size: x-small;"><i><span style="font-family: &quot;verdana&quot; , sans-serif;">}</span></i></span><br /><br />After the ISR has determined that there's a new trigger, it reads the pitch value of channel 1 with OC::ADC::raw_pitch_value(). ADC is defined in OC_ADC.h. I already suggested that you take a look at this file. If you do, you'll see that it has several static methods for getting the value of one of the CV inputs. You have your choice between raw values, smoothed values, pitch values, raw pitched values, etc. I don't know what the practical differences are between these types of values, so I took my cues from the CopierMaschine app and used the raw_pitch_value().<br /><br />The value is immediate sent to the DAC. DAC is defined in OC_DAC.h. There are a couple of static methods for setting an output (DAC::set() and DAC::set_pitch(), among some others). There's no obvious (to me) relationship between ADC value methods and DAC set methods. I call my implementation "naive" because I suspect that I need to do more to get the input voltage and output voltage to be identical. This works <i>pretty well</i>, but it's not a precision app. There's a noticeable difference between the sampled value and the output.<br /><br /><i><b>Update</b>: Patrick Dowling says that the code above is correct, and my O_C's calibration might be off; also the ADC is less accurate than the DAC. My takeaway here is that I might not be able to get the input and output voltages to match exactly, so clearly this will never be the best sample and hold in the world.</i><br /><br />One thing I was wondering about was normalization. Is it possible for O_C to determine whether a cable is plugged into a CV input, and route normalized sources accordingly? For example, it's useful to normalize S&amp;H input to a noise source. I'm pretty sure that the answer is No here, as I haven't seen this used in O_C apps. I think that it would basically require another line of digital inputs. <br /><br /><h3>Patch Instructions</h3><h3>&nbsp;</h3>I'm hoping that you know how to load the code up on your O_C. That's why you're here, right? But if you need to catch up on that part, see the post called <a href="http://butmostlycrime.blogspot.com/2018/06/my-oc-arrived-with-firmware-version-1.html">Firmware Update!</a><br /><br />To use this Sample and Hold, patch a clock source (square LFO, end-of-something trigger on Maths, whatever) to your O_C's first digital input. Then, patch a modulation source (another LFO, or noise) into O_C's first CV input. Finally, patch O_C's first output into a modulation destination (filter cutoff, or V/oct) of your choice.<br /><br /><h3>Exercises</h3><h3>&nbsp;</h3><ul><li>Expand Sample and Hold so that multiple voltages are sampled and output with a single trigger</li><li>Expand Sample and Holt to use multiple triggers</li><li>Figure out how to improve pitch accuracy (see how I snuck that in there?)&nbsp;</li></ul><h3>Coming Up</h3><h3>&nbsp;</h3>In the next post, at least the next tutorial-type post, I plan to explore the Graphics class a bit, as we'll eventually want screen-based interfaces and menus. Thanks for spending some of your time with me today, and we'll meet again soon.JasonJustiannoreply@blogger.com2tag:blogger.com,1999:blog-4076738808198279381.post-30453187187439304142018-06-11T05:55:00.000-07:002018-06-11T05:55:18.144-07:00I've Made a Huge MistakeYou might remember, way back in the first episode, when I said this (cue flashback fade and chime sound effect...)<blockquote class="tr_bq"><span style="background-color: white; color: #4e2800; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13px;">I'm not that interested in the apps that already exist in the O_C firmware and this blog isn't going to be about those at all.</span></blockquote><div>This was a huge mistake.</div><div><br /></div><div>I got my O_C two days ago, and I've spent pretty much all of that time reading source code and poking at it.</div><div><br /></div><div>Sure, I tried Quantermain a little bit, and I tried Piqued a little bit. But I really haven't spent much time with the canonical firmware because I plan to eventually replace it almost entirely with my own stuff. O_C doesn't have much memory, due to Teensy not having much memory, so I have to make room for my ambitions.</div><div><br /></div><div>However, as I looked at the source, I realized that learning how the O_C actually works as a user will answer a lot of my questions, and ultimately save me a lot of time.</div><div><br /></div><div>So I'm putting the code away for a while, to just play around with O_C. Don't worry, I'll get back to the development part soon. See you on the other side!</div>JasonJustiannoreply@blogger.com0tag:blogger.com,1999:blog-4076738808198279381.post-49537724461477969382018-06-09T19:09:00.001-07:002018-06-14T05:23:42.546-07:00Tutorial: Hello, World!<div>Welcome back! In my last post, I promised a minor modification to the codebase. The modification is to add the simplest-possible app, a "Hello, World" program for Ornament and Crime. The project does two things:</div><div><ol><li><span style="font-family: &quot;times&quot; , &quot;times new roman&quot; , serif;">It adds "Hello, World" to the bottom of the app menu (the one you get when you hold down the right encoder button for two seconds and release)</span></li><li><span style="font-family: &quot;times&quot; , &quot;times new roman&quot; , serif;">It adds an Arduino sketch file for the Hello World app, the purpose of which is simply to say "Hello, World!"</span></li></ol><div><span style="font-family: &quot;times&quot; , &quot;times new roman&quot; , serif;">Clone my Tutorial fork into your project directory, so you can follow along:</span></div></div><div><div><ul><li>Tutorial URL:&nbsp;<a href="https://github.com/Chysn/O_C_Tutorial1">https://github.com/Chysn/O_C_Tutorial1</a>, or</li><li><span style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace;">git clone&nbsp;https://github.com/Chysn/O_C_Tutorial1.git</span></li></ul></div></div><span style="font-family: &quot;times&quot; , &quot;times new roman&quot; , serif;">There are three files that have been modified. I'll explain those modifications here (all these files are in the O_C_Tutorial1/software/o_c_REV directory).</span><br /><br /><h3>Modified OC_version.h</h3><br />This is a super-simple one. This is just a string that contains the version at the bottom of the O_C's boot screen. A comment warns that this is a generated file, but it's not going to be overwritten as part of any process that we're undertaking. And it seems to me that if this were a real danger, this file wouldn't be under version control in the first place. So I modified it just to remind you of what your O_C is running.<br /><br /><h3>Modified OC_apps.ino</h3><h3>&nbsp;</h3>I've been working with the O_C firmware for one day now, and here's something that's awesome about it: Apps are fairly self-contained. That is, you just need to tell OC_apps.ino about a new app, and then create a sketch file for it. Compare this to Peaks's firmware, in which references to new modes need to be scattered all over the place. The comments in the code are self-effacing <span style="font-family: &quot;times&quot; , &quot;times new roman&quot; , serif;">("This is a very poor-man's application 'switching' framework")</span>, but this is really quite considerate.<br /><br />To let O_C know what your new app is, you simply add two lines to OC_apps.ino. Both lines are variations of the same information; one is specifying a "boring" but useful name, and one is specifying an absurd pun that provides little information. Absurd pun is the default.<br /><style type="text/css">p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco; color: #4e9072} </style> <br />The lines to be added are near the top, as DECLARE_APP function-like macro calls, like this:<br /><br /><style type="text/css">p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco} span.s1 {text-decoration: underline} span.s2 {text-decoration: underline ; color: #3933ff} </style> <i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace; font-size: x-small;">DECLARE_APP('H','W', "Hello, World", HELLO, HELLO_isr)</span></span></i><br /><span style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace;"><br /></span><span style="font-family: &quot;times&quot; , &quot;times new roman&quot; , serif;">It would be instructive to take a look at the macro definition starting on line 27. The first two values, as far as I can tell, don't matter much. chars are passed, and the first one becomes a most-significant byte, and the second one becomes a least-significant byte of a 16-bit integer value. At this time, I'm not clear about the point of this value.</span><br /><span style="font-family: &quot;times&quot; , &quot;times new roman&quot; , serif;"><br /></span><span style="font-family: &quot;times&quot; , &quot;times new roman&quot; , serif;">The next value is the natural language name of the item, as it will appear in the menu. You're expected to provide two DECLARE_APP() calls, one for if a BORING_APP_NAMES preprocessor option is selected, and one if not.</span><br /><span style="font-family: &quot;times&quot; , &quot;times new roman&quot; , serif;"><br /></span><span style="font-family: &quot;times&quot; , &quot;times new roman&quot; , serif;">The next value is the prefix of the function names. For example, the "Hello, World" code will require method called HELLO_init(), HELLO_menu(), etc. This value is also used for the filename of the .ino file. In our present example, the app file will be called APP_HELLO.ino.</span><br /><span style="font-family: &quot;times&quot; , &quot;times new roman&quot; , serif;"><br /></span><span style="font-family: &quot;times&quot; , &quot;times new roman&quot; , serif;">The final value is the name of the interrupt service routine (ISR) function for the app. I don't know why the macro doesn't just use </span><span style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace;">prefix ## _isr</span><span style="font-family: &quot;times&quot; , &quot;times new roman&quot; , serif;">, like it does for the other funcitons. I suspect it has something to do with allowing apps to share the same ISR function, if necessary. But if that was the case, then APP2_isr() could just call APP2_isr(). Anyway, I'm sure there's a good reason for it.</span><br /><span style="font-family: &quot;times&quot; , &quot;times new roman&quot; , serif;"><br /></span><span style="font-family: &quot;times&quot; , &quot;times new roman&quot; , serif;">Once you've got the two DECLARE_APP() calls in, then you're pretty much done modifying the "framework" code. From now on, you're just creating your own stuff.</span><br /><br /><h3>Created APP_HELLO.ino</h3><h3>&nbsp;</h3>The APP_SOMETHING.ino file is where the main functionality of your app is defined. If you open up APP_HELLO.ino now to examine it, you'll see that I left most of the functions blank (or returning zero, where a return value is needed). This app does only one thing, and that's to print "Hello, World!" in a title bar at the top of the screen. This is done with the <i>graphics</i> object, which is instantiated as an extern Graphics instance in src/drivers/display.h.<br /><br /><i><span style="font-family: &quot;verdana&quot; , sans-serif;"><span style="font-size: x-small;">void HELLO_menu() {<br />&nbsp;&nbsp;&nbsp; menu::DefaultTitleBar::Draw();<br />&nbsp;&nbsp;&nbsp; graphics.setPrintPos(1,1);<br />&nbsp;&nbsp;&nbsp; graphics.print("Hello, World!") ;<br />} </span></span></i><br /><br />Incidentally, the interface for the Graphics class is in src/drivers/weegfx.h, and the implementations are in weegfx.cpp. This looks like a no-nonsense but capable display system, and I look forward to playing around with it.<br /><br />Note that all eleven of the functions specified in the DECLARE_APP macro (that's including the ISR function)&nbsp;<i>must</i> be implemented, even if they're just empty stubs. Otherwise, you get a compile-time error.<br /><br />Take a look at these three file changes, then upload the code to your O_C. Look for the new version at the bottom of the boot screen. Then, hold the right encoder for two seconds, and scroll down to look for the Hello, World app. Select the app and make sure that "Hello, World!" appears. You can get out of "Hello, World" by holding the right encoder down again.<br /><br />Next, we'll be starting on an app that actually <i>does</i> something. In the near future, we'll go over<br /><ul><li>Dealing with app settings and fleshing out the menu</li><li>Reading from one trigger input and one CV input</li><li>Writing to one DAC</li></ul>JasonJustiannoreply@blogger.com0tag:blogger.com,1999:blog-4076738808198279381.post-57372328193408354572018-06-09T07:53:00.002-07:002018-06-09T19:26:10.474-07:00Firmware Update!My O_C arrived with firmware version 1.3.3b. I decided that my first project was going to be to update the firmware from the source.<br /><div><br /></div><div>The instructions at&nbsp;<a href="http://ornament-and-cri.me/firmware/#method_b">http://ornament-and-cri.me/firmware/#method_b</a> are complete enough. But there were a few concepts that I had work through:</div><h3>The USB Trace</h3><div>I did not build my O_C, so I didn't realize that the USB trace was cut on my O_C's Teensy. So I spent a few frustrating minutes wondering why the LED wouldn't light when I had it plugged into my computer. When the USB trace is cut, the Teensy no longer takes its power via USB. You have to have the O_C powered by your modular during an update.</div><h3>Arduino IDE Settings</h3><div>Make sure that all of the settings in Arduino are right. These are all available under the Tools menu.</div><div><ul><li>Teensy 3.2/3.1 Board selected</li><li>USB Type: "No USB"</li><li>CPU Speed: "120MHz (overclocked)"</li><li>Optimize: "Faster"</li></ul></div><h3>Push the Button!</h3><div>When you're ready to compile and upload to your O_C, click the Upload button on Arduino IDE. It'll take a little while for the code to compile. When compilation is done, a separate Teensy application will open and say that the Arduino is attempting to put the Teensy into program mode. This is your cue to push the button on the Teensy, the little button directly across from the USB connector.<br /><br />Note that Arduino IDE might complain that the Teensy isn't responding. This is okay. At this point, the actual upload is in the hands of the Teensy application. You can press the button at any time and the upload will get done. As long as you have the Teensy application open, the most recently-compiled code will be uploaded whenever you press the button on the Teensy.</div><h3>A New Teensy</h3><div>Teensy 3.2 boards are fairly inexpensive, weighing in at around $25USD on Amazon. I'm going to buy an extra one or two. I plan on doing a fairly big application (more on that later), and it would be nice to have on Teensy for "standard" firmware, and one for my custom firmware. If I ever blow my O_C up, I can revert to a known-functional board. Also, I'd like to be able to program the Teensy from my desk, so I'd like to have one without the USB trace cut.</div><h3>What's Next?</h3><div>My next thing is going to be to modify the firmware in some way. I've spent a bit of time studying the source, and I'd like to convert that into a visible change of some kind. You know... get my feet wet, build some confidence with the framework. Until next time!</div>JasonJustiannoreply@blogger.com2tag:blogger.com,1999:blog-4076738808198279381.post-5188934868037686722018-06-09T05:50:00.000-07:002018-06-28T18:27:06.209-07:00Preparations<h2>Firmware&nbsp; </h2>The firmware for Ornament and Crime is on GitHub.<br /><br />https://github.com/mxmxmx/O_C<br /><br />Opened a terminal window, cd to your personal projects directory, and clone the project like this:<br /><br /><span style="font-family: &quot;courier new&quot; , &quot;courier&quot; , monospace;">git clone https://github.com/mxmxmx/O_C.git</span><br /><br />Now you have a new directory called O_C. Later, we'll create a fork of the O_C project on GitHub to manage our own changes. <br /><h2>Arduino</h2>I already had Arduino IDE, but I updated it to the latest version here:<br /><br />https://www.arduino.cc/en/Main/Software<br /><h2> </h2><strike>You'll want the latest version (1.8.5 as of this writing) because the Teensyduino plugin, which is necessary to get firmware to O_C, will only install to certain versions of Arduino IDE.</strike><br /><br /><b><i>Update 6/28: Disregard the above. Currently, you'll want Arduino 1.8.1 and <a href="https://www.pjrc.com/teensy/td_135/TeensyduinoInstall.dmg" target="_blank">Teensyduino 1.35 </a>to ensure maximum stability with O_C.&nbsp;</i></b><br /><br />Arduino IDE comes in a .zip file, not a .dmg file, and there's no installer. So after you download and unzip the file, drag the Arduino icon to your Applications folder.<br /><h2>Teensyduino</h2>After you download, install, and run Arduino IDE, get the Teensyduino plugin here:<br /><br />https://www.pjrc.com/teensy/td_download.html<br /><br />To install Teensyduino, just click on the DMG file and follow the instructions. If all goes well, you'll see the Teensy board managers under Tools &gt; Boards in the Arduino application.JasonJustiannoreply@blogger.com0tag:blogger.com,1999:blog-4076738808198279381.post-16456808901606638012018-06-09T05:20:00.002-07:002018-06-09T19:48:19.574-07:00A Few Thoughts Before We BeginWelcome!<br /><br />This is to be a blog about app development for Ornament and Crime (O_C).<br /><br />My name is Jason, and I will be your host. I'm doing this because I didn't find much help for getting started. I thought that--if I manage to make reasonable inroads--my experiences might help others who are interested in extending the functionality of O_C.<br /><br />Whether I make reasonable inroads is too early to say. If you're reading this, chances are that I've got a tutorial here that I think might help people like me. That is, I won't post the link to this blog unless there's something worth reading.<br /><br />Why O_C? Well, I have some experience writing firmware (specifically, various types of sequencers) for Mutable Instruments Peaks. I hit a wall with Peaks due to its lack of save-able storage and its lack of CV inputs, and I saw potential in the O_C hardware to further my interest in unusual sequencers. I'm not that interested in the apps that already exist in the O_C firmware and this blog isn't going to be about those at all.<br /><br />Three quick notes:<br /><br /><b>One: This will be a detailed step-by-step process</b><br /><br />I'm going to write as I go, to help myself remember in the future, and to help anyone who has similar goals. We're talking baby steps here. I plan to have a whole post about how to install the firmware. If it's going too slowly for you, hop in your TARDIS and move ahead.<br /><br /><b>Two: I'm not an authority</b><br /><br />I'll get things wrong. I'll misinterpret the point of existing code, and I'll probably re-implement things that have already been done. If I catch this kind of stuff (or if it's pointed out to me), I'll go back and edit for clarity. But if you're following me in real time, don't expect me to have any answers, because I'm learning. Maybe we can help each other!<br /><br /><b>Three: I'm Mac-Centric</b><br /><br />Tutorials will be based on procedures for macOS. This will probably resemble Linux procedures very closely, but Windows might be a whole different world. I apologize in advance if this makes things more difficult for some readers, but I don't think the gulf will be too wide.JasonJustiannoreply@blogger.com0