tag:blogger.com,1999:blog-83383968337051577912024-03-14T09:29:23.219+00:00My Code HereSome articles on computing, security, mathematics, science, language, and music. Some not.John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.comBlogger319125tag:blogger.com,1999:blog-8338396833705157791.post-24729272284914953982021-05-05T12:25:00.009+01:002021-05-05T13:52:19.884+01:00Don't Worry (Paul O'Brien)<p style="clear: both; text-align: center;"><span style="font-family: inherit;"><span color="rgba(0, 0, 0, 0.87)" face="Roboto, Noto, sans-serif" style="background-color: white; text-align: start; white-space: pre-wrap;">Song written by Paul O'Brien. Guitar & vocals by Paul O'Brien.<br /></span><span color="rgba(0, 0, 0, 0.87)" face="Roboto, Noto, sans-serif" style="background-color: white; text-align: start; white-space: pre-wrap;">Additional instrument parts written & performed by John Michael Kerr.<br /></span><span color="rgba(0, 0, 0, 0.87)" face="Roboto, Noto, sans-serif" style="background-color: white; text-align: start; white-space: pre-wrap;">Sound production & captioning by John Michael Kerr.</span></span></p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="266" src="https://www.youtube.com/embed/hGdFS67FE54" width="320" youtube-src-id="hGdFS67FE54"></iframe></div><p style="text-align: center;"><a href="https://www.youtube.com/watch?v=hGdFS67FE54" target="_blank">Watch on YouTube</a></p><h3 style="text-align: left;">Instrumentation</h3><ol style="background-color: white; color: #333333;"><li><span style="font-family: inherit;">Video - Paul's original iPhone recording (audio not used)</span></li><li><span style="font-family: inherit;">Vocals - Condenser mic through Behringer UMC204HD</span></li><li><span style="font-family: inherit;">Guitar - Fender semi-acoustic</span></li><li><span style="font-family: inherit;">Drums - 64 Pad Kit Rock</span></li><li><span style="font-family: inherit;">Piano - East Village Grand</span></li><li><span style="font-family: inherit;">Violins 1 - BBC SO Discover</span></li><li><span style="font-family: inherit;">Violins 2 - BBC SO Discover</span></li><li><span style="font-family: inherit;">Violas - BBC SO Discover</span></li></ol><h3 style="text-align: left;"><span style="color: #333333; font-family: inherit; font-size: medium;">Production Notes</span></h3><p style="text-align: left;"><span style="font-family: inherit;">This was the point where I finally abandoned the idea of combining my two vocal audio sources - iPhone and condenser mic - because of the extreme difficulty in getting them to register after stitching for tempo. Most of the time it would work out well, but just occasionally, due to the differences in ambience, the two signals would contain unmatched transients, and any attempt to bring them into mutual agreement would result in - not just distracting echoes and phase effects, but actually disturbing ring modulations, worthy of a 1960s Dr Who episode. And Paul made it quite clear, he was not standing for this sort of treatment!</span></p><div><h3 style="text-align: left;"><span style="font-family: inherit;">Critical Response</span></h3><p style="text-align: left;"><span style="font-family: inherit;">From Paul's Facebook page:</span></p><div><ul style="text-align: left;"><li>Nothing yet!</li></ul></div><div><i>Previously: <a href="http://mycodehere.blogspot.com/2021/04/stations-of-cross-by-paul-obrien.html">Stations Of The Cross</a></i></div></div><p style="text-align: left;"></p><span face="Verdana, Arial, sans-serif" style="color: #333333;"><span style="font-size: medium;"><i></i></span></span><p></p>John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-87931721771128497072021-04-30T23:51:00.018+01:002021-05-05T13:52:22.065+01:00Stations Of The Cross (Paul O'Brien)<p style="clear: both; text-align: center;"><span style="font-family: inherit;"><span color="rgba(0, 0, 0, 0.87)" face="Roboto, Noto, sans-serif" style="background-color: white; text-align: start; white-space: pre-wrap;">Song written by Paul O'Brien. Guitar & vocals by Paul O'Brien.<br /></span><span color="rgba(0, 0, 0, 0.87)" face="Roboto, Noto, sans-serif" style="background-color: white; text-align: start; white-space: pre-wrap;">Additional instrument parts written & performed by John Michael Kerr.<br /></span><span color="rgba(0, 0, 0, 0.87)" face="Roboto, Noto, sans-serif" style="background-color: white; text-align: start; white-space: pre-wrap;">Sound production & captioning by John Michael Kerr.</span></span></p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="266" src="https://www.youtube.com/embed/7Q4DlQ_hNDA" width="320" youtube-src-id="7Q4DlQ_hNDA"></iframe></div><p style="text-align: center;"><a href="https://www.youtube.com/watch?v=7Q4DlQ_hNDA" target="_blank">Watch on YouTube</a></p><h3 style="text-align: left;">Instrumentation</h3><p style="text-align: left;"></p><ol style="text-align: left;"><li>Video - Paul's original iPhone recording (audio not used due to poor recording quality)</li><li>Vocals - Condenser mic through Behringer UMC204HD</li><li>Guitar - Fender semi-acoustic</li><li>Recorders - Ableton Core "Recorder Keys"</li><li>Violins - BBC SO Discover</li><li>Double Bass - BBC SO Discover</li></ol><p></p><h3 style="text-align: left;">Production Notes</h3><p style="text-align: left;">After the 10-channel extravagance of Union Card (mixing like it's 1969!), we had a chat about our next project, and seemed to agree that a simpler, more restrained production would be appropriate. "Something along the lines of the Stairway To Govan intro, with recorders, flutes, or similar..."</p><p style="text-align: left;">Paul's original iPhone video recording had such poor quality audio, we agreed to bin it. This didn't make life much easier for me, however, as the video still had to be stitched or "warped" to ensure the guitar notes synchronised correctly with the video experience. At the time of writing we still haven't figured out why the iPhone audio has taken such a nosedive recently.</p><p style="text-align: left;">I've boosted Paul's guitar in the beginning and end sections. His fingerpicking work is good here, so I showcased it by increasing its volume and dropping out the double bass in the last 8 bars.</p><p style="text-align: left;">Vocals have a tweaked "Vocal Presence" filter applied, and Guitar uses "Acoustic Git EQ 1".</p><h3 style="text-align: left;">Critical Response</h3><p style="text-align: left;">From Paul's Facebook page:</p><p style="text-align: left;"></p><ul style="text-align: left;"><li>SO beautiful!!! ❤️</li><li>Hey, Elenor Rigby, where's ur hair shirt? ( good song)</li></ul><p></p><p style="text-align: left;"><i>Previously: <a href="http://mycodehere.blogspot.com/2021/04/midnight-sky-of-blue-by-paul-obrien.html">Midnight Sky Of Blue</a></i></p><p style="text-align: left;"><i>Next time: <a href="http://mycodehere.blogspot.com/2021/05/dont-worry-by-paul-obrien-produced-by.html">Don't Worry</a></i></p>John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-46160545181753676022021-04-29T07:29:00.006+01:002021-05-05T13:53:34.441+01:00Midnight Sky Of Blue (Paul O'Brien)<p style="clear: both; text-align: center;"><span style="font-family: inherit;"><span color="rgba(0, 0, 0, 0.87)" face="Roboto, Noto, sans-serif" style="background-color: white; text-align: start; white-space: pre-wrap;">Song written by Paul O'Brien. Guitar & vocals by Paul O'Brien.<br /></span><span color="rgba(0, 0, 0, 0.87)" face="Roboto, Noto, sans-serif" style="background-color: white; text-align: start; white-space: pre-wrap;">Additional instrument parts written & performed by John Michael Kerr.<br /></span><span color="rgba(0, 0, 0, 0.87)" face="Roboto, Noto, sans-serif" style="background-color: white; text-align: start; white-space: pre-wrap;">Sound production & captioning by John Michael Kerr.</span></span></p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="266" src="https://www.youtube.com/embed/CaCcaQGZ8xw" width="320" youtube-src-id="CaCcaQGZ8xw"></iframe></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://youtu.be/CaCcaQGZ8xw" target="_blank">Watch on YouTube</a></div><h3 style="text-align: left;">Instrumentation</h3><p></p><ol style="text-align: left;"><li>Video - Paul's original iPhone recording</li><li>Vocals - Condenser mic through Behringer UMC204HD</li><li>Guitar - Fender semi-acoustic</li><li>Bass Guitar - Core Library Guitar Bass</li><li>Drums - 707 Core Kit</li><li>Pedal Steel Guitar, Harmony Mono - Impact Soundworks</li><li>Pedal Steel Guitar, Poly Legato - Impact Soundworks</li></ol><p></p><h3 style="text-align: left;">Production Notes</h3><p>First outing for my shiny new Pedal Steel Guitar! Actually a keyboard instrument, this offering from Impact Soundworks for the Kontakt Player is an absolute joy to play, once you master its two ingenious idiosyncrasies: overlapping notes legato, and a set of left hand keys control harmony generation.</p><p>The main design goal was to interleave the "second country voice" of the steel guitar with Paul's vocals, so that it would never get in the way. When it does play through the vocals, it does so at just one note per beat, lending a little strength to the acoustic guitar chords. On this recording I've used two PSG tracks, to take simultaneous advantage of legato and polyphony, particularly in the second half.</p><p>Had a disagreement with Paul about the quality of one note in the vocals: the word "blue" around the 3:50 mark would originally glide across four notes, and I thought the third of these was missing its mark. We agreed for me to elide it, and I have bent it like the truth.</p><h3 style="text-align: left;">Critical Response</h3><p>From Paul's Facebook page:</p><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><li>A beautiful masterpiece Paul.. so full of emotion and fabulous work by John.</li><li>Can hear this playing on the radio. Wonderful. 👏👏👏👏❤️❤️</li><li>Such a rich, evocative narrative. Lingers in the mind as all great music does 🎼</li></blockquote><p></p><div style="text-align: left;"><i>Previously: <a href="http://mycodehere.blogspot.com/2021/04/union-card-by-paul-obrien.html">Union Card</a></i></div><p style="text-align: left;"></p><i>Next time: <a href="http://mycodehere.blogspot.com/2021/04/stations-of-cross-by-paul-obrien.html">Stations Of The Cross</a></i><br /><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><p></p><div style="text-align: left;"><i><br /></i></div><p></p></blockquote>John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-77652149007150822062021-04-25T16:22:00.028+01:002021-05-11T23:11:15.321+01:00Lyric Captions<h3 style="text-align: left;">Praise the Lord and Pass the Microphone</h3><p>The Digital Audio Workstation "Ableton Live" works almost as well with full video files as it does with audio samples, but has no native facility to add lyric captions to a musical video, nor to generate the necessary caption format files to upload to video services like YouTube. Having produced a full square sixteen of Paul O'Brien's <a href="http://mycodehere.blogspot.com/2021/04/man-with-his-guitar-paul-obrien.html" target="_blank">song recordings</a>, and in the process almost accidentally created an equal number of "live" performance videos, I wanted the ability to add closed captions containing the song lyrics.</p><p>Oh, and it had to be <i>free and quick and easy</i>. There are commercial solutions available, but I didn't want to spend one cent. If you search the intertubes for "Ableton Lyrics" today, most of your results will be from American "worship sites", and a little thought will reveal the reason for that. Obviously this is a bit removed from my particular use case.</p><p>There are also automatic options. The AIs may be coming for all of our jobs, and speech recognition is certainly improving exponentially as we, erm, speak. But it's not quite there yet as far as the singing voice is concerned. I mean, just look at <a href="https://www.youtube.com/watch?v=Mz2narvUkp0" target="_blank">this effort</a>.</p><p>So it has to be <i>accurate</i> too, but within limits. We're not building a karaoke machine complete with bouncing ball. One-second accuracy should be adequate for the display of each line of the lyrics, so the listener can follow along with the performance.</p><h3 style="text-align: left;">SubRip File Format</h3><p>Most subtitles distributed on the internet, for example those ripped from movie DVDs, use a file format called - for obvious reasons - <a href="https://en.wikipedia.org/wiki/SubRip" target="_blank">SubRip</a>. Since this format is one of the two most popular currently supported for videos uploaded to YouTube or Google Drive <i>(the other being SubViewer, support for which was added later)</i> I settled on it initially for this project.</p><p>SubRip is a very simple text format: each text entity (line of dialog or song lyric) is preceded by a header containing its index (line) number and the start and stop times for its display on screen, and followed by a blank line. Obviously any text editor could be used to produce such a file, but look how fiddly it is, even after you've determined the full list of correct values, to incorporate these time marks into the format (the milliseconds separator is a comma because SubRip was originally written in France):</p><p></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiAsmhnLdUdZbASoLKB16yxeZa7b22RhDWAddAKPaZqBrNVdsrwVDuhSx6aQwnOphgRTzHeW69cGNCTe2mxl0cFvFfQlgESFLDxo1VHRAql0rwmAlIA87mA_UXDl4AWP8D-VGXrR0uMyjy/" style="margin-left: auto; margin-right: auto;"><img alt="" data-original-height="473" data-original-width="626" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiAsmhnLdUdZbASoLKB16yxeZa7b22RhDWAddAKPaZqBrNVdsrwVDuhSx6aQwnOphgRTzHeW69cGNCTe2mxl0cFvFfQlgESFLDxo1VHRAql0rwmAlIA87mA_UXDl4AWP8D-VGXrR0uMyjy/s16000/image.png" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">"Union Card" song lyrics - Copyright © 2021 Paul O'Brien</td></tr></tbody></table><p></p><p style="text-align: left;">That's the little app I ended up with, after two hours in Visual Studio - one hour for the calculation engine, and one for the user interface. <a href="https://github.com/dogbiscuituk/Lyricaps" target="_blank">Here's the code</a>, and here's how it works: </p><p></p><ol style="text-align: left;"><li>Specify the total duration of the video file, by either entering the minutes & seconds at the foot of the form, or selecting the video or associated audio file (via menu or drag & drop) and letting the code read the relevant duration from it. This feature uses the magic of <a href="https://github.com/mono/taglib-sharp" target="_blank">TagLibSharp</a>.</li><li>Either drag the lyrics file into the window, or paste the lyrics from the clipboard into the left panel, or right-click and select the lyrics text file to load it.</li><li>The captions file appears immediately in the right panel. This text may be copied to the clipboard, or saved with a menu command.</li><li>Any alterations to the duration controls, or to the contents of the left lyrics panel, are immediately reflected in the right captions panel, so it's always kept up to date, ready to be copied or saved.</li></ol><h3 style="text-align: left;">Tweaking the Timing</h3><p style="text-align: left;">Given the above description of the tool's operation, you probably guessed that it's simply counting the number of lines in the lyrics, and allocating an equal time slice to each out of the total video duration. Sure, this isn't exactly how songs work, and without some degree of tweaking, the lyrics displayed will drift into and out of synchrony with the performance - that's if you're lucky, and they ever enter synchrony at all!<br /></p><p style="text-align: left;">The one blunt weapon at our disposal is the blank line. It's usually enough to restore an adequate level of synchrony, without introducing complicated user operations, judiciously to insert one or more blank lines into the lyrics. For example, the above song <i>Union Card</i> has a classic 12-bar blues structure. If you don't know what that is, think of Led Zeppelin's <i>Rock And Roll</i>. And if you don't know what that it, get off of my lawn.</p><p style="text-align: left;"><i>Union Card</i> has a 4-bar instrumental introduction, during which we don't want any lyrics appearing, although we could use this to add the artist's name, song title, copyright notice etc. Assuming we don't want any of that, we just observe that each "line" of the song lyrics occupies two bars, and add two blank lines to the start of the lyrics to account for those four wordless bars.</p><p style="text-align: left;">Next, observe that here - as often in the 12-bar blues format - the first six bars of a verse are occupied by the first three lines of lyrics; the next two bars are instrumental; the next two hold the fourth "punch" line of the verse; and the last two bars are instrumental once again. Following our guide of one line of text equalling two bars, we see that inserting one blank line after the third and fourth line of each verse should align things very nicely. When my app sees a blank line, it just retains the previously displayed line of text, because why not? There's no advantage in blanking it. Incidentally if you do want to insert a blank line somewhere, just use a line containing only a backslash ('\') instead, and the program will oblige.</p><p style="text-align: left;">But wait - a glance at the Ableton project reveals there's actually a 5-bar outro after the 9th and final verse. If one blank line represents two bars, how can we add half a blank line to compensate for the final, odd-numbered bar? Well, we can't, at least not without complicating our beautifully simple timing scheme. Easier maybe just to add two blank lines, and truncate the final bar. Looking again at the score we see the tempo is 96bpm, the time signature is 4/4, so one bar is 4/96 minutes, or 2½ seconds. So, just clip 2½ seconds from the file duration using the up/down controls at the foot of the form.</p><h3 style="text-align: left;">Och That's Too Complicated</h3><p style="text-align: left;">Okay, how about this then. You can add a half-length line by including an initial period ('.') in the lyrics. If this appears on a line on its own, it's equivalent to a blank line, but of just half the usual duration. If it appears at the start of a lyric line, then that line will occupy just one-half of the usual time for a line; so for example, if each line of lyrics so far has occupied two bars of music, this one will occupy just one bar.</p><p style="text-align: left;">Inspired by musical notation, I'll expand this a little further. So, a line starting with two consecutive periods ('..') will occupy a further 50% of the duration of the single period line, i.e. three quarters or 75% of the usual line length; while a line starting with a colon (':') will occupy just one quarter.</p><p style="text-align: left;">These markups can alternatively be appended to the end of a line, extending its duration by the given amount, so for example a period at the end of a line causes it to be displayed for 1½ times the usual interval, a colon 1¼, and so on. With a little thought, this is almost identical in effect to putting the punctuation on its own (otherwise blank) line, after the lyric. The "almost" covers the fact these trailing marks will be ignored if leading marks are also present.</p><p style="text-align: left;"></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjNOVZRVa9Ac_0xR45Y7CurhSnQYmNz40OxpLVfMKvfnadBfYhdUHTY25zIfp-IdKzqQC4vpq_1z5WBczHr2QWPfb8_PJ2IKypqDVcfIfnpf7UldV8XbzbKN2GXbrNd5e8x7t_j4zwUO0D/" style="margin-left: auto; margin-right: auto;"><img alt="" data-original-height="328" data-original-width="388" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjNOVZRVa9Ac_0xR45Y7CurhSnQYmNz40OxpLVfMKvfnadBfYhdUHTY25zIfp-IdKzqQC4vpq_1z5WBczHr2QWPfb8_PJ2IKypqDVcfIfnpf7UldV8XbzbKN2GXbrNd5e8x7t_j4zwUO0D/s16000/image.png" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Handy reminder from the Help menu or F1 key</td></tr></tbody></table><br /><p style="text-align: left;">But what if my lyrics... end with a haunting ellipsis? If you want to incorporate leading or trailing punctuation in the displayed text of a particular lyric line, no problem, just pad the text with a leading or trailing space, so that your punctuation symbols don't actually appear right at the very start or end of the line. The program strips all leading and trailing markup and whitespace, before adding a single space for legibility to the start and end of each line, so your trailing ellipsis will be preserved without altering the line's display duration.</p><p style="text-align: left;">More general extensions are possible, but I'll reserve those until the need arises. Paul's written some songs in 3:4 time, so that shouldn't be too far in the future.</p><div><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="266" src="https://www.youtube.com/embed/VU2jUxozGJ4" width="320" youtube-src-id="VU2jUxozGJ4"></iframe></div><div class="separator" style="clear: both; text-align: center;"><br /></div><p style="text-align: left;"><a href="https://www.youtube.com/watch?v=xQh0jNI3kFs" target="_blank">Here's the final result</a>. Note how it changes text precisely on the first beat of the bar throughout. A little distracting of course when Paul's singing anticipates this point, but that's by design, and it's doing just what I asked. Precisely positioned lyric captions with the absolute minimum of time, cost, effort and fuss.</p><p></p></div>John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-69195532787682766442021-04-22T11:48:00.018+01:002021-05-08T12:35:22.862+01:00Union Card (Paul O'Brien)<p></p><div style="clear: both; text-align: center;"><span color="rgba(0, 0, 0, 0.87)" face="Roboto, Noto, sans-serif" style="background-color: white; text-align: start; white-space: pre-wrap;">Song written by Paul O'Brien. Guitar & vocals by Paul O'Brien.<br /></span><span color="rgba(0, 0, 0, 0.87)" face="Roboto, Noto, sans-serif" style="background-color: white; text-align: start; white-space: pre-wrap;">Additional instrument parts written & performed by John Michael Kerr.<br /></span><span color="rgba(0, 0, 0, 0.87)" face="Roboto, Noto, sans-serif" style="background-color: white; text-align: start; white-space: pre-wrap;">Sound production & captioning by John Michael Kerr.</span></div><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="266" src="https://www.youtube.com/embed/VU2jUxozGJ4" width="320" youtube-src-id="VU2jUxozGJ4"></iframe></div><div class="separator" style="clear: both; text-align: center;"><a href="https://youtu.be/VU2jUxozGJ4" target="_blank">Watch on YouTube</a></div><h3 style="text-align: left;">Instrumentation</h3><p></p><p style="text-align: left;"></p><ol style="text-align: left;"><li>Video - Paul's original iPhone recording</li><li>Vocals - Condenser mic through Behringer UMC204HD</li><li>Guitar - Fender semi-acoustic</li><li>Bass - Tension AAS "Pick Bass"</li><li>Drum - Mind Flux "Drums Dirty Kick"</li><li>Piano - East Village "Grand Piano"</li><li>Tubular Bell - BBC SO Discover</li><li>Spiccato - BBC SO Discover</li><li>Violins - BBC SO Discover</li><li>Horns - BBC SO Discover</li></ol><p></p><h3 style="text-align: left;">My First 10 Channel Mix</h3><p style="text-align: left;">Two weeks after filling out the requisite survey form, I receive my free licence for Spitfire Audio's "BBC Symphony Orchestra - Discovery" in the email. Under the circumstances, you might agree this track shows considerable restraint in the orchestration department.</p><p style="text-align: left;">Work begins with the now-familiar process of stitching Paul's original YouTube video download, bar by bar, then note by note, to the metronome. Then the separate audio-only file, vox in the left, gtr in the right, is also stitched in the same manual process. Of course the transients don't agree everywhere, this track being distinct from the iPhone microphone feed, so multiple passes are needed to resolve unwanted echoes and phasing effects, if the extra ambience afforded by separate audio signals is to be enjoyed. The very occasional <a href="https://en.wikipedia.org/wiki/Bob_Ross" target="_blank">Bob Ross</a> moment yields a happy accidental effect that's actually worth keeping.</p><p style="text-align: left;"><i>Update: see later articles. Phone audio has been removed at Paul's request. He is the boss! (no, not <b>that</b> Boss, though not for want of trying...)</i></p><p style="text-align: left;">Next, the just-stitched audio is split into two tracks, Vocals and Guitar, so that a Mono effect can be applied to each, isolating just their side of the stereo signal and allowing them to be treated independently in Ableton Live. At this stage the iPhone audio and the Guitar are sent off to opposite sides of the sound stage, to think about what they've done. The Vocals-only track is kept central, and as usual, rewarded for good behaviour with a -12dB A-Reverb return track input.</p><h3 style="text-align: left;">MIDIs</h3><p style="text-align: left;">Bass is the first MIDI channel to be added. First the root notes of the chords are played, then figures are written around these to complement the melody. A single, low kick drum is next, one solemn beat to the bar, with a double kick evoking a heartbeat at the end of each verse (there is no chorus). Then, a piano part to complement the guitar backing. A low tolling tonic tubular bell, bang in the middle of every third bar. Then the rest of the orchestral parts: violins alternating spiccato and long, before finally overlapping with a quartet of French horns.</p><h3 style="text-align: left;">Critical Response</h3><p style="text-align: left;">Initial comments from Paul's Facebook page:</p><p style="text-align: left;"></p><p style="text-align: left;"></p><ul style="text-align: left;"><li>Beautiful song. Beautiful melody 🎶</li><li>This is magnificent Paul... completely breathtaking! You’ve told this miner’s story and brought him to life so clearly. Everything about it is perfect.. lyrics, melody, vocal, production and the b&w video is inspired. BRAVO!!! 👏👏👏👏👏❤️❤️</li><li>Top class</li><li>Lorretta Lynn, Bryan Ferry</li></ul>Okay, the first one was actually from my sister-in-law, but still...<p></p><p style="text-align: left;"><i>Previously: <a href="http://mycodehere.blogspot.com/2021/04/wash-away-this-pain-paul-obrien.html">Wash Away This Pain</a></i></p><p style="text-align: left;"><i>Next time: <a href="http://mycodehere.blogspot.com/2021/04/midnight-sky-of-blue-by-paul-obrien.html">Midnight Sky Of Blue</a></i></p><p></p><p></p>John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-18255561137357855182021-04-18T21:59:00.007+01:002021-05-08T13:47:05.170+01:00Wash Away This Pain (Paul O'Brien)<p style="text-align: center;">
<span style="font-family: inherit;">
Song written by Paul O'Brien. Guitar & vocals by Paul O'Brien.<br />
</span>
<span style="font-family: inherit;">
Additional instrument parts written & performed by John Michael Kerr.<br />
</span>
<span style="font-family: inherit;">
Sound production & captioning by John Michael Kerr.
</span>
</p>
<div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="266" src="https://www.youtube.com/embed/ypOI3gZskQg" width="320" youtube-src-id="ypOI3gZskQg"></iframe></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://www.youtube.com/watch?v=ypOI3gZskQg" target="_blank">Watch on YouTube</a></div><p></p><h3 style="text-align: left;">Instrumentation</h3><p></p><ol style="text-align: left;"><li>Video - Paul's original iPhone recording (audio not used)</li><li>Vocals - Condenser mic through Behringer UMC204HD</li><li>Guitar - Fender semi-acoustic</li><li>Piano - Grand</li><li>Violins 1 - BBC SO Discover</li><li>Violins 2 - BBC SO Discover</li><li>Violins Spiccato - BBC SO Discover</li></ol><p></p><h3 style="text-align: left;">Production Notes</h3><p>Revisited to remove phone audio.</p><h3 style="text-align: left;">Critical Response</h3><p>From Paul's Facebook page:</p><p></p><ul style="text-align: left;"><li>WOW!!! Absolutely STUNNING!! You should SO have a record deal Paul 🙏👏👏👏❤️</li><li>get it sorted! 🙏</li></ul><p></p><p><i>Next time: <a href="http://mycodehere.blogspot.com/2021/04/union-card-by-paul-obrien.html">Union Card</a></i></p>John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-66651523750929659892021-04-03T23:40:00.044+01:002021-05-03T00:50:33.748+01:00Man With His Guitar - Paul O'Brien<h3 style="text-align: left;"><b>My Score Here</b></h3><p style="text-align: left;">I've dabbled previously with music recording in <a href="https://www.audacityteam.org/download/" target="_blank">Audacity</a>, and composing in <a href="https://musescore.org/en" target="_blank">MuseScore</a>, but if you're ever going to learn some serious Digital Audio Workstation (DAW) software, then the middle of a pandemic is a good place to start. So when last Christmas my employer, a manufacturer of computerised spectroscopes, found their Chinese order book collapsing, and started applying a LIFO protocol to HR, I found myself in the ideal position finally to scramble up the learning curves of <a href="https://www.ableton.com/en/" target="_blank">Ableton Live</a> and the Reason Studios (formerly Propellerhead) <a href="https://www.reasonstudios.com/" target="_blank">Reason</a> Rack.</p><h3 style="text-align: left;"><b>A Man, A Guitar...</b><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8O3DKC34d8zPlDmIFzudFdF-8tukdzI8-v11Gs5gD_7e72waXzUDQX2DBLP8aKG-WYiKgxZhenTf5-Y8_pSYuuCCGSCDjFQWPyrl1LVvR7JJ0HA51UCTncB3BWsqTS9DuRJOn3OviGz-k/s2048/IMG_4729.JPG" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="2048" data-original-width="1684" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8O3DKC34d8zPlDmIFzudFdF-8tukdzI8-v11Gs5gD_7e72waXzUDQX2DBLP8aKG-WYiKgxZhenTf5-Y8_pSYuuCCGSCDjFQWPyrl1LVvR7JJ0HA51UCTncB3BWsqTS9DuRJOn3OviGz-k/s320/IMG_4729.JPG" /></a></div></h3><p style="text-align: left;">Let me introduce you to my school friend Paul O'Brien, aka Man With His Guitar. Paul has been singing as a hobby ever since I've known him, stopping only for meal breaks and milkshakes, over the past 45+ years. Last May he started uploading covers to <a href="https://www.youtube.com/channel/UCbdIRuPJPTbLr95CgxwjwOQ/featured" target="_blank">his own YouTube channel</a>, soon to be followed by an increasing number of his own compositions. The format of these videos is as simple as the channel name implies: Paul and his handsome Fender guitar, sitting in front of his phone, singing a wee song.</p><p style="text-align: left;">Several factors in Paul's setup make him an ideal subject for a case study in learning music production. The most important one is that he can hold a note. I can't hold a note. Paul's talent in this area means there's always something worth preserving, enhancing, bringing out in one of his performances. Not only can he hold notes, he can let them resonate and inflect them with passion, vulnerability, irony, pathos, resignation - an impressive emotional spectrum.</p><p style="text-align: left;">Paul's choice of covers, and more importantly his own compositions, range from steady rockers to soft ballads. This gives the producer a good breadth of material to work with, trying to find the best opportunities to add subtleties <i>in the service of the song</i>, while remaining resolutely in the background.</p><h3 style="text-align: left;">Challenges?</h3><p>Of course there must be challenges, else what's to produce? Paul's performance philosophy can be summed up as "one take, warts and all". Which I find must be respected. Vocal talent can be an extremely fragile gift, and whatever gets you <i>into the zone</i>, deserves to be treated as indispensable. So if I was to produce his recordings, my source material each time would be that one video performance recorded on his phone, vocals and guitar coming together simultaneously through effectively one (stereo) microphone.</p><p>Siri can be an interfering nuisance. I read last year about one techno DJ who found his recordings unexpectedly blank, because the ghostly AI in his phone had mistaken his music for road works in the street, and helpfully removed it from the conversation. Paul didn't quite disappear from his own videos, but it can't be productive to have such unknown levels of digital processing going on during your recordings. Added to that, Paul's soundtracks often included phone alarm and watch chimes, creaking furniture, a host of extraneous noises. One take, warts and all, indeed. The skill of pasting clips between parts of a song, to cover up a dinner gong, is quickly learned.</p><h3 style="text-align: left;">Timing</h3><p>Something Paul adds to his work is an abundance of time variation. This takes many forms. Bars speed up and slow down, acquiring extra beats while a chord is hunted, a note is pecked, or a fingerpick inadvertently adds an extra digit. Emotional content contributes still more wow and flutter. Much of this is artistically valid, and in fact there have been times when I've had to give up trying to achieve any kind of synchronisation with the metronome, and just add instrumental parts playing along in variable tempo as if in live accompaniment. See <i>Forever True</i> below for an example of this.</p><p>Normally however, I'll start by <i>warping</i> the performance to first get the bars into a steady tempo, then if necessary, do the same for individual beats, strums or string picks. This makes it <i>so</i> much easier to add accompanying parts, such as drums, bass, guitars, piano, organ, strings, brass, woodwind - there are examples of all of these in the table below. And if necessary, <i>automation</i> can be used in the final stages of production, to re-apply any tempo variations felt artistically valid and vital.<br /></p><h3 style="text-align: left;">Video</h3><p>Originally I would rip the audio from the video file, then warp that bar-by-bar to obtain the first track. Both Ableton Live and Reason have excellent warping facilities, at once visual and incredibly easy to use. Once I'd added whatever additional instrument parts it called for, I'd have a candidate audio mix, which seemed to be the destination. Then one day I learned Ableton can work with video clips just as easily, and almost as comprehensively, as audio ones, and started about stitching the original video back on to the newly produced audio. After a few times working like this, I got to that "duh" moment where I realised it's possible to skip the audio rip stage altogether, and work directly with the source video file.</p><p>Note: with Ableton Live 10, video import and export was only available in the more expensive Standard and Suite editions. With the recent release of Live 11, it has now been added to the Intro edition too. Good times.</p><h3 style="text-align: left;">The Songs</h3><p>Here is a list of the songs I've produced so far. For comparison, the originals are still available on Paul's own YouTube channel and <a href="https://www.facebook.com/manwithhisguitarpaulobrien/" target="_blank">Facebook page</a>. Here I have only provided links to the final produced MP3 (audio) and MP4 (video) versions. Unless otherwise stated, these are Paul's own compositions, and his copyright.</p><h4 style="text-align: left;">March 2021</h4><p></p><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"></blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><a href="https://drive.google.com/file/d/1WZntVBMqpVpZaf4dlprxa2fHesSko2bv/view?usp=sharing" target="_blank">MP3</a> - <a href="https://drive.google.com/file/d/1P7ck9KS99_vc0JjhIyP6Y1CLChxBwHSj/view?usp=sharing" target="_blank">MP4</a> - <b>If You Could Read My Mind</b> <i>(Gordon Lightfoot cover)<br /></i><i><a href="https://drive.google.com/file/d/12RyX5-ihg63d126GLOSEtlgIuGS6RVQx/view?usp=sharing" style="font-style: normal;" target="_blank">MP3</a><span style="font-style: normal;"> - </span><a href="https://drive.google.com/file/d/1MZrdLn_SKBN0h6a0CXN320G30GwJslrh/view?usp=sharing" style="font-style: normal;" target="_blank">MP4</a><span style="font-style: normal;"> - <b>You're My Soul Concern<br /></b></span></i><a href="https://drive.google.com/file/d/1gtIlkIrFVPloELT6H5jRBMYCGM50IMMU/view?usp=sharing" target="_blank">MP3</a> - <a href="https://drive.google.com/file/d/1XoM1OtzWdy0FGEAsgpm29UvK7Fbrw69l/view?usp=sharing" target="_blank">MP4</a> - <b>Go Softly Into the Night<br /></b><a href="https://drive.google.com/file/d/1JmMp_FW-UaPBWc1xqCmpkoJ3vQd9KX6y/view?usp=sharing" target="_blank">MP3</a> - <a href="https://drive.google.com/file/d/10FvS_FEd4B4HWuGV7Hm5qGxC-wf83NGo/view?usp=sharing" target="_blank">MP4</a> - <b>When I'm Gone<br /></b><a href="https://drive.google.com/file/d/1gGOTc7m9VSsttQ6Q3MsjKgDq92YiywNw/view?usp=sharing" target="_blank">MP3</a> - <a href="https://drive.google.com/file/d/1C9HMuaD__XbBanfEgkrpSJ2gQNGK-cfs/view?usp=sharing" target="_blank">MP4</a> - <b>This Land<br /></b><a href="https://drive.google.com/file/d/1c022WfcbGsVez9CBD41FnV4cPyQXm2X4/view?usp=sharing" target="_blank">MP3</a> - <a href="https://drive.google.com/file/d/1zC6yuh_tOu7wrHBdeiaIuji1PCnchoaV/view?usp=sharing" target="_blank">MP4</a> - <b>Forever True<br /></b><a href="https://drive.google.com/file/d/1P0GG-skQnLejCpjTtkCrpSDgwdztpPVX/view?usp=sharing" target="_blank">MP3</a> - <a href="https://drive.google.com/file/d/1AMGzRXjS0E4YHxLy7wP6nmPVdc8KbiJ3/view?usp=sharing" target="_blank">MP4</a> - <b>It Has To Be Tonight<br /></b><a href="https://drive.google.com/file/d/1dkB63wlBNxG9lDKdO-CWZwN8eySskEMD/view?usp=sharing" target="_blank">MP3</a> - <a href="https://drive.google.com/file/d/1tK4Zsx2ApKVR3K6bV3sGTDwAAaHVspoS/view?usp=sharing" target="_blank">MP4</a> - <b>Case Full of Broken Dreams</b></blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"></blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"></blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"></blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"></blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"></blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"></blockquote><h4 style="text-align: left;">April 2021</h4><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"></blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><a href="https://drive.google.com/file/d/1oPi1nMqLlpVGn4K0lPb9StQ0GTCScvta/view?usp=sharing" target="_blank">MP3</a> - <a href="https://drive.google.com/file/d/1U1bXKQIWe6rTFkwVVkSMmpUg1brp2N6K/view?usp=sharing" target="_blank">MP4</a> - <b>Forever Young</b> <i>(Bob Dylan cover)<br /></i><a href="https://drive.google.com/file/d/1WYPs2broPhIqySkzwwlerhFaT_WmaApf/view?usp=sharing" target="_blank">MP3</a> - <a href="https://drive.google.com/file/d/1B8BmtjMbBV1kmI3uKmxFBYeCM1i-YHlu/view?usp=sharing" target="_blank">MP4</a> - <b>Too Much To Say<br /></b><a href="https://drive.google.com/file/d/1vHJxpfKe4eICDVBZyYEwkaSRXN26jvuv/view?usp=sharing" target="_blank">MP3</a> - <a href="https://drive.google.com/file/d/1s9B6J9ZgGZPJAryy1yCyYLXVCr9zc-QO/view?usp=sharing" target="_blank">MP4</a> - <b>Tomorrow Belongs to Yesterday</b> </blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><a href="https://drive.google.com/file/d/17gTuKNeuZCs-lJMn_ne3MaaPqwqouo7e/view?usp=sharing" target="_blank">MP3</a> - <a href="https://drive.google.com/file/d/1oa_EDZd8geLuHbZPGNosWQOMK3a9Y2Q_/view?usp=sharing" target="_blank">MP4</a> - <b>Hurt</b> <i>(Trent Reznor / Johnny Cash cover)</i></blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><a href="https://drive.google.com/file/d/1MbnO5r2XLtPuZRUmpzvkMUYc39I17x7F/view?usp=sharing" target="_blank">MP3</a> - <a href="https://drive.google.com/file/d/1CGacHQcPPJ62pNHqTTYOPQycwYWZfi0Q/view?usp=sharing" target="_blank">MP4</a> - <b>Keep A Light On In Your Heart</b></blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><a href="https://drive.google.com/file/d/1Mzbt9r5BouLXy1dpMVHWtkGJx87FuGKe/view?usp=sharing" target="_blank">MP3</a> - <a href="https://drive.google.com/file/d/1YI3iv7f5aYnG6yKjJ4goTORHAy9dBarT/view?usp=sharing" target="_blank">MP4</a> - <b>This Road I'm On</b> </blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><a href="https://drive.google.com/file/d/1F0pbw8DbNdOps9Jd2JWxXUjjmCIfy7kM/view?usp=sharing" target="_blank">MP3</a> - <a href="https://drive.google.com/file/d/1TlVHif4GxEI9z1Gf7vOtoRxV_PmcnmQB/view?usp=sharing" target="_blank">MP4</a> - <b>Wash Away This Pain</b> </blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><a href="https://drive.google.com/file/d/1DcI2yKHky5MXDr5PWwecrAuJy8VXIB03/view?usp=sharing" target="_blank">MP3</a> - <a href="https://drive.google.com/file/d/15gLyWB56O3QCEXzXJFmQYI7pvuJcX8-V/view?usp=sharing" target="_blank">MP4</a> - <b>Union Card</b> </blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><a href="https://drive.google.com/file/d/1ZuvD4enAvT0yDb8GpkkEUP_jfZvcOYoi/view?usp=sharing" target="_blank">MP3</a> - <a href="https://drive.google.com/file/d/1-iel47P1p-3iKq-EWEVucYleDgq9quW9/view?usp=sharing" target="_blank">MP4</a> - <b>Midnight Sky Of Blue</b></blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><a href="https://drive.google.com/file/d/1fmQK6PJWgJe6onUg6txYbKuFfGJ1TPcV/view?usp=sharing" target="_blank">MP3</a> - <a href="https://drive.google.com/file/d/1yBDAB2JF1efodUoRcb7UDyUQIfczoOgk/view?usp=sharing" target="_blank">MP4</a> - <b>Stations Of The Cross</b></blockquote><div><div><h3 style="text-align: left;"><br /></h3><h3 style="text-align: left;">In Future</h3><p></p><p>I've literally lost count of the number of times Paul has asked me to boost the vocals, which given the above setup, is of course almost impossible without simultaneously boosting his guitar. Not entirely impossible mind you, some gains can be made with filtering (pun intended), but it's certainly difficult and - at least on my budget - unsatisfactory (there are AI megabuck solutions offering stunning results). I think I might have talked him into giving me a secondary audio source by plugging a condenser mic and his semi-acoustic guitar into the left and right channels of a PC audio interface, and recording these into Audacity at the same time as doing his existing phone camera capture. It's still far from ideal; there will be crosstalk, particularly acoustic guitar pickup in the condenser mic, if not vice-versa; but it should be a great improvement on what we currently have.</p><p>Now, this will obviously involve more work, and seems to signal a return to the process of working with ripped audio and re-stitching with the video component at the end. This will be even more significant should I decide to use the phone audio as an additional source for its ambience, since there will be ample opportunity to introduce unwanted phasing and echo effects - the trick will be to stitch so carefully as to keep only the wanted ones. Will report back here as soon as results are available for examination.</p><h3 style="text-align: left;">Update <i>(5 Apr 2021)</i></h3><p>Success! Voice in the left channel, guitar in the centre-right, which is just about as good a separation as I was hoping for, well done us.</p><p>Stitching the warped AV components together has indeed turned out to be rather more difficult and time consuming than before, since the software couples to different sets of transients across the audio sources, due to ambience differences, microphone positions, etc. However the results are well worth the extra effort on both our parts, being at times almost as good as a full, two-take double tracking of the vocals, as well as a three-mic guitar setup - acoustic pickup, phone mic, and leakage through the cardioid condenser mic, contributing three distinct audio sources for the guitar. And when it becomes too difficult to match transients, there's always the option of dropping the audio component of the video down to a faint echo, or muting it completely.</p></div></div>John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-939364490511192422019-06-14T01:00:00.000+01:002019-06-14T09:49:02.097+01:00ToyGraf<b>Taylored Polynomials</b><br />
<br />
<i>Previously:</i><br />
<blockquote><i><a href="https://mycodehere.blogspot.com/2019/03/differentiating-fxgx.html">Differentiating f(x)^g(x)</a><br />
<a href="http://mycodehere.blogspot.com/2019/03/the-differentiator.html">The Differentiator</a><br />
<a href="http://mycodehere.blogspot.com/2019/04/graphing-derivatives.html">Graphing Derivatives</a><br />
<a href="https://mycodehere.blogspot.com/2019/04/the-complexities-of-simplifier.html">The Complexities Of The Simplifier</a><br />
<a href="http://mycodehere.blogspot.com/2019/04/formula-translation-error-function.html">Formula Translation: The Error Function</a><br />
<a href="https://mycodehere.blogspot.com/2019/04/expression-parser-part-1-of-2.html">Expression Parser</a></i></blockquote><i>Note: As always, the latest ToyGraf code can be found at <a href="https://github.com/dogbiscuituk/Sid/">https://github.com/dogbiscuituk/Sid/</a>.</i><br />
<br />
What better use for a graphing differentiator, than to generate Taylor polynomials for arbitrary functions, in the fewest possible keystrokes?<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZZnnNTzuK04nieSoe9VLjWtI_tfsI1AjikO54dJJV1hOY5ARzJEj6-a_qFGPpe-3jmrXWpsMqjIj695RNHqCbDQG_f7aYs9nDaz7sSW9I-1kExPtqUgzYwE0N-B-2E2SeF32eeLz38Pmk/s1600/Taylor1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="473" data-original-width="786" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZZnnNTzuK04nieSoe9VLjWtI_tfsI1AjikO54dJJV1hOY5ARzJEj6-a_qFGPpe-3jmrXWpsMqjIj695RNHqCbDQG_f7aYs9nDaz7sSW9I-1kExPtqUgzYwE0N-B-2E2SeF32eeLz38Pmk/s1600/Taylor1.png" /></a></div><ul><li>Run ToyGraf;</li>
<li>Press <b>F2</b> (or click the green <span style="color: #6aa84f; font-size: large;"><b>+</b></span> button) to add a new function;</li>
<li>Type <b>sin x + cos x</b></li>
</ul><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiP6tOxzxgPvFFQfWhln8aol5ZUZp2m5XM7DWBJEf4xU5VNlt4cW0__IDWiOecTXJeTGaB1KnRk2FXRC_77JFrQ_MrUok7rshVGegWrbg06MAvQgmuRV2h0mgoNtgvxtKm2WX-jgoKf143j/s1600/Taylor2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="473" data-original-width="786" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiP6tOxzxgPvFFQfWhln8aol5ZUZp2m5XM7DWBJEf4xU5VNlt4cW0__IDWiOecTXJeTGaB1KnRk2FXRC_77JFrQ_MrUok7rshVGegWrbg06MAvQgmuRV2h0mgoNtgvxtKm2WX-jgoKf143j/s1600/Taylor2.png" /></a></div><ul><li>Click the little yellow pencil in the top row to bring up the Trace Properties dialog;</li>
<li>Type <b>Alt+T</b>, or click the big friendly <i>Taylor Polynomial...</i> button;</li>
<li>Use the spin edit to select the desired degree of your polynomial, then click <b>OK</b>.</li>
</ul><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJVtXYwtLo2qF1gcm3i3RmWblF0qB8xqnQ1mFQZMQppfrxH4LDmOE0zCSEJBQixwe4HTVduaHjyZGewZnizXt4sbakZWI74EMECcMXlbv1UwMFA14QGAwOy12RLEjiRfHBC8wAXaki-qHU/s1600/Taylor3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="473" data-original-width="786" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJVtXYwtLo2qF1gcm3i3RmWblF0qB8xqnQ1mFQZMQppfrxH4LDmOE0zCSEJBQixwe4HTVduaHjyZGewZnizXt4sbakZWI74EMECcMXlbv1UwMFA14QGAwOy12RLEjiRfHBC8wAXaki-qHU/s1600/Taylor3.png" /></a></div>The Legend Box that appears will obscure most of the new graph, while the Property Table hides the rest. But that's your own fault for selecting such a ridiculously high degree of polynomial. There are both menu options and toolbar buttons to control both <i>if</i> and <i>where</i> stuff like the toolbar, legend and property table appear - have a hunt for them!<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6Lyn-aGL4VFV-NY0QN-0anmJ9SUmPC6f8gCIEZSa_O_nkRMTEqGmRedMJt6HBnPmZ9EAqaucyI5nhKq6jXBFAI34kVKabxTr8A2I6ObaKlBDwylihiWvpTQw6ze6U0UJXyEicToT5qvJw/s1600/Taylor4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6Lyn-aGL4VFV-NY0QN-0anmJ9SUmPC6f8gCIEZSa_O_nkRMTEqGmRedMJt6HBnPmZ9EAqaucyI5nhKq6jXBFAI34kVKabxTr8A2I6ObaKlBDwylihiWvpTQw6ze6U0UJXyEicToT5qvJw/s1600/Taylor4.png" data-original-width="786" data-original-height="473" /></a></div>Save some files in the ToyGraf .tgf format, then open them in Notepad++ to read what they contain. Don't worry, you won't injure yourself on the sharp brackets, they're not XML (thanks to my code reviewer and tester Stuart for convincing me JSON was the way to go).<br />
<br />
<i>Next time: Using ToyGraf to make real analysis demo animations...</i>John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-8184125113965833942019-04-18T01:43:00.000+01:002019-04-28T14:40:39.216+01:00Expression Parser<b>Modus Operandi</b><br />
<br />
<i>Previously:</i><br />
<blockquote><i><a href="https://mycodehere.blogspot.com/2019/03/differentiating-fxgx.html">Differentiating f(x)^g(x)</a><br />
<a href="http://mycodehere.blogspot.com/2019/03/the-differentiator.html">The Differentiator</a><br />
<a href="http://mycodehere.blogspot.com/2019/04/graphing-derivatives.html">Graphing Derivatives</a><br />
<a href="https://mycodehere.blogspot.com/2019/04/the-complexities-of-simplifier.html">The Complexities Of The Simplifier</a><br />
<a href="http://mycodehere.blogspot.com/2019/04/formula-translation-error-function.html">Formula Translation: The Error Function</a></i></blockquote>The Differentiator's fluent expression-building syntax is great when you have source code access, but it's easy to build a standard, recursive-descent parser for everyone else to enjoy. As you'll see below, the best thing about that is the sheer pleasure of creating your own precedence and associativity rules.<br />
<br />
Our little language of arithmetic expressions in a single real variable <i>x</i> has quite the simple grammar, which in mostly-<a href="https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form">EBNF</a>, looks like this:<br />
<pre style="background-color: white; margin: 0em; overflow: auto;"><code style="color: black; font-family: "consolas" , "courier new" , "courier" , monospace; font-size: 10pt;">
<span style="color: green;"><b>Parameter</b></span> = <span style="color: red;">"x"</span> ;
<span style="color: green;"><b>Number</b></span> = <span style="color: grey;">? \d*\.?\d*([eE][+-]?\d+)? ?</span> ;
<span style="color: green;"><b>NamedConstant</b></span> = <span style="color: red;">"e"</span> | <span style="color: red;">"π"</span> | <span style="color: red;">"pi"</span> | <span style="color: red;">"ϕ"</span> | <span style="color: red;">"phi"</span> ;
<span style="color: green;"><b>UnaryOperator</b></span> = <span style="color: red;">"+"</span> | <span style="color: red;">"-"</span> | <span style="color: red;">"!"</span> | <span style="color: red;">"~"</span> | <span style="color: red;">"√"</span> ;
<span style="color: green;"><b>BinaryOperator</b></span> = <span style="color: red;">"||"</span> | <span style="color: red;">"&&"</span> | <span style="color: red;">"|"</span> | <span style="color: red;">"&"</span> | <span style="color: red;">"="</span> | <span style="color: red;">"=="</span> | <span style="color: red;">"≠"</span> | <span style="color: red;">"<>"</span> | <span style="color: red;">"!="</span> | <span style="color: red;">"<"</span> | <span style="color: red;">">"</span> | <span style="color: red;">"≮ "</span> | <span style="color: red;">"≯ "</span> | <span style="color: red;">"<="</span> | <span style="color: red;">">="</span>
| <span style="color: red;">"+"</span> | <span style="color: red;">"-"</span> | <span style="color: red;">"*"</span> | <span style="color: red;">"/"</span> | <span style="color: red;">"^"</span> ;
<span style="color: green;"><b>Function</b></span> = <span style="color: red;">"Abs"</span> | <span style="color: red;">"Acos"</span> | <span style="color: red;">"Acosh"</span> | <span style="color: red;">"Acot"</span> | <span style="color: red;">"Acoth"</span> | <span style="color: red;">"Acsc"</span> | <span style="color: red;">"Acsch"</span> | <span style="color: red;">"Asec"</span> | <span style="color: red;">"Asech"</span> | <span style="color: red;">"Asin"</span>
| <span style="color: red;">"Asinh"</span> | <span style="color: red;">"Atan"</span> | <span style="color: red;">"Atanh"</span> | <span style="color: red;">"Ceiling"</span> | <span style="color: red;">"Cos"</span> | <span style="color: red;">"Cosh"</span> | <span style="color: red;">"Cot"</span> | <span style="color: red;">"Coth"</span> | <span style="color: red;">"Csc"</span> | <span style="color: red;">"Csch"</span>
| <span style="color: red;">"Erf"</span> | <span style="color: red;">"Exp"</span> | <span style="color: red;">"Floor"</span> | <span style="color: red;">"Ln"</span> | <span style="color: red;">"Log10"</span> | <span style="color: red;">"Round"</span> | <span style="color: red;">"Sec"</span> | <span style="color: red;">"Sech"</span> | <span style="color: red;">"Sign"</span> | <span style="color: red;">"Sin"</span>
| <span style="color: red;">"Sinh"</span> | <span style="color: red;">"Sqrt"</span> | <span style="color: red;">"Step"</span> | <span style="color: red;">"Tan"</span> | <span style="color: red;">"Tanh"</span> ;
<span style="color: green;"><b>Operand</b></span> = <span style="color: green;"><b>Parameter</b></span>
| <span style="color: green;"><b>Number</b></span>
| <span style="color: green;"><b>NamedConstant</b></span>
| <span style="color: green;"><b>Operand</b></span>, <span style="color: red;">"'"</span> <span style="color: grey;"><i>(* Form the derivative *)</i></span>
| <span style="color: red;">"("</span>, <span style="color: green;"><b>Expression</b></span>, <span style="color: red;">")"</span>
| <span style="color: green;"><b>Function</b></span>, <span style="color: green;"><b>Operand</b></span>
| <span style="color: green;"><b>UnaryOperator</b></span>, <span style="color: green;"><b>Operand</b></span> ;
<span style="color: green;"><b>Expression</b></span> = <span style="color: green;"><b>Operand</b></span>, { <span style="color: green;"><b>BinaryOperator</b></span>, <span style="color: green;"><b>Operand</b></span> }
| <span style="color: green;"><b>Expression</b></span>, <span style="color: red;">"?"</span>, <span style="color: green;"><b>Expression</b></span>, <span style="color: red;">":"</span>, <span style="color: green;"><b>Expression</b></span> ;
</code></pre><br />
The mutual recursion visible between the above <i>Expression</i> and <i>Operand</i> nonterminals is the hallmark of a classic infix-notation, parentheses-laden syntax.<br />
<br />
<b>Notes on the Grammar</b><br />
<ul><li>The Parameter 'x' is case insensitive, but will be converted to lower case during the parsing process.</li>
<li>The <i>Number</i> element is defined above using a regular expression pattern. This pattern overreaches, since for simplicity's sake, none of its character groups are mandatory. So the empty string is regarded as a valid number; as is, a single dot; or for example, the sequence '.E-'. But that's OK, as long as the tokeniser is just greedy enough, but doesn't steal characters from subsequent tokens. The actual parsing of constants will be done later by the <i>double.Parse()</i> method, so if the tokeniser passes through any such malformed number, the error will be detected.</li>
<li>A <i>Number</i> starts with either a digit or a decimal point; any preceding '+' or '-' signs will be treated as unary operators (although the parser will later collapse all such operators into a single <i>Constant</i> node).</li>
<li>The <i>NamedConstant</i> class does allow you to type the names of (some of) the few supported values as Greek letters (π, ϕ)... but you're wasting your time! This code collapses (combines) constants eagerly, and everything soon ends up as a double-precision, floating point number. NamedConstant elements are case insensitive.</li>
<li>The <i>UnaryOperator</i> elements '!' (nothing to do with factorials) and '~' both represent a <i>logical not</i> operation. The number zero is regarded as <i>false</i>, anything else as <i>true</i>. So if <i>x=0</i>, then <i>!x</i> (or equivalently <i>~x</i>) will return <i>1</i>; otherwise <i>0</i>.</li>
<li>UnaryOperator '√' performs the same function as 'Sqrt'.</li>
<li>The <i>BinaryOperator</i> elements '&' and '&&' both perform a <i>logical And</i> operation, likewise '|' and '||' a <i>logical Or</i>. They differ only in their levels of precedence: as in C#, the order of evaluation is '&', '|', '&&', '||' (obviously you can just stick to one preferred style, as the same effect can be achieved using parentheses). Similarly, '=' and '==' perform the same equality test; '≠', '<>' and '!=' the same inequality test; and so on for the comparison operators.</li>
<li>The available <i>Function</i> terminal elements are just the names of the 35 functions made available in the <a href="http://mycodehere.blogspot.com/2019/04/formula-translation-error-function.html">previous article</a>.</li>
<li><i>Function</i> names are case insensitive, but will be converted to Title Case (initial capital) during the parsing process. Notice that the <i>Function</i> syntax doesn't require parentheses; <i>abs x</i> works just as well as <i>Abs(x)</i>.</li>
<li>The apostrophe "'" is the only postfix operator in the grammar. It has the effect of differentiating the preceding <i>Operand</i>, so for example, <i>(Sin(x))'</i> evaluates to <i>Cos(x)</i>.</li>
</ul><b>Turning Japanese</b><br />
<br />
It's always at this point, turning from the syntax diagram to the semantics of coding, that I inevitably get seduced by this cool feature, first encountered in my first couple of pocket computers: the Sharp <a href="https://en.wikipedia.org/wiki/Sharp_PC-1211">PC-1211</a> and <a href="https://en.wikipedia.org/wiki/Sharp_PC-1500">PC-1500</a>. The cool feature is <i>implied products</i>. TL;DR: every memory category was in such short supply back then <i>(see: millennium bug)</i>, even BASIC variables were meted out individually, and their names were <i>A..Z</i>. The unexpected benefit of this rationing was that products of variables (variables which individually could only have single-character names like <i>A</i> or <i>B</i>) could now be expressed unambiguously in the form <i>AB</i>, saving a whole byte by omitting the multiplication symbol.<br />
<br />
At first glimpse this implied multiplication seems no big deal, but wait. Does it necessarily have the same associativity and precedence as normal multiplication? If so, then<br />
<blockquote><i>A/BC</i> would be parsed as <i>(A/B)*C = (A*C)/B</i>.<br />
</blockquote>But this seems wrong! The expression looks so much more like a quantity <i>A</i> being divided by a quantity <i>BC</i>, in other words,<br />
<blockquote><i>A/BC</i> = <i>A/(B*C)</i>.<br />
</blockquote>Now the <i>C</i> has landed below the line; before, it was above. Which is correct?<br />
<br />
Utility is monarch, and the solution turns out to be the introduction of a new kind of <i>implied multiplication</i> with a higher precedence than the usual one. By analogy: the associativity of '^' was once settled to be <i>right</i>, unlike its additive and multiplicative colleagues which all tended <i>left</i>.<br />
<blockquote>So, while subtraction goes like this: <i>A-B-C = (A-B)-C = A-(B+C)</i>,<br />
<br />
by contrast exponentiation does this: <i>A^B^C = A^(B^C)</i><br />
<br />
rather than the much less useful <i>(A^B)^C</i>,<br />
</blockquote>because the real action's in the exponent. In the case of the implicit multiplier, the new operator was given a higher precedence than its peers, so for example<br />
<blockquote><i>A^BC = A^(B*C)</i>, rather than <i>(A^B)*C</i>.<br />
</blockquote>In this way multiplication, if it just remain hidden, can leapfrog other more powerful operators, perhaps even unary operators, perhaps even function calls:<br />
<blockquote><i>sin AB = sin (A * B)</i>, compared to <i>sin A * B = (sin A) * B</i>.<br />
</blockquote><br />
<b>White Feather</b><br />
<br />
So why did I hesitate to implement such an operator in <i>Parser</i>?<br />
<br />
A recurring subject in the C# language designers' blogs and comment responses is <i>cost</i>. Nothing comes for free! There's always at least a test budget to consider, and almost always, myriad further hidden design and development costs. Implied products are almost <i>too</i> attractive a feature. Sure, <i>(x+2)(x-2)</i> and its ilk are compelling, but to get the full story, you must go back to the language's grammar definition and look at all newly emerging cases.<br />
<br />
What about<br />
<blockquote><i>2 sin x cos x</i><br />
</blockquote>for example. The familiar <i>sine of a double angle</i> formula, obviously and unambiguously intended to be read as<br />
<blockquote><i>2*sin(x)*cos(x)</i>.<br />
</blockquote>But should the precedence of "implicit multiply" exceed even unary and function calls, the interpretation becomes<br />
<blockquote><i>2*sin(x*cos(x))</i>,<br />
</blockquote>which, simply, ouch.<br />
<br />
Then again, why all this detail, if I never intended to implement such an operator? <i>Because I knew I would</i>. I knew that about myself, and so wanted all this background material to be available for review, when I finally revisited the code to smoosh it up a bit. Also, the very inevitability of that revisit is why I made this code as <i>clean</i> and <i>readily understandable</i> as I could. Part of this process is of course eliminating all but the most vital of comments from the source; hence their home in this article.<br />
<br />
That <i>Precedence</i> enumeration was a proper godsend when smooshing time arrived! Anyway, here comes the parser code:<br />
<pre style="margin:0em; overflow:auto; background-color:#ffffff;"><code style="font-family:Consolas,"Courier New",Courier,Monospace; font-size:10pt; color:#000000;">
<span style="color:#0000ff;">namespace</span> FormulaBuilder
{
<span style="color:#0000ff;">using</span> System;
<span style="color:#0000ff;">using</span> System.Collections.Generic;
<span style="color:#0000ff;">using</span> System.Linq;
<span style="color:#0000ff;">using</span> System.Linq.Expressions;
<span style="color:#0000ff;">using</span> System.Text.RegularExpressions;
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">class</span> Parser
{
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">enum</span> Precedence
{
RightParenthesis,
Additive,
Multiplicative,
Exponential,
Functional,
ImpliedProduct,
SuperscriptPower
}
<span style="color:#0000ff;">const</span> <span style="color:#0000ff;">char</span>
SquareRoot = <span style="color:#a31515;">'√'</span>;
<span style="color:#0000ff;">const</span> <span style="color:#0000ff;">string</span>
ImpliedProduct = <span style="color:#a31515;">"i*"</span>,
SuperscriptPower = <span style="color:#a31515;">"s^"</span>,
UnaryMinus = <span style="color:#a31515;">"u-"</span>,
UnaryPlus = <span style="color:#a31515;">"u+"</span>;
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">string</span> Formula;
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">int</span> Index;
<span style="color:#0000ff;">private</span> Stack<Expression> Operands;
<span style="color:#0000ff;">private</span> Stack<<span style="color:#0000ff;">string</span>> Operators;
<span style="color:#0000ff;">public</span> Expression Parse(<span style="color:#0000ff;">string</span> formula)
{
Formula = formula;
Index = 0;
Operands = <span style="color:#0000ff;">new</span> Stack<Expression>();
Operators = <span style="color:#0000ff;">new</span> Stack<<span style="color:#0000ff;">string</span>>(<span style="color:#0000ff;">new</span>[] { <span style="color:#a31515;">"("</span> });
ParseExpression();
<span style="color:#0000ff;">if</span> (Operators.Any())
<span style="color:#0000ff;">throw</span> <span style="color:#0000ff;">new</span> FormatException(
$<span style="color:#a31515;">"Unexpected end of expression, input='{Formula}'"</span>);
<span style="color:#0000ff;">return</span> Operands.Peek();
}
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">bool</span> TryParse(<span style="color:#0000ff;">string</span> formula, <span style="color:#0000ff;">out</span> <span style="color:#0000ff;">object</span> result)
{
<span style="color:#0000ff;">try</span>
{
result = Parse(formula);
<span style="color:#0000ff;">return</span> <span style="color:#0000ff;">true</span>;
}
<span style="color:#0000ff;">catch</span> (Exception e)
{
result = e.Message;
<span style="color:#0000ff;">return</span> <span style="color:#0000ff;">false</span>;
}
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> GetNamedConstantValue(<span style="color:#0000ff;">string</span> constant)
{
<span style="color:#0000ff;">switch</span> (constant.ToLower())
{
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">"e"</span>:
<span style="color:#0000ff;">return</span> Math.E;
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">"π"</span>:
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">"pi"</span>:
<span style="color:#0000ff;">return</span> Math.PI;
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">"ϕ"</span>:
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">"phi"</span>:
<span style="color:#0000ff;">return</span> (1 + Math.Sqrt(5)) / 2;
}
<span style="color:#0000ff;">return</span> 0;
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">static</span> ExpressionType GetExpressionType(<span style="color:#0000ff;">string</span> op)
{
<span style="color:#0000ff;">switch</span> (op)
{
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">"+"</span>:
<span style="color:#0000ff;">return</span> ExpressionType.Add;
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">"-"</span>:
<span style="color:#0000ff;">return</span> ExpressionType.Subtract;
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">"*"</span>:
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">"i*"</span>:
<span style="color:#0000ff;">return</span> ExpressionType.Multiply;
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">"/"</span>:
<span style="color:#0000ff;">return</span> ExpressionType.Divide;
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">"^"</span>:
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">"s^"</span>:
<span style="color:#0000ff;">return</span> ExpressionType.Power;
<span style="color:#0000ff;">case</span> UnaryPlus:
<span style="color:#0000ff;">return</span> ExpressionType.UnaryPlus;
<span style="color:#0000ff;">case</span> UnaryMinus:
<span style="color:#0000ff;">return</span> ExpressionType.Negate;
}
<span style="color:#0000ff;">throw</span> <span style="color:#0000ff;">new</span> FormatException();
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">static</span> Precedence GetPrecedence(<span style="color:#0000ff;">string</span> op)
{
<span style="color:#0000ff;">switch</span> (op)
{
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">")"</span>:
<span style="color:#0000ff;">return</span> Precedence.RightParenthesis;
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">"+"</span>:
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">"-"</span>:
<span style="color:#0000ff;">return</span> Precedence.Additive;
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">"*"</span>:
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">"/"</span>:
<span style="color:#0000ff;">return</span> Precedence.Multiplicative;
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">"^"</span>:
<span style="color:#0000ff;">return</span> Precedence.Exponential;
<span style="color:#0000ff;">case</span> ImpliedProduct:
<span style="color:#0000ff;">return</span> Precedence.ImpliedProduct;
<span style="color:#0000ff;">case</span> SuperscriptPower:
<span style="color:#0000ff;">return</span> Precedence.SuperscriptPower;
}
<span style="color:#0000ff;">return</span> Precedence.Functional;
}
<span style="color:#0000ff;">private</span> Expression MakeBinary(<span style="color:#0000ff;">string</span> op, Expression lhs, Expression rhs) =>
Expression.MakeBinary(GetExpressionType(op), lhs, rhs);
<span style="color:#0000ff;">private</span> Expression MakeFunction(<span style="color:#0000ff;">string</span> f, Expression operand)
{
f = $<span style="color:#a31515;">"{char.ToUpper(f[0])}{f.ToLower().Substring(1)}"</span>;
<span style="color:#0000ff;">var</span> result = Expressions.Function(f, operand);
<span style="color:#0000ff;">if</span> (operand <span style="color:#0000ff;">is</span> ConstantExpression c)
<span style="color:#0000ff;">return</span> result.AsDouble((<span style="color:#0000ff;">double</span>)c.Value).Constant();
<span style="color:#0000ff;">return</span> result;
}
<span style="color:#0000ff;">private</span> Expression MakeUnary(<span style="color:#0000ff;">string</span> op, Expression operand)
{
<span style="color:#0000ff;">if</span> (operand <span style="color:#0000ff;">is</span> ConstantExpression c)
<span style="color:#0000ff;">switch</span> (op)
{
<span style="color:#0000ff;">case</span> UnaryPlus: <span style="color:#0000ff;">return</span> operand;
<span style="color:#0000ff;">case</span> UnaryMinus: <span style="color:#0000ff;">return</span> (-(<span style="color:#0000ff;">double</span>)c.Value).Constant();
}
<span style="color:#0000ff;">return</span> Expression.MakeUnary(GetExpressionType(op), operand, <span style="color:#0000ff;">null</span>);
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">string</span> MatchFunction() => MatchRegex(@<span style="color:#a31515;">"^[\p{Lu}\p{Ll}\d]+"</span>).ToLower();
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">string</span> MatchNumber() => MatchRegex(@<span style="color:#a31515;">"^\d*\.?\d*([eE][+-]?\d+)?"</span>);
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">string</span> MatchRegex(<span style="color:#0000ff;">string</span> pattern)
{
<span style="color:#0000ff;">var</span> match = Regex.Match(Formula.Substring(Index), pattern);
<span style="color:#0000ff;">return</span> Formula.Substring(Index + match.Index, match.Length);
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">string</span> MatchSubscript() => MatchRegex($<span style="color:#a31515;">"^[{StringUtilities.Subscripts}]+"</span>);
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">string</span> MatchSuperscript() => MatchRegex($<span style="color:#a31515;">"^[{StringUtilities.Superscripts}]+"</span>);
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">char</span> NextChar()
{
<span style="color:#0000ff;">var</span> count = Formula.Length;
<span style="color:#0000ff;">while</span> (Index < count && Formula[Index] == <span style="color:#a31515;">' '</span>)
Index++;
<span style="color:#0000ff;">return</span> Index < count ? Formula[Index] : Index == count ? <span style="color:#a31515;">')'</span> : <span style="color:#a31515;">'$'</span>;
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">string</span> NextToken()
{
<span style="color:#0000ff;">var</span> nextChar = NextChar();
<span style="color:#0000ff;">switch</span> (nextChar)
{
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">'+'</span>:
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">'-'</span>:
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">'*'</span>:
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">'/'</span>:
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">'^'</span>:
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">'('</span>:
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">')'</span>:
<span style="color:#0000ff;">case</span> SquareRoot:
<span style="color:#0000ff;">return</span> nextChar.ToString();
<span style="color:#0000ff;">case</span> <span style="color:#0000ff;">char</span> c when <span style="color:#0000ff;">char</span>.IsDigit(c):
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">'.'</span>:
<span style="color:#0000ff;">return</span> MatchNumber();
<span style="color:#0000ff;">case</span> <span style="color:#0000ff;">char</span> c when c.IsSuperscript():
<span style="color:#0000ff;">return</span> MatchSuperscript();
<span style="color:#0000ff;">case</span> <span style="color:#0000ff;">char</span> c when <span style="color:#0000ff;">char</span>.IsLetter(c):
<span style="color:#0000ff;">return</span> MatchFunction();
}
<span style="color:#0000ff;">throw</span> <span style="color:#0000ff;">new</span> FormatException(
$<span style="color:#a31515;">"Unexpected character '{nextChar}', input='{Formula}', index={Index}"</span>);
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> ParseExpression()
{
<span style="color:#0000ff;">do</span>
{
ParseOperand();
<span style="color:#0000ff;">var</span> op = NextChar();
<span style="color:#0000ff;">switch</span> (op)
{
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">'+'</span>:
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">'-'</span>:
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">'*'</span>:
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">'/'</span>:
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">'^'</span>:
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">')'</span>:
ParseOperator(op.ToString());
<span style="color:#0000ff;">if</span> (op == <span style="color:#a31515;">')'</span>)
<span style="color:#0000ff;">return</span>;
<span style="color:#0000ff;">break</span>;
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">'$'</span> when Index == Formula.Length + 2: <span style="color:#008000;">// End of input (normal)</span>
<span style="color:#0000ff;">return</span>;
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">'$'</span> when Index < Formula.Length + 2: <span style="color:#008000;">// End of input (unexpected)</span>
<span style="color:#0000ff;">throw</span> <span style="color:#0000ff;">new</span> FormatException(
$<span style="color:#a31515;">"Unexpected end of text, input='{Formula}', index={Index}"</span>);
<span style="color:#0000ff;">case</span> <span style="color:#0000ff;">char</span> c when c.IsSuperscript():
ParseOperator(SuperscriptPower); <span style="color:#008000;">//</span>
<span style="color:#0000ff;">break</span>;
<span style="color:#0000ff;">default</span>:
<span style="color:#0000ff;">if</span> (Operands.Peek() <span style="color:#0000ff;">is</span> ConstantExpression)
{
ParseOperator(ImpliedProduct); <span style="color:#008000;">// Implied multiplication</span>
<span style="color:#0000ff;">break</span>;
}
<span style="color:#0000ff;">throw</span> <span style="color:#0000ff;">new</span> FormatException(
$<span style="color:#a31515;">"Unexpected character '{op}', input='{Formula}', index={Index}"</span>);
}
}
<span style="color:#0000ff;">while</span> (<span style="color:#0000ff;">true</span>);
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> ParseFunction(<span style="color:#0000ff;">string</span> function)
{
Operators.Push(function);
ReadPast(function);
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">bool</span> ParseNamedConstant(<span style="color:#0000ff;">string</span> constant)
{
<span style="color:#0000ff;">var</span> value = GetNamedConstantValue(constant);
<span style="color:#0000ff;">if</span> (value == 0)
<span style="color:#0000ff;">return</span> <span style="color:#0000ff;">false</span>;
Operands.Push(value.Constant());
ReadPast(constant);
<span style="color:#0000ff;">return</span> <span style="color:#0000ff;">true</span>;
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> ParseNumber(<span style="color:#0000ff;">string</span> number)
{
<span style="color:#0000ff;">try</span>
{
Operands.Push(<span style="color:#0000ff;">double</span>.Parse(number).Constant());
}
<span style="color:#0000ff;">catch</span> (FormatException)
{
<span style="color:#0000ff;">throw</span> <span style="color:#0000ff;">new</span> FormatException(
$<span style="color:#a31515;">"Invalid number format '{number}', input='{Formula}', index={Index}"</span>);
}
<span style="color:#0000ff;">catch</span> (OverflowException)
{
<span style="color:#0000ff;">throw</span> <span style="color:#0000ff;">new</span> FormatException(
$<span style="color:#a31515;">"Numerical overflow '{number}', input='{Formula}', index={Index}"</span>);
}
ReadPast(number);
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> ParseOperand()
{
<span style="color:#0000ff;">var</span> token = NextToken();
<span style="color:#0000ff;">switch</span> (<span style="color:#0000ff;">char</span>.ToLower(token[0]))
{
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">'x'</span> when token.Length == 1:
ParseParameter(token);
<span style="color:#0000ff;">break</span>;
<span style="color:#0000ff;">case</span> <span style="color:#0000ff;">char</span> c when <span style="color:#0000ff;">char</span>.IsDigit(c):
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">'.'</span>:
ParseNumber(token);
<span style="color:#0000ff;">break</span>;
<span style="color:#0000ff;">case</span> <span style="color:#0000ff;">char</span> c when c.IsSuperscript():
ParseSuperscript(token);
<span style="color:#0000ff;">break</span>;
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">'('</span>:
Operators.Push(token);
ReadPast(token);
ParseExpression();
<span style="color:#0000ff;">break</span>;
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">'+'</span>:
<span style="color:#0000ff;">case</span> <span style="color:#a31515;">'-'</span>:
ParseUnary(token);
ParseOperand();
<span style="color:#0000ff;">break</span>;
<span style="color:#0000ff;">case</span> <span style="color:#0000ff;">char</span> c when <span style="color:#0000ff;">char</span>.IsLetter(c):
<span style="color:#0000ff;">if</span> (!ParseNamedConstant(token))
{
ParseFunction(token);
ParseOperand();
}
<span style="color:#0000ff;">break</span>;
<span style="color:#0000ff;">case</span> SquareRoot:
ParseSquareRoot();
ParseOperand();
<span style="color:#0000ff;">break</span>;
<span style="color:#0000ff;">default</span>:
<span style="color:#0000ff;">throw</span> <span style="color:#0000ff;">new</span> FormatException(
$<span style="color:#a31515;">"Missing operand, input='{Formula}', index={Index}"</span>);
}
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> ParseOperator(<span style="color:#0000ff;">string</span> op)
{
<span style="color:#0000ff;">do</span>
{
<span style="color:#0000ff;">var</span> ours = GetPrecedence(op);
<span style="color:#0000ff;">var</span> pending = Operators.Peek();
<span style="color:#0000ff;">if</span> (pending == <span style="color:#a31515;">"("</span>)
<span style="color:#0000ff;">break</span>;
<span style="color:#0000ff;">var</span> theirs = GetPrecedence(pending);
<span style="color:#008000;">// Operator '^' is right associative: a^b^c = a^(b^c).</span>
<span style="color:#0000ff;">if</span> (theirs > ours || theirs == ours && op != <span style="color:#a31515;">"^"</span>)
{
Operators.Pop();
<span style="color:#0000ff;">var</span> operand = Operands.Pop();
<span style="color:#0000ff;">if</span> (theirs == Precedence.Functional)
<span style="color:#0000ff;">switch</span> (pending)
{
<span style="color:#0000ff;">case</span> UnaryPlus:
<span style="color:#0000ff;">break</span>;
<span style="color:#0000ff;">case</span> UnaryMinus:
operand = MakeUnary(pending, operand);
<span style="color:#0000ff;">break</span>;
<span style="color:#0000ff;">default</span>:
<span style="color:#0000ff;">try</span>
{
operand = MakeFunction(pending, operand);
}
<span style="color:#0000ff;">catch</span> (ArgumentNullException)
{
<span style="color:#0000ff;">throw</span> <span style="color:#0000ff;">new</span> FormatException(
$<span style="color:#a31515;">"Unrecognised function '{pending}', input='{Formula}'"</span>);
};
<span style="color:#0000ff;">break</span>;
}
<span style="color:#0000ff;">else</span>
{
<span style="color:#0000ff;">if</span> (pending == ImpliedProduct)
pending = <span style="color:#a31515;">"*"</span>;
operand = MakeBinary(pending, Operands.Pop(), operand);
}
Operands.Push(operand);
}
<span style="color:#0000ff;">else</span>
<span style="color:#0000ff;">break</span>;
}
<span style="color:#0000ff;">while</span> (<span style="color:#0000ff;">true</span>);
<span style="color:#0000ff;">if</span> (op == <span style="color:#a31515;">")"</span>)
Operators.Pop();
<span style="color:#0000ff;">else</span>
Operators.Push(op);
<span style="color:#0000ff;">if</span> (op != ImpliedProduct && op != SuperscriptPower)
ReadPast(op);
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> ParseParameter(<span style="color:#0000ff;">string</span> token)
{
Operands.Push(Expressions.x);
ReadPast(token);
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> ParseSquareRoot()
{
Operators.Push(<span style="color:#a31515;">"Sqrt"</span>);
ReadPast(SquareRoot.ToString());
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> ParseSuperscript(<span style="color:#0000ff;">string</span> superscript)
{
Operands.Push(<span style="color:#0000ff;">new</span> Parser().Parse(superscript.SuperscriptToNormal()));
ReadPast(superscript);
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> ParseUnary(<span style="color:#0000ff;">string</span> unary)
{
Operators.Push($<span style="color:#a31515;">"u{unary}"</span>);
ReadPast(unary);
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> ReadPast(<span style="color:#0000ff;">string</span> token) => Index += token.Length;
}
}
</code></pre><br />
<b>Going Wild!</b><br />
<br />
It's a fair cop. If you've read through the source code above, you'll have spotted vestiges of not only implied multiplication, but also (implied) superscript exponentiation. Like a lot of subsequent developments, they're not visible in the grammar spec at the head of this article. But to start with, you can omit the multiplication sign after any constant number, and before the next operand, which may be <i>"x"</i>, or a function, or "(" introducing a nested expression, or even another number (separated from the first by at least one space).<br />
<br />
And what of the over-eager precedence problem? This new <i>implicit multiplication</i> operator needs its own, new precedence level, <i>Implied</i>, above <i>Unary</i>, so stuff like <i>sin 2x</i> works properly. But that's also higher than <i>Exponential</i> precedence, so <i>2x^3</i> becomes <i>(2*x)^3</i>, whereas in the arena of polynomials we'd rather see <i>2*(x^3)</i>. This can be fixed, luckily, quickly, and quirkily: introduce Unicode superscripts, and give them their own <i>implicit exponentiation</i> operator! This has the highest precedence of all, and causes <i>2x³</i> to get dressed as the wanted <i>2*(x^3)</i>.<br />
<br />
Just how far can we go with superscripts? Unicode characters include numbers and common mathematical symbols ⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾, a full superscript Latin lowercase alphabet ᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖʳˢᵗᵘᵛʷˣʸᶻ (well, full except for <i>'q'</i>), a limited uppercase Latin alphabet ᴬᴮᴰᴱᴳᴴᴵᴶᴷᴸᴹᴺᴼᴾᴿᵀᵁⱽᵂ, and some Greek letters ᵅᵝᵞᵟᵋᶿᶥᶲᵠᵡ. These limitations already suggest we should use some other type of markup language - remember, this series started with <a href="https://mycodehere.blogspot.com/2019/03/differentiating-fxgx.html">a look at LaTeX</a> - and the fact that even these available glyphs come from different ranges, so depending on your typeface can vary in size and position, surely cements that opinion. But until we make that switch, we can enjoy the novelty of chucking expressions like <i>x⁴-4x³+6x²-4x+1</i>, or <i>eᶜᵒˢ⁽ˣ⁾</i>, into our mathematical stockpot.<br />
<br />
You don't have superscripts on your keyboard? Sorry, I'm just a code monkey. That's definitely a hardware problem.<br />
<pre style="margin:0em; overflow:auto; background-color:#ffffff;"><code style="font-family:Consolas,"Courier New",Courier,Monospace; font-size:10pt; color:#000000;">
<span style="color:#0000ff;">namespace</span> FormulaBuilder
{
<span style="color:#0000ff;">using</span> System;
<span style="color:#0000ff;">using</span> System.Text;
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">class</span> StringUtilities
{
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">const</span> <span style="color:#0000ff;">string</span>
Subscripts = <span style="color:#a31515;">"₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎ₐₑₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓᵦᵧᵨᵩᵪ"</span>,
Transcripts = <span style="color:#a31515;">"0123456789+-=()aehijklmnoprstuvxβγρψχbcdfgwyzABDEGHIJKLMNOPRTUVWαδεθιφ"</span>,
Superscripts = <span style="color:#a31515;">"⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾ᵃᵉʰⁱʲᵏˡᵐⁿᵒᵖʳˢᵗᵘᵛˣᵝᵞρᵠᵡᵇᶜᵈᶠᵍʷʸᶻᴬᴮᴰᴱᴳᴴᴵᴶᴷᴸᴹᴺᴼᴾᴿᵀᵁⱽᵂᵅᵟᵋᶿᶥᶲ"</span>;
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">bool</span> IsSubscript(<span style="color:#0000ff;">this</span> <span style="color:#0000ff;">char</span> c) => Subscripts.IndexOf(c) >= 0;
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">bool</span> IsSuperscript(<span style="color:#0000ff;">this</span> <span style="color:#0000ff;">char</span> c) => Superscripts.IndexOf(c) >= 0;
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">string</span> NormalToSubscript(<span style="color:#0000ff;">this</span> <span style="color:#0000ff;">string</span> number) => Transcribe(number, Transcripts, Subscripts);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">string</span> NormalToSuperscript(<span style="color:#0000ff;">this</span> <span style="color:#0000ff;">string</span> number) => Transcribe(number, Transcripts, Superscripts);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">string</span> SubscriptToNormal(<span style="color:#0000ff;">this</span> <span style="color:#0000ff;">string</span> number) => Transcribe(number, Subscripts, Transcripts);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">string</span> SubscriptToSuperscript(<span style="color:#0000ff;">this</span> <span style="color:#0000ff;">string</span> number) => Transcribe(number, Subscripts, Superscripts);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">string</span> SuperscriptToNormal(<span style="color:#0000ff;">this</span> <span style="color:#0000ff;">string</span> number) => Transcribe(number, Superscripts, Transcripts);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">string</span> SuperscriptToSubscript(<span style="color:#0000ff;">this</span> <span style="color:#0000ff;">string</span> number) => Transcribe(number, Superscripts, Subscripts);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">string</span> Transcribe(<span style="color:#0000ff;">this</span> <span style="color:#0000ff;">string</span> number, <span style="color:#0000ff;">string</span> source, <span style="color:#0000ff;">string</span> target)
{
<span style="color:#0000ff;">var</span> stringBuilder = <span style="color:#0000ff;">new</span> StringBuilder(number);
<span style="color:#0000ff;">for</span> (<span style="color:#0000ff;">var</span> index = 0; index < Math.Min(source.Length, target.Length); index++)
stringBuilder.Replace(source[index], target[index]);
<span style="color:#0000ff;">return</span> stringBuilder.ToString();
}
}
}
</code></pre><br />
<b>But Does It Parse The Test?</b><br />
<br />
Obviously we need some tests for all this, and in fact the following lines have just appeared in <i>Expressions.Tests.cs</i>. Despite giving the visual appearance of comparing one string with another one rather like it, these tests actually embody quite the round trip. The left string is pumped into the parser, generating an expression tree as its output. This is then fed through a stage 2 amplifier, namely the <i>AsString()</i> extension method, where we hope to see a similarly structured string emerge, but sporting at the most <i>binary</i> operations, and all the other minor effects and transformations expected.<br />
<pre style="margin:0em; overflow:auto; background-color:#ffffff;"><code style="font-family:Consolas,"Courier New",Courier,Monospace; font-size:10pt; color:#000000;">
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">void</span> TestParse(<span style="color:#0000ff;">string</span> input, <span style="color:#0000ff;">string</span> output) =>
Check(output, <span style="color:#0000ff;">new</span> Parser().Parse(input).AsString());
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">void</span> TestParseFail(<span style="color:#0000ff;">string</span> input, <span style="color:#0000ff;">string</span> error)
{
<span style="color:#0000ff;">try</span>
{
<span style="color:#0000ff;">new</span> Parser().Parse(input);
Check(<span style="color:#a31515;">"Exception thrown"</span>, <span style="color:#a31515;">"no Exception thrown"</span>);
}
<span style="color:#0000ff;">catch</span> (Exception ex)
{
Check(error, ex.Message);
}
}
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">void</span> TestParser()
{
TestParserFailure();
TestParserSuccess();
}
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">void</span> TestParserFailure()
{
TestParseFail(<span style="color:#a31515;">"x~2"</span>, <span style="color:#a31515;">"Unexpected character '~', input='x~2', index=1"</span>);
TestParseFail(<span style="color:#a31515;">"x+123,456"</span>, <span style="color:#a31515;">"Unexpected character ',', input='x+123,456', index=5"</span>);
TestParseFail(<span style="color:#a31515;">"x+1$2"</span>, <span style="color:#a31515;">"Unexpected end of text, input='x+1$2', index=3"</span>);
TestParseFail(<span style="color:#a31515;">"x+1e999"</span>, <span style="color:#a31515;">"Numerical overflow '1e999', input='x+1e999', index=2"</span>);
TestParseFail(<span style="color:#a31515;">"x+."</span>, <span style="color:#a31515;">"Invalid number format '.', input='x+.', index=2"</span>);
TestParseFail(<span style="color:#a31515;">"x+.E+1"</span>, <span style="color:#a31515;">"Invalid number format '.E+1', input='x+.E+1', index=2"</span>);
TestParseFail(<span style="color:#a31515;">"x+"</span>, <span style="color:#a31515;">"Missing operand, input='x+', index=2"</span>);
TestParseFail(<span style="color:#a31515;">"(x+(2*(x+(3)))"</span>, <span style="color:#a31515;">"Unexpected end of text, input='(x+(2*(x+(3)))', index=15"</span>);
}
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">void</span> TestParserSuccess()
{
TestParse(<span style="color:#a31515;">"0"</span>, <span style="color:#a31515;">"0"</span>);
TestParse(<span style="color:#a31515;">"e"</span>, <span style="color:#a31515;">"2.71828182845905"</span>);
TestParse(<span style="color:#a31515;">"π"</span>, <span style="color:#a31515;">"3.14159265358979"</span>);
TestParse(<span style="color:#a31515;">"pi"</span>, <span style="color:#a31515;">"3.14159265358979"</span>);
TestParse(<span style="color:#a31515;">"ϕ"</span>, <span style="color:#a31515;">"1.61803398874989"</span>);
TestParse(<span style="color:#a31515;">"phi"</span>, <span style="color:#a31515;">"1.61803398874989"</span>);
TestParse(<span style="color:#a31515;">"X+1"</span>, <span style="color:#a31515;">"(x+1)"</span>);
TestParse(<span style="color:#a31515;">"((x+1))"</span>, <span style="color:#a31515;">"(x+1)"</span>);
TestParse(<span style="color:#a31515;">"x+x*x^x/x-x"</span>, <span style="color:#a31515;">"((x+((x*(x^x))/x))-x)"</span>);
TestParse(<span style="color:#a31515;">"3*x+x/5"</span>, <span style="color:#a31515;">"((3*x)+(x/5))"</span>);
TestParse(<span style="color:#a31515;">"x-2-x"</span>, <span style="color:#a31515;">"((x-2)-x)"</span>); <span style="color:#008000;">// Subtraction is left associative</span>
TestParse(<span style="color:#a31515;">"x^2^x"</span>, <span style="color:#a31515;">"(x^(2^x))"</span>); <span style="color:#008000;">// Exponentiation is right associative</span>
TestParse(<span style="color:#a31515;">"(x-3)*(5-x)/10"</span>, <span style="color:#a31515;">"(((x-3)*(5-x))/10)"</span>);
TestParse(<span style="color:#a31515;">"sin x * cos x"</span>, <span style="color:#a31515;">"(Sin(x)*Cos(x))"</span>);
TestParse(<span style="color:#a31515;">"Ln(sin x - tanh(x)) - 1"</span>, <span style="color:#a31515;">"(Ln((Sin(x)-Tanh(x)))-1)"</span>);
TestParse(<span style="color:#a31515;">"Abs Cos Sin Tan 1.5"</span>, <span style="color:#a31515;">"0.540839774154307"</span>);
TestParse(<span style="color:#a31515;">"Abs Cos Sin Tan (x/2)"</span>, <span style="color:#a31515;">"Abs(Cos(Sin(Tan((x/2)))))"</span>);
TestParse(<span style="color:#a31515;">"2*(sin x + cos x ^ 3 - tan(x^3))/3"</span>, <span style="color:#a31515;">"((2*((Sin(x)+(Cos(x)^3))-Tan((x^3))))/3)"</span>);
TestParse(<span style="color:#a31515;">"2*(x+3*(x-4^x)-5)/6"</span>, <span style="color:#a31515;">"((2*((x+(3*(x-(4^x))))-5))/6)"</span>);
TestParse(<span style="color:#a31515;">"1/5x"</span>, <span style="color:#a31515;">"(1/(5*x))"</span>); <span style="color:#008000;">// Implied products have higher precedence</span>
TestParse(<span style="color:#a31515;">"1/2sqrt(x)"</span>, <span style="color:#a31515;">"(1/(2*Sqrt(x)))"</span>);
TestParse(<span style="color:#a31515;">"2 sin 3x"</span>, <span style="color:#a31515;">"(2*Sin((3*x)))"</span>);
TestParse(<span style="color:#a31515;">"2 sin 3x * 5 cos 7x"</span>, <span style="color:#a31515;">"((2*Sin((3*x)))*(5*Cos((7*x))))"</span>);
TestParse(<span style="color:#a31515;">"2 3"</span>, <span style="color:#a31515;">"(2*3)"</span>);
TestParse(<span style="color:#a31515;">"2(x+3)"</span>, <span style="color:#a31515;">"(2*(x+3))"</span>);
TestParse(<span style="color:#a31515;">"2x^3)"</span>, <span style="color:#a31515;">"((2*x)^3)"</span>);
TestParse(<span style="color:#a31515;">"2(x^3)"</span>, <span style="color:#a31515;">"(2*(x^3))"</span>);
TestParse(<span style="color:#a31515;">"x⁴-4x³+6x²-4x+1"</span>, <span style="color:#a31515;">"(((((x^4)-(4*(x^3)))+(6*(x^2)))-(4*x))+1)"</span>);
TestParse(<span style="color:#a31515;">"1/2√(1-x²)"</span>, <span style="color:#a31515;">"(1/(2*Sqrt((1-(x^2)))))"</span>);
TestParse(<span style="color:#a31515;">"eˣ"</span>, <span style="color:#a31515;">"(2.71828182845905^x)"</span>);
TestParse(<span style="color:#a31515;">"eᶜᵒˢ⁽ˣ⁾"</span>, <span style="color:#a31515;">"(2.71828182845905^Cos(x))"</span>);
}
</code></pre>John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-69236827847147297742019-04-17T10:48:00.000+01:002019-04-24T23:11:10.061+01:00Formula Translation: The Error Function<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvdOEfepi1TwYrywuZZ0g7YAenPeFQ6ofrQXIjScrEyRhi3uE4fedlTSDUMcjJ8NS26yiGNYle4uklPmfMjQKWAWMK1U7S7YgCbBNUSJPI8-Zn1hiJX7rh7COzdgsWF-yoxLDxuBTvwEgA/s1600/400px-Error_Function.svg.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="280" data-original-width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvdOEfepi1TwYrywuZZ0g7YAenPeFQ6ofrQXIjScrEyRhi3uE4fedlTSDUMcjJ8NS26yiGNYle4uklPmfMjQKWAWMK1U7S7YgCbBNUSJPI8-Zn1hiJX7rh7COzdgsWF-yoxLDxuBTvwEgA/s1600/400px-Error_Function.svg.png" /></a><b>Not At All Elementary, Watson</b><br />
<br />
<i>Previously:</i><br />
<blockquote><i><a href="https://mycodehere.blogspot.com/2019/03/differentiating-fxgx.html">Differentiating f(x)^g(x)</a><br />
<a href="http://mycodehere.blogspot.com/2019/03/the-differentiator.html">The Differentiator</a><br />
<a href="http://mycodehere.blogspot.com/2019/04/graphing-derivatives.html">Graphing Derivatives</a><br />
<a href="https://mycodehere.blogspot.com/2019/04/the-complexities-of-simplifier.html">The Complexities Of The Simplifier</a></i></blockquote>Project Differentiator is growing rapidly (!) into a customizable calculus teaching tool. Such resources are sometimes more valuable than any generally available mathematics code library, utility, or even online suite like <i>Wolfram Alpha</i> or <i>Mathematica</i>. Just ask <a href="https://www.3blue1brown.com/about">Grant Sanderson</a>, whose magnificent YouTube channel <a href="https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw">3blue1brown</a> - in addition to its inestimable educational payload - delivers and promotes beautiful mathematics as an art form, rolling along atop its very own powerful, handcrafted, dynamic, mathematical graphics engine. When you want to explain things in great detailed graphics, and especially in animations, nothing beats running your own source code.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNloc_fSgZ4KXzPDUHU57imZ8C5Bx8bQll_7p_48mCvaAVId7DpbwxT4uMgIPri4A2mIq9eCmJCWwyHbjBVrH8KXMiGrVOKfVNO-NH0nCsR-GFvOXju5KsNt3SbJYfti5pX1XXVZ9bn9Bs/s1600/erf.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="101" data-original-width="182" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNloc_fSgZ4KXzPDUHU57imZ8C5Bx8bQll_7p_48mCvaAVId7DpbwxT4uMgIPri4A2mIq9eCmJCWwyHbjBVrH8KXMiGrVOKfVNO-NH0nCsR-GFvOXju5KsNt3SbJYfti5pX1XXVZ9bn9Bs/s1600/erf.png" /></a>Waiting for the dust to settle on the latest UI iteration, I thought I'd mention this short function here: a three-line translation, based on some old 1992 code found in a <a href="https://www.amazon.co.uk/Numerical-Recipes-FORTRAN-Scientific-Computing/dp/052143064X">Fortran77 book</a>, of the error function, <i>y=erf(x)</i>, as defined in the integral equation alongside. That FORmula TRANslation provenance remains visible in my choice of variable names in the code below: well <i>of course</i> <b><i>n</i></b> is the integer loop variable.<br />
<br />
<i>Incidentally, you should never use the Fortran-recommended <b>I</b> as your loop variable. If a single pin breaks in your dot matrix printer, the resultant printout on your <a href="https://en.wikipedia.org/wiki/Line_printer#Paper_(forms)_handling">music ruled paper</a> might be mistaken for the digit <b>1</b>, crashing your lander on the moon.</i><br />
<br />
The interesting thing about <i>erf(x)</i> is that it's non-elementary, making it actually one of the uninteresting ones, globally speaking.<br />
<br />
<b>What's Special About Error?</b><br />
<pre style="margin:0em; overflow:auto; background-color:#ffffff;"><code style="font-family:Consolas,"Courier New",Courier,Monospace; font-size:10pt; color:#000000;">
<span style="color:#0000ff;">double</span> t = 1 / (1 + Math.Abs(x) / 2), u = 1, v = 0;
<span style="color:#0000ff;">for</span> (<span style="color:#0000ff;">var</span> n = 0; n < 10; v += erf_a[n] * u, n++, u *= t) ;
<span style="color:#0000ff;">return</span> (1 - t * Math.Exp(v - x * x)) * Math.Sign(x);
</code></pre><br />
Yeah, absolutely nothing. Non-elementary functions might appear curious to engineers learning calculus, but the proportion of all possible continuous functions that <i>are</i> elementary is zero (assuming that's possible for the ratio of two uncountable infinities). An elementary function, as originally introduced by <a href="https://en.wikipedia.org/wiki/Joseph_Liouville">Joseph Liouville</a> starting in 1833, is one built from certain basic functions (constant, algebraic, exponential and logarithmic, along with their inverses if they exist), by recursive composition and application of arithmetic operators, including root extraction.<br />
<br />
On the other hand, despite its non-elementary nature, <i>erf(x)</i> is in great demand, mostly because it's literally the area under the normal distribution graph in statistics. And when you hear there's no way to integrate y=exp(x²), well, a constant multiple of <i>erf(x)</i> is the answer to that too. These reasons, together with its eminent differentiability (it's defined as an integral, after all), are why I wanted it in the Lego™ box.<br />
<br />
Speaking of which, the list of elementary functions available in <i>Sid</i>, as the project is now dubbed, has more than doubled since you last looked. And if you ever needed an inverse trigonometric or hyperbolic function, but discovered it missing from the supplied <i>System.Math</i> set, you'll find the entire lot implemented here. Anyway, the <i>erf</i> code is the bottom third of this file:<br />
<pre style="margin:0em; overflow:auto; background-color:#ffffff;"><code style="font-family:Consolas,"Courier New",Courier,Monospace; font-size:10pt; color:#000000;">
<span style="color:#0000ff;">namespace</span> FormulaBuilder
{
<span style="color:#0000ff;">using</span> System;
<span style="color:#008000;">/// <summary></span>
<span style="color:#008000;">/// Several math functions are either absent from System.Math (Asec, Sech, Asech,</span>
<span style="color:#008000;">/// Step) or unsuitable (Sign, which has an integer output), so they are provided</span>
<span style="color:#008000;">/// in this class. Might as well include the whole System.Math lot here too; then</span>
<span style="color:#008000;">/// we never need perform two reflection operations to find any function by name.</span>
<span style="color:#008000;">/// </summary></span>
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">class</span> Functions
{
#region Elementary Functions
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Abs(<span style="color:#0000ff;">double</span> x) => Math.Abs(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Acos(<span style="color:#0000ff;">double</span> x) => Math.Acos(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Acosh(<span style="color:#0000ff;">double</span> x) => Math.Log(x + Math.Sqrt(Math.Pow(x, 2) - 1));
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Acot(<span style="color:#0000ff;">double</span> x) => Math.Atan(1 / x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Acoth(<span style="color:#0000ff;">double</span> x) => Math.Log((x + 1) / (x - 1)) / 2;
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Acsc(<span style="color:#0000ff;">double</span> x) => Math.Asin(1 / x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Acsch(<span style="color:#0000ff;">double</span> x) => Math.Log((1 + Math.Sqrt(1 + Math.Pow(x, 2))) / x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Asec(<span style="color:#0000ff;">double</span> x) => Math.Acos(1 / x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Asech(<span style="color:#0000ff;">double</span> x) => Math.Log((1 + Math.Sqrt(1 - Math.Pow(x, 2))) / x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Asin(<span style="color:#0000ff;">double</span> x) => Math.Asin(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Asinh(<span style="color:#0000ff;">double</span> x) => Math.Log(x + Math.Sqrt(Math.Pow(x, 2) + 1));
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Atan(<span style="color:#0000ff;">double</span> x) => Math.Atan(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Atanh(<span style="color:#0000ff;">double</span> x) => Math.Log((1 + x) / (1 - x)) / 2;
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Ceiling(<span style="color:#0000ff;">double</span> x) => Math.Ceiling(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Cos(<span style="color:#0000ff;">double</span> x) => Math.Cos(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Cosh(<span style="color:#0000ff;">double</span> x) => Math.Cosh(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Cot(<span style="color:#0000ff;">double</span> x) => 1 / Math.Tan(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Coth(<span style="color:#0000ff;">double</span> x) => 1 / Math.Tanh(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Csc(<span style="color:#0000ff;">double</span> x) => 1 / Math.Sin(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Csch(<span style="color:#0000ff;">double</span> x) => 1 / Math.Sinh(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Exp(<span style="color:#0000ff;">double</span> x) => Math.Exp(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Floor(<span style="color:#0000ff;">double</span> x) => Math.Floor(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Ln(<span style="color:#0000ff;">double</span> x) => Math.Log(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Log10(<span style="color:#0000ff;">double</span> x) => Math.Log10(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Round(<span style="color:#0000ff;">double</span> x) => Math.Round(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Sec(<span style="color:#0000ff;">double</span> x) => 1 / Math.Cos(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Sech(<span style="color:#0000ff;">double</span> x) => 1 / Math.Cosh(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Sign(<span style="color:#0000ff;">double</span> x) => Math.Sign(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Sin(<span style="color:#0000ff;">double</span> x) => Math.Sin(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Sinh(<span style="color:#0000ff;">double</span> x) => Math.Sinh(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Sqrt(<span style="color:#0000ff;">double</span> x) => Math.Sqrt(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Step(<span style="color:#0000ff;">double</span> x) => x < 0 ? 0 : 1;
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Tan(<span style="color:#0000ff;">double</span> x) => Math.Tan(x);
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Tanh(<span style="color:#0000ff;">double</span> x) => Math.Tanh(x);
#endregion
#region Non-elementary Functions
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">readonly</span> <span style="color:#0000ff;">double</span>[] erf_a =
{
-1.26551223, +1.00002368, +0.37409196, +0.09678418, -0.18628806,
+0.27886807, -1.13520398, +1.48851587, -0.82215223, +0.17087277
};
<span style="color:#008000;">/// <summary></span>
<span style="color:#008000;">/// An approximation, with a worst case error of 1.2e-7, from the book</span>
<span style="color:#008000;">/// "Numerical Recipes in Fortran 77: The Art of Scientific Computing"</span>
<span style="color:#008000;">/// (ISBN 0-521-43064-X), 1992, page 214, Cambridge University Press.</span>
<span style="color:#008000;">/// </summary></span>
<span style="color:#008000;">/// <param name="x">The input value to the function Erf(x).</param></span>
<span style="color:#008000;">/// <returns>An approximation to the value of erf(x).</returns></span>
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Erf(<span style="color:#0000ff;">double</span> x)
{
<span style="color:#0000ff;">double</span> t = 1 / (1 + Math.Abs(x) / 2), u = 1, v = 0;
<span style="color:#0000ff;">for</span> (<span style="color:#0000ff;">var</span> n = 0; n < 10; v += erf_a[n] * u, n++, u *= t) ;
<span style="color:#0000ff;">return</span> (1 - t * Math.Exp(v - x * x)) * Math.Sign(x);
}
#endregion
}
}
</code></pre><br />
I'll confess to considering the elision of that redundant final <b><i>u *= t</i></b> in the <i>for</i> loop, before remembering the pipeline exception would certainly waste more picoseconds than a blast through the floating point silicon. Old optimisation rabbits die hard.<br />
<br />
The algorithm first sets <i>t=1/(1+|x|/2)</i>. Then in the <i>for</i> loop, it forms a ninth degree polynomial <i>v(t)</i>, using ten supplied coefficients <i>erf_a[0..9]</i>. Finally, according to the original sign of <i>x</i>, your answer is either <i>1-t*e^(v(t)-x²)</i>, or its negative.<br />
<br />
<a href="https://mycodehere.blogspot.com/2019/04/expression-parser-part-1-of-2.html">Next time</a>: let's get that parser up and running...<br />
<br />
<i>Image credits: <a href="https://en.wikipedia.org/wiki/Error_function">https://en.wikipedia.org/wiki/Error_function</a>.</i>John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-54808551403170365682019-04-08T00:30:00.000+01:002019-05-26T23:40:54.995+01:00The Complexities Of The Simplifier<b>The Simplifier The Better</b><br />
<br />
<i>Previously:</i><br />
<blockquote><i><a href="https://mycodehere.blogspot.com/2019/03/differentiating-fxgx.html">Differentiating f(x)^g(x)</a><br />
<a href="http://mycodehere.blogspot.com/2019/03/the-differentiator.html">The Differentiator</a><br />
<a href="http://mycodehere.blogspot.com/2019/04/graphing-derivatives.html">Graphing Derivatives</a></i></blockquote>Simplification of algebraic expressions was never the intended destination of this series, but it's where we've landed. The rules of differentiation guarantee an answer for any input function, subject to certain reasonable conditions of continuity. They just don't promise a <i>pretty</i> answer; that much is up to us! And generally, it requires more work.<br />
<br />
Reduction of algebraic formulae is a big subject. Sometimes, to be fair, that's due to subjective constraints we place on answers. Take polynomials for example. It's natural to expect every power of <b><i>x</i></b> to be expressed as <b><i>a<sub>n</sub>xⁿ</i></b>, rather than say <b><i>(xⁿ)*a<sub>n</sub></i></b>, albeit the algebra is indifferent to such distinctions. On the other hand, nobody ever ordered <b><i>xⁿ+xⁿ</i></b> when they actually wanted <b><i>2xⁿ</i></b>.<br />
<br />
Our <i><a href="http://mycodehere.blogspot.com/2019/03/the-differentiator.html#Simplify">Simplify()</a></i> method is a fairly arbitrary piece of ad-hoc coding, which applies a specific set of transformation rules to the expression tree. These rules are motivated by the particular output from certain popular examples of differentiation, and are all applied during a single traversal of the tree. Here we will consider an alternative, where rules are applied one by one to the entire tree, starting at the root and proceeding depth-first recursively. These steps successively transform the tree such that new invariants become true upon each pass, resulting in an overall directed process of reduction.<br />
<br />
Let's continue using our informal rule notation, where<br />
<ul><li><b><i>s</i></b>, <b><i>t</i></b> represent any subtrees of arbitrary size and complexity;</li>
<li><b><i>+</i></b>, <b><i>-</i></b>, <b><i>*</i></b>, <b><i>/</i></b>, <b><i>^</i></b> are binary operators;</li>
<li><b><i>u+</i></b>, <b><i>u-</i></b> are the unary plus (identity) and minus (negate) operators respectively;</li>
<li><b><i>c</i></b>, <b><i>d</i></b> are numeric constants;</li>
<li><b><i>0</i></b>, <b><i>1</i></b>, <b><i>-1</i></b> are the particular numeric constants 0, ±1.</li>
</ul>Then using the right arrow <b><i>'→'</i></b> to indicate the application of a transformation rule to its left operand, resulting in the new tree in its right operand, we have the following examples. First, the unary <b><i>u+</i></b> operator represents the identity transformation, and can just be dropped from wherever it occurs:<br />
<pre><blockquote><b>u+(s) → s <i>(Rule 1)</i></b>
</blockquote></pre>The unary <b><i>u-</i></b> operator is equivalent to the binary <b><i>'-'</i></b> with <b><i>(0)</i></b> on its left hand side. This is not strictly a reductive transformation, but it might be useful if we want to rid the tree of all unary operators, so that some other rule requiring this precondition can then be applied <i>(note: the converse rule, which <u>is</u> reductive, was actually used in the original code)</i>:<br />
<pre><blockquote><b>u-(s) → 0 - s <i>(Rule 2)</i></b>
</blockquote></pre><b>One And One Is One</b><br />
<br />
Any binary operator with constants on both sides (constant siblings) can be replaced by a single, computed constant value. Let <b><i>@</i></b> be any one of the five binary operators. Then we have this rule template:<br />
<pre><blockquote><b>c @ d → c@d <i>(Rule 3)</i></b>
</blockquote></pre>where the notation <b><i>c@d</i></b> written without intervening spaces indicates a single constant node, comprising an immediate numerical calculation. Obviously checks against division by zero, and other indeterminate forms such as <i>0⁰</i>, are assumed to raise the appropriate exceptions (<i>DivideByZeroException</i>, <i>InvalidOperationException</i>) when poked.<br />
<br />
Similarly, certain constant niblings (nieces or nephews) can be combined, when their operators "match" - i.e., they are either both additive, or both multiplicative. For ease of legibility and comprehension in what follows, I'm going to let <b><i>'+'</i></b> represent the commutative operator in such a pair (which will actually be either <b><i>'+'</i></b> or <b><i>'*'</i></b>), and let <b><i>'-'</i></b> represent its noncommutative inverse (actually <b><i>'-'</i></b> or <b><i>'/'</i></b>). Then we have these four templates:<br />
<pre><blockquote><b>(s <span style="background-color: yellow;">+</span> c) + d → s <span style="background-color: yellow;">+</span> c+d <i>(Rule 4a)</i></b>
<b>(s <span style="background-color: yellow;">+</span> c) - d → s <span style="background-color: yellow;">+</span> c-d <i>(Rule 4b)</i></b>
<b>(s <span style="background-color: yellow;">-</span> c) + d → s <span style="background-color: yellow;">-</span> c-d <i>(Rule 4c)</i></b>
<b>(s <span style="background-color: yellow;">-</span> c) - d → s <span style="background-color: yellow;">-</span> c+d <i>(Rule 4d)</i></b>
</blockquote></pre>Notice the pattern of operators in these templates. The <span style="background-color: yellow;">first</span> operator, following the <b><i>s</i></b>, is unchanged; while the second is set to <i>either</i> the commutative <b><i>'+'</i></b> if the original two operators were the same, <i>or</i> the noncommutative <b><i>'-'</i></b> if they were different. This logic was handled by local variables <a href="https://mycodehere.blogspot.com/2019/03/the-differentiator.html#SameOpps"><i>same</i> & <i>opps</i></a> in the original <a href="https://mycodehere.blogspot.com/2019/03/the-differentiator.html#SimplifyBinary"><i>SimplifyBinary()</i></a> method.<br />
<br />
<b>Constant Craving</b><br />
<br />
Everything up to this point, with the singular exception of the unary <b><i>u-</i></b> noted above, was included in the earlier single traversal code. However these four latest example templates assume all the constants are on the RHS of their parent binaries. That suggests another twelve such patterns exist, where either one or both constants are actually on the LHS:<br />
<pre><blockquote><b>(c + s) + d → s + c+d <i>(Rule 5a)</i></b>
<b>(c + s) - d → s + c-d <i>(Rule 5b)</i></b>
<b>(c - s) + d → c+d - s <i>(Rule 5c)</i></b>
<b>(c - s) - d → c-d - s <i>(Rule 5d)</i></b>
<b>c + (s + d) → s + c+d <i>(Rule 6a)</i></b>
<b>c + (s - d) → s + c-d <i>(Rule 6b)</i></b>
<b>c - (s + d) → c+d - s <i>(Rule 6c)</i></b>
<b>c - (s - d) → c-d - s <i>(Rule 6d)</i></b>
<b>c + (d + s) → s + c+d <i>(Rule 7a)</i></b>
<b>c + (d - s) → c+d - s <i>(Rule 7b)</i></b>
<b>c - (d + s) → c-d - s <i>(Rule 7c)</i></b>
<b>c - (d - s) → s + c-d <i>(Rule 7d)</i></b>
</blockquote></pre>Almost half of these can be handled by preprocessing the tree to ensure that whenever possible, i.e. when a binary operator is commutative, its constant term appears on the RHS, using this supplementary rule:<br />
<pre><blockquote><b>c + s → s + c <i>(Rule 8)</i></b>
</blockquote></pre>A single application of Rule <i>8</i> carries Rules <i>5a</i> and <i>6a</i> into <i>4a</i>, <i>5b</i> into <i>4b</i>, and <i>6b</i> into <i>4c</i>, while two successive applications carry <i>7a</i> into <i>4a</i>. That's five out of these new twelve cases dealt with. We are frustrated in claiming a full 50% by Rule <i>7d</i>, which requires recognising that <b><i>s</i></b> is governed by two <b><i>'-'</i></b> signs (or <b><i>'/'</i></b> signs in the multiplicative version).<br />
<br />
<b>Constant Cousins</b><br />
<br />
Another important transformation which could have been included in the original sample code, but was omitted for purposes of clarity, is the combination of <i>cousin</i> constants. This is where a binary operation has two binary children, each of which has a constant operand. Since we are not handling distributivity here, the three binaries involved must be either all additive, or all multiplicative. This yields another 8 templates which can be expressed, reusing the previous operator definitions, like this:<br />
<pre><blockquote><b>(s <span style="background-color: yellow;">+</span> c) <span style="background-color: lime;">+</span> (t + d) → (s <span style="background-color: lime;">+</span> t) <span style="background-color: yellow;">+</span> c+d <i>(Rule 9a)</i></b>
<b>(s <span style="background-color: yellow;">+</span> c) <span style="background-color: lime;">+</span> (t - d) → (s <span style="background-color: lime;">+</span> t) <span style="background-color: yellow;">+</span> c-d <i>(Rule 9b)</i></b>
<b>(s <span style="background-color: yellow;">+</span> c) <span style="background-color: lime;">-</span> (t + d) → (s <span style="background-color: lime;">-</span> t) <span style="background-color: yellow;">+</span> c-d <i>(Rule 9c)</i></b>
<b>(s <span style="background-color: yellow;">+</span> c) <span style="background-color: lime;">-</span> (t - d) → (s <span style="background-color: lime;">-</span> t) <span style="background-color: yellow;">+</span> c+d <i>(Rule 9d)</i></b>
<b>(s <span style="background-color: yellow;">-</span> c) <span style="background-color: lime;">+</span> (t + d) → (s <span style="background-color: lime;">+</span> t) <span style="background-color: yellow;">-</span> c-d <i>(Rule 9e)</i></b>
<b>(s <span style="background-color: yellow;">-</span> c) <span style="background-color: lime;">+</span> (t - d) → (s <span style="background-color: lime;">+</span> t) <span style="background-color: yellow;">-</span> c+d <i>(Rule 9f)</i></b>
<b>(s <span style="background-color: yellow;">-</span> c) <span style="background-color: lime;">-</span> (t + d) → (s <span style="background-color: lime;">-</span> t) <span style="background-color: yellow;">-</span> c+d <i>(Rule 9g)</i></b>
<b>(s <span style="background-color: yellow;">-</span> c) <span style="background-color: lime;">-</span> (t - d) → (s <span style="background-color: lime;">-</span> t) <span style="background-color: yellow;">-</span> c-d <i>(Rule 9h)</i></b>
</blockquote></pre>Again there's a pattern to the operators, before and after. This time the <span style="background-color: yellow;">first</span> and <span style="background-color: lime;">second</span> operators swap places, while the third is set <i>either</i> to the commutative <b><i>'+'</i></b> if the original set of three operators contained an <i>even</i> number (0 or 2) of <b><i>'-'</i></b>s, <i>or</i> to the noncommutative <b><i>'-'</i></b> if it contained an <i>odd</i> number (1 or 3). And just as before, the possibility of constants appearing in the LHS leads to another proliferation of additional cases (initially 24), around half of which are handled by our commutativity code, with the rest requiring further special handling.<br />
<br />
<b>Finally, Semantix Rears Its Monstrous Head</b><br />
<br />
Merged constants aside, all of the foregoing transformations have been syntactical, in that they have not relied upon any particular constant values, function calls, powers of <b><i>x</i></b>, etc. Now we've combined as many constants into single values as we feel inclined to do, we can take advantage of a few simple semantic rules, such as those pertaining to additive and multiplicative identities and inverses. Assuming we're on the final stretch, we presumably no longer need maintain the unary-free invariant property of our expression tree, and can also reintroduce negative versions of the multiplicative rules. So, we may have <i>0</i> or <i>±1</i> on the RHS:<br />
<pre><blockquote><b>s + 0 → s <i>(Rule 10a)</i></b>
<b>s - 0 → s <i>(Rule 10b)</i></b>
<b>s * 0 → 0 <i>(Rule 10c)</i></b>
<b>s * 1 → s <i>(Rule 10d)</i></b>
<b>s * -1 → -s <i>(Rule 10e)</i></b>
<b>s / 1 → s <i>(Rule 10f)</i></b>
<b>s / -1 → -s <i>(Rule 10g)</i></b>
<b>s ^ 0 → 1 <i>(Rule 10h)</i></b>
<b>s ^ 1 → s <i>(Rule 10i)</i></b>
</blockquote></pre>Rules <i>10e</i> & <i>10g</i> are the negatives mentioned above. Finally, we may have <i>0</i> or <i>1</i> on the LHS, in these few cases which have not already been handled by commutativity:<br />
<pre><blockquote><b>0 / s → 0 <i>(Rule 11a)</i></b>
<b>0 ^ s → 0 <i>(Rule 11b)</i></b>
<b>1 ^ s → 1 <i>(Rule 11c)</i></b>
</blockquote></pre><a href="https://mycodehere.blogspot.com/2019/04/formula-translation-error-function.html">Next time</a>: work continues on the above optimisation system, and a new UI is simultaneously being developed to allow user entry of formulae as plain text. While all that rages on, I'll report on several interesting design snippets just to keep the post count ticking over. The first of these will be the addition of the <i>non-elementary error function Erf(x)</i> to the library.John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-40297467496117433082019-04-04T00:56:00.000+01:002019-04-13T18:36:15.293+01:00Graphing Derivatives<b>A Series Of Points</b><br />
<br />
<i>Previously:</i><br />
<blockquote><i><a href="https://mycodehere.blogspot.com/2019/03/differentiating-fxgx.html">Differentiating f(x)^g(x)</a><br />
<a href="http://mycodehere.blogspot.com/2019/03/the-differentiator.html">The Differentiator</a></i></blockquote>Note: The latest code can be found at <a href="https://github.com/dogbiscuituk/Sid/">https://github.com/dogbiscuituk/Sid/</a>.<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisZCVm6UffTfKN-7omU6OZQ4asXC-ackJw7QWHa-4SC_X-u76KBiD2p38c0ZQezfVdEwqQ2uY3JIDEUmcXEdyk3s4tEcgMqJcYOzRTPe11n133KYqA3jKHAnKLgw4BXgMVgc5gb1urJ0wZ/s1600/Ln.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisZCVm6UffTfKN-7omU6OZQ4asXC-ackJw7QWHa-4SC_X-u76KBiD2p38c0ZQezfVdEwqQ2uY3JIDEUmcXEdyk3s4tEcgMqJcYOzRTPe11n133KYqA3jKHAnKLgw4BXgMVgc5gb1urJ0wZ/s640/Ln.png" width="640" height="361" data-original-width="946" data-original-height="533" /></a></div>I've been inundated by a request from one reader, for a concrete example illustrating the plotting of graphs and their derivatives using my <i>Differentiator</i> class. This here quick and filthy Winforms application is my terse response. It uses no modern graphics library, just oldschool <i>GDI+</i>. Starting with a new Winforms app containing the <i>FormulaBuilder</i> class library from last time, add a new <i>FormulaGrapher</i> library to contain the two classes I'm about to describe.<br />
<br />
Visual graphing controls normally provide a capability to plot multiple traces on the same canvas, so the first thing we need is a class to represent just one of these traces. Again following tradition, we'll call this the <i>Series</i> class. Its main responsibility will be to store the <i>Expression</i> used to calculate the points on its curve. This is passed into the constructor as the <i>Formula</i> parameter, and immediately lambda-compiled into the callable function <i>Func</i>.<br />
<br />
For demo purposes, we'll also have this class store the resolution <i>StepCount</i> (the number of tiny line segments in the output, normally one less than the number of points computed); the <i>PenColour</i> used to draw the trace; and a cache containing the current plot limits, together with all of the computed point coordinates. After all, you might have any number of nested trigonometric functions to compute per point, and as long as the logical plot limits don't change, there's no need to recalculate these in response to e.g. resizing the output canvas in terms of physical pixels, or switching between screen and printer output.<br />
<pre style="margin:0em; overflow:auto; background-color:#ffffff;"><code style="font-family:Consolas,"Courier New",Courier,Monospace; font-size:10pt; color:#000000;">
<span style="color:#0000ff;">using</span> System;
<span style="color:#0000ff;">using</span> System.Collections.Generic;
<span style="color:#0000ff;">using</span> System.Drawing;
<span style="color:#0000ff;">using</span> System.Linq.Expressions;
<span style="color:#0000ff;">using</span> FormulaBuilder;
<span style="color:#0000ff;">namespace</span> FormulaGrapher
{
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">class</span> Series
{
<span style="color:#0000ff;">public</span> Series(Expression formula, <span style="color:#0000ff;">int</span> stepCount)
{
Func = formula.AsFunction();
StepCount = stepCount;
}
<span style="color:#0000ff;">public</span> Color PenColour { <span style="color:#0000ff;">get</span>; <span style="color:#0000ff;">set</span>; }
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">void</span> Draw(Graphics g, RectangleF limits, <span style="color:#0000ff;">float</span> penWidth)
{
<span style="color:#0000ff;">if</span> (Limits != limits)
{
Limits = limits;
ComputePoints();
}
<span style="color:#0000ff;">using</span> (<span style="color:#0000ff;">var</span> pen = <span style="color:#0000ff;">new</span> Pen(PenColour, penWidth))
PointLists.ForEach(p => g.DrawLines(pen, p.ToArray()));
}
<span style="color:#0000ff;">private</span> Func<<span style="color:#0000ff;">double</span>, <span style="color:#0000ff;">double</span>> Func;
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">int</span> StepCount;
<span style="color:#0000ff;">private</span> RectangleF Limits;
<span style="color:#0000ff;">private</span> List<List<PointF>> PointLists = <span style="color:#0000ff;">new</span> List<List<PointF>>();
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> ComputePoints()
{
PointLists.Clear();
List<PointF> points = <span style="color:#0000ff;">null</span>;
<span style="color:#0000ff;">float</span>
x1 = Limits.Left, y1 = Limits.Top, y2 = Limits.Bottom,
w = Limits.Width, h = 8 * Limits.Height;
<span style="color:#0000ff;">var</span> skip = <span style="color:#0000ff;">true</span>;
<span style="color:#0000ff;">for</span> (<span style="color:#0000ff;">var</span> step = 0; step <= StepCount; step++)
{
<span style="color:#0000ff;">float</span> x = x1 + step * w / StepCount, y = (<span style="color:#0000ff;">float</span>)Func(x);
<span style="color:#0000ff;">if</span> (<span style="color:#0000ff;">float</span>.IsInfinity(y) || <span style="color:#0000ff;">float</span>.IsNaN(y) || y < y1 - h || y > y2 + h)
skip = <span style="color:#0000ff;">true</span>;
<span style="color:#0000ff;">else</span>
{
<span style="color:#0000ff;">if</span> (skip)
{
skip = <span style="color:#0000ff;">false</span>;
points = <span style="color:#0000ff;">new</span> List<PointF>();
PointLists.Add(points);
}
points.Add(<span style="color:#0000ff;">new</span> PointF(x, y));
}
}
<span style="color:#008000;">// Every segment of the trace must include at least 2 points.</span>
PointLists.RemoveAll(p => p.Count < 2);
}
}
}
</code></pre><br />
So our first class essentially consists of just this <i>Draw</i> method, which first checks whether the supplied logical limits are the same as the previous call, if any. When it decides recomputation is required, it iterates over <i>x</i> values between the supplied limits, splitting the interval into <i>StepCount</i> equal segments, and computing and storing the corresponding <i>y</i> values.<br />
<br />
One interesting aspect of the structure used to store these points is its type: a <i>List</i> of <i>List</i>s of <i>PointF</i>s. This particular contortion is needed because some graphs have discontinuities, or worse still, whole interior intervals on which they are entirely undefined. Hence the need to break the list into segments, whenever a <i>y</i> value is found to be <i>infinite</i>, or <i>not a number</i>, or just <i>too damned big</i> for our paltry coordinate system.<br />
<br />
<b>A Group Of Series</b><br />
<br />
Next up, we have our supervisor class, which I've called <i>Graph</i>. This will take full control of its multiple individual <i>Series</i>, so we will only need to interact directly with the <i>Graph</i> class itself. Let's start by examining its constructor parameters.<br />
<ul><li><i>limits</i> is a <i>RectangleF</i> structure specifying the logical window for the plots. Suppose we want the <i>x</i> domain to go from -9 to +9, and <i>y</i> to range from -7 to +7. The two minimum (negative) values can be supplied as-is, but the <i>GDI+</i> convention requires <i>width</i> and <i>height</i> values to specify the window size, rather than the corresponding maximum (positive) values of <i>x</i> and <i>y</i>. So in our example, we would pass the window bounds as <i>new RectangleF(-9, -7, 18, 14)</i>.</li>
<li><i>stepCount</i> has already been described; it is simply passed through to the <i>Series</i> objects for use when drawing.</li>
</ul>As with the previous <i>Series</i> class, the interesting details are in the <i>Draw</i> method and its minions:<br />
<pre style="margin:0em; overflow:auto; background-color:#ffffff;"><code style="font-family:Consolas,"Courier New",Courier,Monospace; font-size:10pt; color:#000000;">
<span style="color:#0000ff;">using</span> System;
<span style="color:#0000ff;">using</span> System.Collections.Generic;
<span style="color:#0000ff;">using</span> System.Diagnostics;
<span style="color:#0000ff;">using</span> System.Drawing;
<span style="color:#0000ff;">using</span> System.Drawing.Drawing2D;
<span style="color:#0000ff;">using</span> System.Linq.Expressions;
<span style="color:#0000ff;">using</span> FormulaBuilder;
<span style="color:#0000ff;">namespace</span> FormulaGrapher
{
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">class</span> Graph
{
<span style="color:#0000ff;">public</span> Graph(RectangleF limits, <span style="color:#0000ff;">int</span> stepCount)
{
Limits = limits;
StepCount = stepCount;
}
<span style="color:#0000ff;">public</span> Series AddSeries(Expression formula)
{
Debug.WriteLine(formula.AsString());
<span style="color:#0000ff;">var</span> series = <span style="color:#0000ff;">new</span> Series(formula, StepCount);
Series.Add(series);
<span style="color:#0000ff;">return</span> series;
}
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">void</span> Clear()
{
Series.Clear();
}
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">void</span> Draw(Graphics g, Rectangle r)
{
<span style="color:#0000ff;">if</span> (r.Width == 0 || r.Height == 0)
<span style="color:#0000ff;">return</span>; <span style="color:#008000;">// Nothing to draw!</span>
g.SmoothingMode = SmoothingMode.AntiAlias;
g.Transform = GetMatrix(r);
g.FillRectangle(Brushes.LightYellow, Limits);
<span style="color:#0000ff;">var</span> penWidth = (Limits.Width / r.Width + Limits.Height / r.Height);
DrawGrid(g, penWidth);
Series.ForEach(s => s.Draw(g, Limits, penWidth));
}
<span style="color:#0000ff;">public</span> PointF ScreenToGraph(Point p, Rectangle r)
{
<span style="color:#0000ff;">var</span> points = <span style="color:#0000ff;">new</span>[] { <span style="color:#0000ff;">new</span> PointF(p.X, p.Y) };
<span style="color:#0000ff;">var</span> matrix = GetMatrix(r);
matrix.Invert();
matrix.TransformPoints(points);
<span style="color:#0000ff;">return</span> points[0];
}
<span style="color:#0000ff;">private</span> RectangleF Limits;
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">int</span> StepCount;
<span style="color:#0000ff;">private</span> List<Series> Series = <span style="color:#0000ff;">new</span> List<Series>();
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> DrawGrid(Graphics g, <span style="color:#0000ff;">float</span> penWidth)
{
<span style="color:#0000ff;">float</span> x1 = Limits.Left, y1 = Limits.Bottom, x2 = Limits.Right, y2 = Limits.Top;
<span style="color:#0000ff;">using</span> (Pen gridPen = <span style="color:#0000ff;">new</span> Pen(Color.LightGray, penWidth) { DashStyle = DashStyle.Dot },
axisPen = <span style="color:#0000ff;">new</span> Pen(Color.DarkGray, penWidth))
<span style="color:#0000ff;">using</span> (<span style="color:#0000ff;">var</span> font = <span style="color:#0000ff;">new</span> Font(<span style="color:#a31515;">"Arial"</span>, 5 * penWidth))
<span style="color:#0000ff;">using</span> (<span style="color:#0000ff;">var</span> format = <span style="color:#0000ff;">new</span> StringFormat(StringFormat.GenericTypographic) { Alignment = StringAlignment.Far })
{
<span style="color:#0000ff;">var</span> brush = Brushes.DarkGray;
<span style="color:#0000ff;">for</span> (<span style="color:#0000ff;">int</span> phase = 0; phase < 4; phase++)
{
<span style="color:#0000ff;">var</span> vertical = (phase & 1) != 0;
<span style="color:#0000ff;">if</span> (phase < 2)
{
<span style="color:#0000ff;">var</span> size = Math.Log10(Math.Abs(y2 - y1));
<span style="color:#0000ff;">var</span> step = Math.Floor(size);
size -= step;
step = (size < 0.3 ? 2 : size < 0.7 ? 5 : 10) * Math.Pow(10, step - 1);
<span style="color:#0000ff;">for</span> (<span style="color:#0000ff;">var</span> y = step; y <= Math.Max(Math.Abs(y1), Math.Abs(y2)); y += step)
{
DrawGridLine(g, gridPen, font, brush, x1, x2, (<span style="color:#0000ff;">float</span>)y, format, vertical);
DrawGridLine(g, gridPen, font, brush, x1, x2, -(<span style="color:#0000ff;">float</span>)y, format, vertical);
}
}
<span style="color:#0000ff;">else</span>
DrawGridLine(g, axisPen, font, brush, x1, x2, 0, format, vertical);
<span style="color:#0000ff;">var</span> t = x1; x1 = y1; y1 = t;
t = x2; x2 = y2; y2 = t;
}
}
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> DrawGridLine(Graphics g, Pen pen, Font font, Brush brush,
<span style="color:#0000ff;">float</span> x1, <span style="color:#0000ff;">float</span> x2, <span style="color:#0000ff;">float</span> y, StringFormat format, <span style="color:#0000ff;">bool</span> vertical)
{
<span style="color:#0000ff;">float</span> x = 0, y1 = y, y2 = y, z = -y;
<span style="color:#0000ff;">if</span> (vertical)
{
<span style="color:#0000ff;">var</span> t = x; x = y; y = t;
t = x1; x1 = y1; y1 = t;
t = x2; x2 = y2; y2 = t;
z = -z;
}
g.DrawLine(pen, x1, y1, x2, y2);
g.ScaleTransform(1, -1);
g.DrawString(z.ToString(), font, brush, x - pen.Width, y + pen.Width, format);
g.ScaleTransform(1, -1);
}
<span style="color:#0000ff;">private</span> Matrix GetMatrix(Rectangle r)
{
<span style="color:#0000ff;">return</span> <span style="color:#0000ff;">new</span> Matrix(Limits,
<span style="color:#0000ff;">new</span>[] { <span style="color:#0000ff;">new</span> PointF(r.Left, r.Bottom), <span style="color:#0000ff;">new</span> PointF(r.Right, r.Bottom), r.Location });
}
}
}
</code></pre><br />
The <i>GDI+ Transform</i> property of a <i>Graphics</i> context is a <i>Matrix</i>, which handles all conversions between our logical units, e.g. the <i>'one'</i> that's the <i>sine</i> of <i>half a pie (π/2)</i>, and physical units, e.g. the <i>'one'</i> that's the size of a pixel on the display device. Here we basically set the <i>Transform</i> to equal the ratio between these coordinate systems, and forget it...<br />
<br />
...until, that is, we come to specify a pen width for our plots. Here we can't just say <i>1</i>, hoping for a single pixel width line. What we would get instead is a line as thick as the height of the sine graph we're trying to plot, making it look as if drawn with a giant crayon. Go ahead, try it, it's comical! The solution is just to scale the desired pixel pen width by the inverse of the coordinate system ratio, so undoing the effect of the <i>Transform</i> on our pen size.<br />
<br />
<i>DrawGrid()</i> prepares the canvas, giving it the appearance of a musty old sheet of graph paper with <i>x-y</i> axes, and grid lines with numerical labels. Regardless of whether you scale your limits up or down by a factor of 100, each axis is divided into between 4 and 10 sensibly sized strips, by the magic of logarithms (initiates will quickly spot the usual four lines of code doing this essential work). The strips are then labelled in ones, twos or fives, as appropriate. A total of four phases are used to draw the <i>x</i> grid, <i>y</i> grid, <i>x</i> axis, and <i>y</i> axis in turn, ensuring that the grid is always <i>behind</i> the axes (and the axes <i>behind</i> the traces, which will be drawn next).<br />
<br />
Note the inversions of the y axis near the end of <i>DrawGridLine()</i>. Since our graph's <i>y</i> values increase from bottom up, while device <i>y</i> values increase from top down, there is necessarily a flip in the transform used to map one to the other. If ignored, this flip would cause all text to be printed upside down! One of several simple solutions is to invert the <i>y</i> axis before printing text, and then again after, to restore the original coordinate mapping.<br />
<br />
<i>GetMatrix()</i> has been factored out from the original <i>Draw()</i> method, to let external clients access logical coordinates via <i>ScreenToGraph()</i>. The main form below will use this feature to show on a ToolTip the updated logical position, i.e. graph world coordinates, of the moving mouse.<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeCw6ZI_S0GWtmsqlzcqTOZq8IzVvnOJmj2uGPWZIoqBR0DJsebFkwd6FjS492ncKMajPWBBkZWIsDkmy9bxntbZOGy7Fzy-szIY3WSxuoRF8mmJuNCw5zvaEjAdL7jEoGdgggED5F9X-e/s1600/DifferentiatorDemo.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="300" data-original-width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeCw6ZI_S0GWtmsqlzcqTOZq8IzVvnOJmj2uGPWZIoqBR0DJsebFkwd6FjS492ncKMajPWBBkZWIsDkmy9bxntbZOGy7Fzy-szIY3WSxuoRF8mmJuNCw5zvaEjAdL7jEoGdgggED5F9X-e/s1600/DifferentiatorDemo.png" /></a></div><b>A Form Of Pencils</b><br />
<br />
Finally it's time to add a main form to our Winforms app. Give it a context menu with an item for each formula you want to draw, following the pattern shown here. The code in the <i>DrawGraphs()</i> method will draw your selected function in black, then on the same canvas it will draw the first, second and third derivatives in red, green and blue respectively.<br />
<pre style="margin:0em; overflow:auto; background-color:#ffffff;"><code style="font-family:Consolas,"Courier New",Courier,Monospace; font-size:10pt; color:#000000;">
<span style="color:#0000ff;">using</span> System.Drawing;
<span style="color:#0000ff;">using</span> System.Linq.Expressions;
<span style="color:#0000ff;">using</span> System.Windows.Forms;
<span style="color:#0000ff;">using</span> FormulaBuilder;
<span style="color:#0000ff;">using</span> FormulaGrapher;
<span style="color:#0000ff;">namespace</span> Sid
{
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">partial</span> <span style="color:#0000ff;">class</span> MainForm : Form
{
<span style="color:#0000ff;">public</span> MainForm()
{
InitializeComponent();
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> MainForm_Paint(<span style="color:#0000ff;">object</span> sender, PaintEventArgs e)
{
DrawGraph(e.Graphics);
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> MainForm_Resize(<span style="color:#0000ff;">object</span> sender, System.EventArgs e)
{
Invalidate();
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> MainForm_MouseMove(<span style="color:#0000ff;">object</span> sender, MouseEventArgs e)
{
<span style="color:#0000ff;">var</span> p = Graph.ScreenToGraph(e.Location, ClientRectangle);
ToolTip.SetToolTip(<span style="color:#0000ff;">this</span>, <span style="color:#0000ff;">string</span>.Format(<span style="color:#a31515;">"({0}, {1})"</span>, p.X, p.Y));
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> MenuSinX_Click(<span style="color:#0000ff;">object</span> sender, System.EventArgs e)
{
DrawGraphs(sender, x.Sin());
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> MenuSinhXover2_Click(<span style="color:#0000ff;">object</span> sender, System.EventArgs e)
{
DrawGraphs(sender, x.Over(2).Sinh());
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> Menu1overX_Click(<span style="color:#0000ff;">object</span> sender, System.EventArgs e)
{
DrawGraphs(sender, x.Power(-1));
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> MenuLnXsquaredMinus1_Click(<span style="color:#0000ff;">object</span> sender, System.EventArgs e)
{
DrawGraphs(sender, x.Squared().Minus(1).Log());
}
<span style="color:#0000ff;">private</span> Graph Graph = <span style="color:#0000ff;">new</span> Graph(<span style="color:#0000ff;">new</span> RectangleF(-9, -7, 18, 14), 16000);
<span style="color:#0000ff;">private</span> Expression x = Differentiator.x;
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> DrawGraph(Graphics g)
{
Graph.Draw(g, ClientRectangle);
}
<span style="color:#0000ff;">private</span> <span style="color:#0000ff;">void</span> DrawGraphs(<span style="color:#0000ff;">object</span> Sender, Expression y)
{
Text = ((ToolStripMenuItem)Sender).Text;
Graph.Clear();
Graph.AddSeries(y).PenColour = Color.Black;
y = y.Differentiate();
Graph.AddSeries(y).PenColour = Color.Red;
y = y.Differentiate();
Graph.AddSeries(y).PenColour = Color.Green;
y = y.Differentiate();
Graph.AddSeries(y).PenColour = Color.Blue;
Invalidate();
}
}
}
</code></pre><br />
The results are anisotropic, so as you resize the form, the <i>x</i> and <i>y</i> scalings vary independently. This affects the pen too - horizontal and vertical lines are generally of different thicknesses. This is only proof of concept code, which you might extend for your own purposes.<br />
<br />
One last thing to note is the appearance of the sample graph at the head of this article. Its primary function <i>y=ln(x²-1)</i> is undefined for all values of <i>x</i> between <i>-1</i> and <i>+1</i>, as is borne out by the shape of the two black curves, resembling a funnel, with no connection between the left and right sides. So far, so good. But if it's undefined in that region, why does it seem to have perfectly well defined first, second and third derivatives there, as indicated by the red, green and blue curves in the centre area? <i>(Hint: look for a complex explanation!)</i><br />
<br />
<a href="https://mycodehere.blogspot.com/2019/04/the-complexities-of-simplifier.html">Next time</a> I'll be returning to the business of simplifying the algebraic expressions returned by the differentiator.John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-18461532405508366702019-03-27T16:45:00.000+00:002019-04-12T13:51:56.738+01:00The Differentiator<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKdgHQ-ohKCjH6x6P4F7DmJzQ13alqCtrH7G9kxXXxxJ0sxRk93NCdKliEkfrELBgNfFgjHCuEc1cP0Y3GyUlAX8Efz538BHfKGKAf5Wq6o5CF9BM5Pc54Rnou-UqZ5uj36FUyH1Qw8vJR/s1600/C%2523.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="269" data-original-width="305" height="281" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKdgHQ-ohKCjH6x6P4F7DmJzQ13alqCtrH7G9kxXXxxJ0sxRk93NCdKliEkfrELBgNfFgjHCuEc1cP0Y3GyUlAX8Efz538BHfKGKAf5Wq6o5CF9BM5Pc54Rnou-UqZ5uj36FUyH1Qw8vJR/s320/C%2523.png" width="320" /></a><b>Fun With C# Expressions</b><br />
<br />
<i>Previously:</i><br />
<blockquote><i><a href="https://mycodehere.blogspot.com/2019/03/differentiating-fxgx.html">Differentiating f(x)^g(x)</a><br />
</i></blockquote>Note: The latest code can be found at <a href="https://github.com/dogbiscuituk/Sid/">https://github.com/dogbiscuituk/Sid/</a>.<br />
<br />
Take a seat with Visual Studio and create a new solution - Console, Winforms, whatever - your choice. Now add a new class library <i>FormulaBuilder</i>, and add to that a static class <i>Differentiator</i>. This is going to let us manipulate objects of type <i>System.Linq.Expression</i>, representing real-valued functions of a single real variable, and ultimately to plot the graph of any such function, as well as its <i>n</i>th derivatives. It will use an easy-to-read fluent interface, while illustrating some key design tenets I'd like to highlight.<br />
<br />
Back to the class, and here come our first three requirements. We will need ways to:<br />
<ol><li>represent constants and variables, and combine these fluently into compound expressions;</li>
<li>display any such expression as an easy-to-read formula, and evaluate it for given values of <i>x</i>; and</li>
<li>verify correctness of these combinations and conversions.</li>
</ol>Leaving aside the whole "combine these fluently into compound expressions" thing for just a moment, let's open with a <i>ParameterExpression</i> for <i>x</i>, and the following starter set of extension methods:<br />
<pre style="background-color: white; margin: 0em; overflow: auto;"><code style="color: black; font-family: "consolas" , "courier new" , "courier" , monospace; font-size: 10pt;">
<span style="color: blue;">using</span> System;
<span style="color: blue;">using</span> System.Diagnostics;
<span style="color: blue;">using</span> System.Linq.Expressions;
<span style="color: blue;">namespace</span> FormulaBuilder
{
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">class</span> Differentiator
{
<span style="color: blue;">public</span> <span style="color: blue;">static</span> ParameterExpression x = Expression.Variable(<span style="color: blue;">typeof</span>(<span style="color: blue;">double</span>));
<span style="color: blue;">public</span> <span style="color: blue;">static</span> ConstantExpression Constant(<span style="color: blue;">double</span> c) => Expression.Constant(c);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">string</span> AsString(<span style="color: blue;">this</span> Expression e, <span style="color: blue;">string</span> variableName = <span style="color: #a31515;">"x"</span>) =>
e.ToString().Replace(<span style="color: #a31515;">"Param_0"</span>, variableName);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Func<<span style="color: blue;">double</span>, <span style="color: blue;">double</span>> AsFunction(<span style="color: blue;">this</span> Expression e) =>
Expression.Lambda<Func<<span style="color: blue;">double</span>, <span style="color: blue;">double</span>>>(e, x).Compile();
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">double</span> AsDouble(<span style="color: blue;">this</span> Expression e, <span style="color: blue;">double</span> x) => AsFunction(e)(x);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> Check(<span style="color: blue;">double</span> expected, <span style="color: blue;">double</span> actual) =>
Check(expected.ToString(), actual.ToString());
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> Check(<span style="color: blue;">string</span> expected, <span style="color: blue;">string</span> actual)
{
<span style="color: blue;">if</span> (actual == expected)
Debug.WriteLine(<span style="color: #a31515;">"OK: "</span> + actual);
<span style="color: blue;">else</span>
Debug.WriteLine(<span style="color: #a31515;">"*** Comparison failed. Expected: {0}, Actual: {1}."</span>, expected, actual);
}
</code></pre><br />
On first look, the expression display requirement (1) seems to be met by the built-in <i>ToString()</i> method, available to every type; but that spits out an ugly <i>Param_0</i> instead of a nice tidy <i>x</i>, so we supply a new <i>AsString()</i> method to tidy that up.<br />
<br />
The expression evaluation/iteration requirement (2) means converting our expression into a <i>LambdaExpression</i>, compiling that into a function, and finally executing that function using our chosen values of <i>x</i>. For efficiency I've split this process into two steps. The conversion to a compiled <i>LambdaExpression</i> is performed by the <i>ToFunction()</i> method. Once we have this function, we can call it a million times with different <i>x</i> values, without having to perform a runtime lambda recompilation every time. The <i>ToDouble()</i> method is also provided for those out-of-loop test cases, where we just want to obtain a single value using a single method call.<br />
<br />
As for testing requirement (3), the overloaded <i>Check</i> methods provide a line of debug output for each checked operation, beginning with either <i>OK</i> or <i>*** Comparison failed</i>.<br />
<br />
<b>Fluency First</b><br />
<br />
Now back to those compounds. Actually, the <i>System.Linq.Expression</i> class already has some fine methods for compounding expressions into <i>BinaryExpression</i>s, but I'm going to deviate a little from these. The target is a fluent syntax for building expressions on the fly, and to that end, I find the names <i>Plus</i>, <i>Minus</i>, <i>Times</i>, <i>Over</i> a little easier to scan than <i>Add</i>, <i>Subtract</i>, <i>Multiply</i>, <i>Divide</i>. So let's add these to the <i>Differentiator</i> class:<br />
<pre style="background-color: white; margin: 0em; overflow: auto;"><code style="color: black; font-family: "consolas" , "courier new" , "courier" , monospace; font-size: 10pt;">
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Plus(<span style="color: blue;">this</span> Expression f, Expression g) => Expression.Add(f, g);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Minus(<span style="color: blue;">this</span> Expression f, Expression g) => Expression.Subtract(f, g);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Times(<span style="color: blue;">this</span> Expression f, Expression g) => Expression.Multiply(f, g);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Over(<span style="color: blue;">this</span> Expression f, Expression g) => Expression.Divide(f, g);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Power(<span style="color: blue;">this</span> Expression f, Expression g) => Expression.Power(f, g);
</code></pre><br />
Actually I also considered replacing <i>Power</i> with <i>ToThe</i>, but maybe that's just a colloquial idiom too far!<br />
<br />
The <i>Constant()</i> syntax is quite clumsy, almost the opposite of the <i>fluent</i> design goal. It would be great to have an implicit operator for converting constants such as <i>2.0</i> or <i>Math.PI</i> into <i>ConstantExpression</i>s as required - just as it would be equally great to be able to overload the standard arithmetic operators for <i>Expression</i>s, and be finished with fluent method calls! Sadly, C# restrictions on overloaded operators make both of these dreams impossible, but at least in the <i>Constant()</i> case, what we can do instead is overload the calling methods at the point(s) of invocation. For example, we'd like to be able to write <i>x.Power(2)</i> or <i>x.Power(2.0)</i> instead of <i>x.Power(Constant(2))</i>. This can be achieved by overloading the <i>Power()</i> method, supplying a <i>double</i> in place of an <i>Expression</i> for its second argument, and similarly for the other <i>BinaryExpression</i> methods:<br />
<pre style="background-color: white; margin: 0em; overflow: auto;"><code style="color: black; font-family: "consolas" , "courier new" , "courier" , monospace; font-size: 10pt;">
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Plus(<span style="color: blue;">this</span> Expression f, <span style="color: blue;">double</span> g) => f.Plus(Constant(g));
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Minus(<span style="color: blue;">this</span> Expression f, <span style="color: blue;">double</span> g) => f.Minus(Constant(g));
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Times(<span style="color: blue;">this</span> Expression f, <span style="color: blue;">double</span> g) => f.Times(Constant(g));
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Over(<span style="color: blue;">this</span> Expression f, <span style="color: blue;">double</span> g) => f.Over(Constant(g));
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Power(<span style="color: blue;">this</span> Expression f, <span style="color: blue;">double</span> g) => f.Power(Constant(g));
</code></pre><br />
This almost gets us where we want to go, but we still have to write e.g. <i>Constant(3).Times(x)</i>, when we'd prefer just <i>3.Times(x)</i>. Here it's the <i>first</i> argument of the <i>Times()</i> extension method that needs overloading, and there's a spanner thrown in by C#'s rules of method resolution. Sure, we can supply alternate methods with a <i>double</i> in that position, but where previously we could rely on an <i>int</i> argument being promoted to <i>double</i>, no such luck this time. If we insist <i>(and we do!)</i> upon being able to write <i>3.Times(x)</i>, as opposed to workarounds like <i>x.Times(3)</i>, <i>3.0.Times(x)</i>, or worst of all <i>((double)3).Times(x)</i> - which seems hardly an improvement! - then we must supply separate <i>double </i>and <i>int </i>versions.<br />
<pre style="background-color: white; margin: 0em; overflow: auto;"><code style="color: black; font-family: "consolas" , "courier new" , "courier" , monospace; font-size: 10pt;">
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Plus(<span style="color: blue;">this</span> <span style="color: blue;">double</span> f, Expression g) => Constant(f).Plus(g);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Minus(<span style="color: blue;">this</span> <span style="color: blue;">double</span> f, Expression g) => Constant(f).Minus(g);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Times(<span style="color: blue;">this</span> <span style="color: blue;">double</span> f, Expression g) => Constant(f).Times(g);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Over(<span style="color: blue;">this</span> <span style="color: blue;">double</span> f, Expression g) => Constant(f).Over(g);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Power(<span style="color: blue;">this</span> <span style="color: blue;">double</span> f, Expression g) => Constant(f).Power(g);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Plus(<span style="color: blue;">this</span> <span style="color: blue;">int</span> f, Expression g) => Constant(f).Plus(g);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Minus(<span style="color: blue;">this</span> <span style="color: blue;">int</span> f, Expression g) => Constant(f).Minus(g);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Times(<span style="color: blue;">this</span> <span style="color: blue;">int</span> f, Expression g) => Constant(f).Times(g);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Over(<span style="color: blue;">this</span> <span style="color: blue;">int</span> f, Expression g) => Constant(f).Over(g);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Power(<span style="color: blue;">this</span> <span style="color: blue;">int</span> f, Expression g) => Constant(f).Power(g);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> UnaryExpression Negate(<span style="color: blue;">this</span> Expression e) => Expression.Negate(e);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Reciprocal(<span style="color: blue;">this</span> Expression e) => e.Power(-1);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Squared(<span style="color: blue;">this</span> Expression e) => e.Power(2);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Cubed(<span style="color: blue;">this</span> Expression e) => e.Power(3);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> TestCompoundExpression()
{
<span style="color: blue;">var</span> f = x.Squared().Plus(3.Times(x)).Minus(5); <span style="color: green;">// f(x) = x²+3x-5</span>
Check(<span style="color: #a31515;">"(((x ^ 2) + (3 * x)) - 5)"</span>, f.AsString()); <span style="color: green;">// Check the built expression formula</span>
Check(Math.Pow(7, 2) + 3 * 7 - 5, f.AsDouble(7)); <span style="color: green;">// Check the expression value at x = 7 (should be 65)</span>
}
</code></pre><br />
With the addition of these few minor utilities, we now have the means to build polynomials and other arithmetic expressions in <i>x</i>. Give your app a project reference to <i>FormulaBuilder</i>, add it to your <i>using</i> clauses, then add a call to <i>Differentiator.TestCompoundExpression()</i> somewhere in your main code and run it. You should see the following two lines of reassurance in your debug output:<br />
<blockquote class="tr_bq">OK: (((x ^ 2) + (3 * x)) - 5)<br />
OK: 65</blockquote>These show that the quadratic expression <i>x² + 3x - 5</i> was built successfully, and that it correctly evaluates to <i>65</i> at <i>x = 7</i>.<br />
<br />
<b>Function Calls</b><br />
<br />
Arithmetic operations are one way of building out our expressions, but what about invoking functions? What use a differentiator that can't tell you the derivative of <i>sin</i> is <i>cos</i>, while that of <i>cos</i> is <i>-sin</i>?<br />
<br />
Elementary functions in the <i>System.Linq.Expression</i> hierarchy are represented by the <i>MethodCallExpression</i> class. The <i>Function()</i> code below generates such an expression from the correspondingly named method of the <i>Math</i> class, obtained via reflection. Below it are listed a generous dozen or so concrete functions, all which happen to be implemented in <i>Math</i>; and below these, a further group showing how functions "missing" from <i>Math</i>, such as the reciprocal trigonometrics and hyperbolics, as well as their inverses, can be built out of expressions in the elementary ones.<br />
<br />
Naturally, the input to any functional expression can itself be an arbitrary expression tree, maybe containing further nested function calls, etc.<br />
<pre style="background-color: white; margin: 0em; overflow: auto;"><code style="color: black; font-family: "consolas" , "courier new" , "courier" , monospace; font-size: 10pt;">
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression Function(<span style="color: blue;">string</span> functionName, Expression e) =>
Expression.Call(<span style="color: blue;">typeof</span>(Math).GetMethod(functionName, <span style="color: blue;">new</span>[] { <span style="color: blue;">typeof</span>(<span style="color: blue;">double</span>) }), e);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression Abs(<span style="color: blue;">this</span> Expression e) => Function(<span style="color: #a31515;">"Abs"</span>, e);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression Sqrt(<span style="color: blue;">this</span> Expression e) => Function(<span style="color: #a31515;">"Sqrt"</span>, e);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression Exp(<span style="color: blue;">this</span> Expression e) => Function(<span style="color: #a31515;">"Exp"</span>, e);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression Log(<span style="color: blue;">this</span> Expression e) => Function(<span style="color: #a31515;">"Log"</span>, e);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression Log10(<span style="color: blue;">this</span> Expression e) => Function(<span style="color: #a31515;">"Log10"</span>, e);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression Sin(<span style="color: blue;">this</span> Expression e) => Function(<span style="color: #a31515;">"Sin"</span>, e);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression Cos(<span style="color: blue;">this</span> Expression e) => Function(<span style="color: #a31515;">"Cos"</span>, e);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression Tan(<span style="color: blue;">this</span> Expression e) => Function(<span style="color: #a31515;">"Tan"</span>, e);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression Asin(<span style="color: blue;">this</span> Expression e) => Function(<span style="color: #a31515;">"Asin"</span>, e);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression Acos(<span style="color: blue;">this</span> Expression e) => Function(<span style="color: #a31515;">"Acos"</span>, e);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression Atan(<span style="color: blue;">this</span> Expression e) => Function(<span style="color: #a31515;">"Atan"</span>, e);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression Sinh(<span style="color: blue;">this</span> Expression e) => Function(<span style="color: #a31515;">"Sinh"</span>, e);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression Cosh(<span style="color: blue;">this</span> Expression e) => Function(<span style="color: #a31515;">"Cosh"</span>, e);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression Tanh(<span style="color: blue;">this</span> Expression e) => Function(<span style="color: #a31515;">"Tanh"</span>, e);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Sec(<span style="color: blue;">this</span> Expression e) => Reciprocal(Cos(e));
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Csc(<span style="color: blue;">this</span> Expression e) => Reciprocal(Sin(e));
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Cot(<span style="color: blue;">this</span> Expression e) => Reciprocal(Tan(e));
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression Asec(<span style="color: blue;">this</span> Expression e) => Acos(Reciprocal(e));
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression Acsc(<span style="color: blue;">this</span> Expression e) => Asin(Reciprocal(e));
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression Acot(<span style="color: blue;">this</span> Expression e) => Atan(Reciprocal(e));
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Sech(<span style="color: blue;">this</span> Expression e) => Reciprocal(Cosh(e));
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Csch(<span style="color: blue;">this</span> Expression e) => Reciprocal(Sinh(e));
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression Coth(<span style="color: blue;">this</span> Expression e) => Reciprocal(Tanh(e));
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression ArcSinh(<span style="color: blue;">this</span> Expression e) => Log(e.Plus(Sqrt(e.Squared().Plus(1))));
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression ArcCosh(<span style="color: blue;">this</span> Expression e) => Log(e.Plus(Sqrt(e.Squared().Minus(1))));
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression ArcTanh(<span style="color: blue;">this</span> Expression e) => Log(1.Plus(e).Over(1.Minus(e))).Over(2);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression ArcSech(<span style="color: blue;">this</span> Expression e) => Log(1.Plus(Sqrt(1.Minus(e.Squared()))).Over(e));
<span style="color: blue;">public</span> <span style="color: blue;">static</span> MethodCallExpression ArcCsch(<span style="color: blue;">this</span> Expression e) => Log(1.Plus(Sqrt(1.Plus(e.Squared()))).Over(e));
<span style="color: blue;">public</span> <span style="color: blue;">static</span> BinaryExpression ArcCoth(<span style="color: blue;">this</span> Expression e) => Log(e.Plus(1).Over(e.Minus(1))).Over(2);
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> TestTrigonometricExpression()
{
<span style="color: blue;">var</span> f = Sin(x).Squared().Plus(Cos(x).Squared()); <span style="color: green;">// f(x) = sin²x+cos²x</span>
Check(<span style="color: #a31515;">"((Sin(x) ^ 2) + (Cos(x) ^ 2))"</span>, f.AsString()); <span style="color: green;">// Check the built expression formula</span>
<span style="color: blue;">double</span> p3 = Math.PI / 3, s = Math.Sin(p3), c = Math.Cos(p3); <span style="color: green;">// Check the expression value at x = π/3</span>
Check(Math.Pow(s, 2) + Math.Pow(c, 2), f.AsDouble(p3)); <span style="color: green;">// (should be 1)</span>
}
</code></pre><br />
In your app level code, add a call to <i>Differentiator.TestTrigonometricExpression()</i>, which builds the expression <i>sin²x + cos²x</i>: the LHS of the most famous trigonometric identity, whose RHS is <i>1</i> for all values of <i>x</i>. The new test method checks, first using <i>AsString()</i>, that the correct expression has in fact been built; then, using <i>AsDouble()</i>, that it evaluates to <i>1</i> as expected, in this case when supplied with <i>π/3</i> as a parameter. At runtime, your debug window should display a further two comforting lines of verification:<br />
<blockquote class="tr_bq">OK: ((Sin(x) ^ 2) + (Cos(x) ^ 2))<br />
OK: 1</blockquote><b>Finally, A Derivative</b><br />
<br />
Can't finish this article without a look at some actual differentiation, so let's add the function <i>D()</i> that differentiates expressions. It needs to handle just these four cases:<br />
<ul><li>a constant;</li>
<li><i>x</i> itself;</li>
<li>a function call;</li>
<li>an artithmetic operation (unary or binary).</li>
</ul>The third and fourth cases here have potential for recursion. At long last, here is the code to differentiate any expression:<br />
<pre style="background-color: white; margin: 0em; overflow: auto;"><code style="color: black; font-family: "consolas" , "courier new" , "courier" , monospace; font-size: 10pt;">
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Expression D(<span style="color: blue;">this</span> Expression e)
{
<span style="color: blue;">if</span> (e <span style="color: blue;">is</span> ConstantExpression) <span style="color: blue;">return</span> Constant(0); <span style="color: green;">// d(c)/dx = 0</span>
<span style="color: blue;">if</span> (e <span style="color: blue;">is</span> ParameterExpression) <span style="color: blue;">return</span> Constant(1); <span style="color: green;">// d(x)/dx = 1</span>
<span style="color: blue;">if</span> (e <span style="color: blue;">is</span> MethodCallExpression m)
{
<span style="color: blue;">var</span> f = m.Arguments[0];
<span style="color: blue;">return</span> DifferentiateFunction(m.Method.Name, f).Times(D(f)); <span style="color: green;">// Chain Rule</span>
}
<span style="color: blue;">if</span> (e <span style="color: blue;">is</span> UnaryExpression u)
{
<span style="color: blue;">var</span> f = D(u.Operand);
<span style="color: blue;">return</span> u.NodeType == ExpressionType.UnaryPlus ? f : Negate(f);
}
<span style="color: blue;">if</span> (e <span style="color: blue;">is</span> BinaryExpression b)
{
Expression f = b.Left, g = b.Right;
<span style="color: blue;">switch</span> (b.NodeType)
{
<span style="color: blue;">case</span> ExpressionType.Add: <span style="color: green;">// (f+g)' = f'+g'</span>
<span style="color: blue;">return</span> D(f).Plus(D(g));
<span style="color: blue;">case</span> ExpressionType.Subtract: <span style="color: green;">// (f-g)' = f'-g'</span>
<span style="color: blue;">return</span> D(f).Minus(D(g));
<span style="color: blue;">case</span> ExpressionType.Multiply: <span style="color: green;">// (fg)' = f'g+fg'</span>
<span style="color: blue;">return</span> D(f).Times(g).Plus(f.Times(D(g)));
<span style="color: blue;">case</span> ExpressionType.Divide: <span style="color: green;">// (f÷g)' = (f'g-fg')÷(g^2) = f'÷g-fg'÷(g^2)</span>
<span style="color: blue;">return</span> D(f).Over(g).Minus(f.Times(D(g)).Over(g.Squared()));
<span style="color: blue;">case</span> ExpressionType.Power: <span style="color: green;">// (f^g)' = (f^g)*(f'g÷f+g'Log(f)) = (f'g+fg'Log(f))f^(g-1)</span>
<span style="color: blue;">return</span> D(f).Times(g).Plus(f.Times(D(g)).Times(Log(f))).Times(f.Power(g.Minus(1)));
}
}
<span style="color: blue;">throw</span> <span style="color: blue;">new</span> InvalidOperationException();
}
<span style="color: blue;">private</span> <span style="color: blue;">static</span> Expression DifferentiateFunction(<span style="color: blue;">string</span> methodName, Expression x)
{
<span style="color: blue;">switch</span> (methodName)
{
<span style="color: blue;">case</span> <span style="color: #a31515;">"Abs"</span>: <span style="color: blue;">return</span> x.Over(Abs(x)); <span style="color: green;">// d(|x|)/dx = x/|x|</span>
<span style="color: blue;">case</span> <span style="color: #a31515;">"Sqrt"</span>: <span style="color: blue;">return</span> x.Power(-0.5).Over(2); <span style="color: green;">// d(√x)/dx = 1/(2√x)</span>
<span style="color: blue;">case</span> <span style="color: #a31515;">"Exp"</span>: <span style="color: blue;">return</span> Exp(x); <span style="color: green;">// d(eˣ)/dx = eˣ</span>
<span style="color: blue;">case</span> <span style="color: #a31515;">"Log"</span>: <span style="color: blue;">return</span> Reciprocal(x); <span style="color: green;">// d(ln(x))/dx = 1/x</span>
<span style="color: blue;">case</span> <span style="color: #a31515;">"Log10"</span>: <span style="color: blue;">return</span> Reciprocal(x).Times(Math.Log10(Math.E)); <span style="color: green;">// d(log₁₀(x))/dx = log₁₀(e)/x</span>
<span style="color: blue;">case</span> <span style="color: #a31515;">"Sin"</span>: <span style="color: blue;">return</span> Cos(x); <span style="color: green;">// d(sin(x))/dx = cos(x)</span>
<span style="color: blue;">case</span> <span style="color: #a31515;">"Cos"</span>: <span style="color: blue;">return</span> Negate(Sin(x)); <span style="color: green;">// d(cos(x))/dx = -sin(x)</span>
<span style="color: blue;">case</span> <span style="color: #a31515;">"Tan"</span>: <span style="color: blue;">return</span> Cos(x).Power(-2); <span style="color: green;">// d(tan(x))/dx = sec²(x)</span>
<span style="color: blue;">case</span> <span style="color: #a31515;">"Asin"</span>: <span style="color: blue;">return</span> 1.Minus(x.Squared()).Power(-0.5); <span style="color: green;">// d(arcsin(x))/dx = 1/√(1-x²)</span>
<span style="color: blue;">case</span> <span style="color: #a31515;">"Acos"</span>: <span style="color: blue;">return</span> Negate(1.Minus(x.Squared()).Power(-0.5)); <span style="color: green;">// d(arccos(x))/dx = -1/√(1-x²)</span>
<span style="color: blue;">case</span> <span style="color: #a31515;">"Atan"</span>: <span style="color: blue;">return</span> 1.Over(1.Plus(x.Squared())); <span style="color: green;">// d(arctan(x))/dx = 1/(1+x²)</span>
<span style="color: blue;">case</span> <span style="color: #a31515;">"Sinh"</span>: <span style="color: blue;">return</span> Cosh(x); <span style="color: green;">// d(sinh(x))/dx = cosh(x)</span>
<span style="color: blue;">case</span> <span style="color: #a31515;">"Cosh"</span>: <span style="color: blue;">return</span> Sinh(x); <span style="color: green;">// d(cosh(x))/dx = sinh(x)</span>
<span style="color: blue;">case</span> <span style="color: #a31515;">"Tanh"</span>: <span style="color: blue;">return</span> 1.Minus(Tanh(x).Squared()); <span style="color: green;">// d(tanh(x))/dx = 1-tanh²(x)</span>
}
<span style="color: blue;">throw</span> <span style="color: blue;">new</span> InvalidOperationException();
}
</code></pre><br />
<i>DifferentiateFunction()</i> provides the basic template for a function's derivative, without actually applying the chain rule to its argument <i>Expression</i>. That's why it has been implemented as a private helper function; calling it out of context could result in incorrect results if chain rule application was omitted by mistake.<br />
<a id="Simplify"></a><br />
<b>Algebraic Simplification</b><br />
<br />
Still we're not quite ready to supply the final batch of unit tests, stick a fork in it and call it done. While the <i>D()</i> method may be returning strictly correct results, good enough for numeric applications such as graphing, algebraically they are a bit of a mess. For example, differentiating any candidate containing a function call, even one as simple as <i>Sin(x)</i>, results in a clumsy <i>*1</i> becoming appended to the output expression. Some level of simplification is clearly needed to provide visually acceptable results. For this reason, <i>D()</i> gets called not directly, but from within a wrapper function <i>Differentiate()</i> which applies a layer of simplification before returning the resultant <i>Expression</i>:<br />
<pre style="background-color: white; margin: 0em; overflow: auto;"><code style="color: black; font-family: "consolas" , "courier new" , "courier" , monospace; font-size: 10pt;">
<span style="color: blue;">public</span> <span style="color: blue;">static</span> Expression Differentiate(<span style="color: blue;">this</span> Expression e) => Simplify(D(e));
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> Expression Simplify(<span style="color:#0000ff;">this</span> Expression e)
{
<span style="color:#0000ff;">if</span> (e <span style="color:#0000ff;">is</span> MethodCallExpression m) <span style="color:#0000ff;">return</span> SimplifyMethodCall(m);
<span style="color:#0000ff;">if</span> (e <span style="color:#0000ff;">is</span> UnaryExpression u) <span style="color:#0000ff;">return</span> SimplifyUnary(u);
<span style="color:#0000ff;">if</span> (e <span style="color:#0000ff;">is</span> BinaryExpression b) <span style="color:#0000ff;">return</span> SimplifyBinary(b);
<span style="color:#0000ff;">return</span> e;
}
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> Expression SimplifyMethodCall(MethodCallExpression m)
{
<span style="color:#0000ff;">var</span> operand = Simplify(m.Arguments[0]);
<span style="color:#0000ff;">if</span> (operand <span style="color:#0000ff;">is</span> ConstantExpression ce)
{
<span style="color:#0000ff;">var</span> c = (<span style="color:#0000ff;">double</span>)ce.Value;
<span style="color:#0000ff;">return</span> Constant(Function(m.Method.Name, x).AsDouble(c));
}
<span style="color:#0000ff;">return</span> Function(m.Method.Name, operand);
}
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> Expression SimplifyUnary(UnaryExpression u)
{
<span style="color:#0000ff;">var</span> operand = Simplify(u.Operand);
<span style="color:#0000ff;">if</span> (operand <span style="color:#0000ff;">is</span> ConstantExpression ce)
{
<span style="color:#0000ff;">var</span> c = (<span style="color:#0000ff;">double</span>)ce.Value;
<span style="color:#0000ff;">return</span> Constant(u.NodeType == ExpressionType.UnaryPlus ? c : -c);
}
<span style="color:#0000ff;">return</span> u.NodeType == ExpressionType.UnaryPlus ? operand : Negate(operand);
}
<a id="SimplifyBinary"></a>
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> Expression SimplifyBinary(BinaryExpression b)
{
b = Expression.MakeBinary(b.NodeType, Simplify(b.Left), Simplify(b.Right));
<span style="color:#0000ff;">if</span> (b.Left <span style="color:#0000ff;">is</span> ConstantExpression ce1)
{
<span style="color:#0000ff;">var</span> c1 = (<span style="color:#0000ff;">double</span>)ce1.Value;
<span style="color:#0000ff;">if</span> (b.Right <span style="color:#0000ff;">is</span> ConstantExpression ce2)
<span style="color:#0000ff;">return</span> Constant(b.NodeType, c1, (<span style="color:#0000ff;">double</span>)ce2.Value);
<span style="color:#0000ff;">switch</span> (b.NodeType)
{
<span style="color:#0000ff;">case</span> ExpressionType.Add: <span style="color:#008000;">// c + x → x + c</span>
<span style="color:#0000ff;">case</span> ExpressionType.Multiply: <span style="color:#008000;">// c * x → x * c</span>
b = Expression.MakeBinary(b.NodeType, b.Right, b.Left);
<span style="color:#0000ff;">break</span>;
<span style="color:#0000ff;">case</span> ExpressionType.Subtract:
<span style="color:#0000ff;">if</span> (c1 == 0) <span style="color:#0000ff;">return</span> Negate(b.Right); <span style="color:#008000;">// 0 - x → -x</span>
<span style="color:#0000ff;">break</span>;
<span style="color:#0000ff;">case</span> ExpressionType.Divide:
<span style="color:#0000ff;">if</span> (c1 == 0) <span style="color:#0000ff;">return</span> Constant(0); <span style="color:#008000;">// 0 ÷ x → 0</span>
<span style="color:#0000ff;">break</span>;
<span style="color:#0000ff;">case</span> ExpressionType.Power:
<span style="color:#0000ff;">if</span> (c1 == 0) <span style="color:#0000ff;">return</span> Constant(0); <span style="color:#008000;">// 0 ^ x → 0</span>
<span style="color:#0000ff;">if</span> (c1 == 1) <span style="color:#0000ff;">return</span> Constant(1); <span style="color:#008000;">// 1 ^ x → 1</span>
<span style="color:#0000ff;">break</span>;
}
}
<span style="color:#0000ff;">if</span> (b.Right <span style="color:#0000ff;">is</span> ConstantExpression ce3)
{
<span style="color:#0000ff;">var</span> c3 = (<span style="color:#0000ff;">double</span>)ce3.Value;
<span style="color:#0000ff;">switch</span> (b.NodeType)
{
<span style="color:#0000ff;">case</span> ExpressionType.Add:
<span style="color:#0000ff;">case</span> ExpressionType.Subtract:
<span style="color:#0000ff;">if</span> (c3 == 0) <span style="color:#0000ff;">return</span> b.Left; <span style="color:#008000;">// x ± 0 → x</span>
<span style="color:#0000ff;">break</span>;
<span style="color:#0000ff;">case</span> ExpressionType.Multiply:
<span style="color:#0000ff;">if</span> (c3 == 0) <span style="color:#0000ff;">return</span> Constant(0); <span style="color:#008000;">// x * 0 → 0</span>
<span style="color:#0000ff;">if</span> (c3 == 1) <span style="color:#0000ff;">return</span> b.Left; <span style="color:#008000;">// x * 1 → x</span>
<span style="color:#0000ff;">if</span> (c3 == -1) <span style="color:#0000ff;">return</span> Negate(b.Left); <span style="color:#008000;">// x * -1 → -x</span>
<span style="color:#0000ff;">break</span>;
<span style="color:#0000ff;">case</span> ExpressionType.Divide:
<span style="color:#0000ff;">if</span> (c3 == 0)
<span style="color:#0000ff;">throw</span> <span style="color:#0000ff;">new</span> DivideByZeroException();
<span style="color:#0000ff;">if</span> (c3 == 1) <span style="color:#0000ff;">return</span> b.Left; <span style="color:#008000;">// x ÷ 1 → x</span>
<span style="color:#0000ff;">if</span> (c3 == -1) <span style="color:#0000ff;">return</span> Negate(b.Left); <span style="color:#008000;">// x ÷ -1 → -x</span>
<span style="color:#0000ff;">break</span>;
<span style="color:#0000ff;">case</span> ExpressionType.Power:
<span style="color:#0000ff;">if</span> (c3 == 0) <span style="color:#0000ff;">return</span> Constant(1); <span style="color:#008000;">// x ^ 0 → 1</span>
<span style="color:#0000ff;">if</span> (c3 == 1) <span style="color:#0000ff;">return</span> b.Left; <span style="color:#008000;">// x ^ 1 → x</span>
<span style="color:#0000ff;">break</span>;
}
<span style="color:#0000ff;">switch</span> (b.NodeType)
{
<span style="color:#0000ff;">case</span> ExpressionType.Add: <span style="color:#008000;">// (x + c1) ± c2 → x + (c1 ± c2)</span>
<span style="color:#0000ff;">case</span> ExpressionType.Subtract: <span style="color:#008000;">// (x - c1) ± c2 → x - (c1 ∓ c2)</span>
<span style="color:#0000ff;">case</span> ExpressionType.Multiply: <span style="color:#008000;">// (x * c1) */ c2 → x * (c1 */ c2)</span>
<span style="color:#0000ff;">case</span> ExpressionType.Divide: <span style="color:#008000;">// (x / c1) */ c2 → x / (c1 /* c2)</span>
<span style="color:#0000ff;">if</span> (!(b.Left <span style="color:#0000ff;">is</span> BinaryExpression c && c.Right <span style="color:#0000ff;">is</span> ConstantExpression ce4))
<span style="color:#0000ff;">break</span>;
<a id="SameOpps"></a> <span style="color:#0000ff;">bool</span> same = c.NodeType == b.NodeType, opps = c.NodeType == Invert(b.NodeType);
<span style="color:#0000ff;">if</span> (!(same || opps))
<span style="color:#0000ff;">break</span>;
<span style="color:#0000ff;">var</span> c4 = (<span style="color:#0000ff;">double</span>)ce4.Value;
<span style="color:#0000ff;">var</span> nodeType = c.NodeType == ExpressionType.Add || c.NodeType == ExpressionType.Subtract
? ExpressionType.Add
: ExpressionType.Multiply;
<span style="color:#0000ff;">if</span> (opps) nodeType = Invert(nodeType);
<span style="color:#0000ff;">return</span> Expression.MakeBinary(c.NodeType, c.Left, Constant(nodeType, c4, c3));
}
}
<span style="color:#0000ff;">return</span> b;
}
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> ExpressionType Invert(<span style="color:#0000ff;">this</span> ExpressionType nodeType)
{
<span style="color:#0000ff;">switch</span> (nodeType)
{
<span style="color:#0000ff;">case</span> ExpressionType.Add: <span style="color:#0000ff;">return</span> ExpressionType.Subtract;
<span style="color:#0000ff;">case</span> ExpressionType.Subtract: <span style="color:#0000ff;">return</span> ExpressionType.Add;
<span style="color:#0000ff;">case</span> ExpressionType.Multiply: <span style="color:#0000ff;">return</span> ExpressionType.Divide;
<span style="color:#0000ff;">case</span> ExpressionType.Divide: <span style="color:#0000ff;">return</span> ExpressionType.Multiply;
}
<span style="color:#0000ff;">throw</span> <span style="color:#0000ff;">new</span> InvalidOperationException();
}
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> ConstantExpression Constant(ExpressionType nodeType, <span style="color:#0000ff;">double</span> c, <span style="color:#0000ff;">double</span> d) =>
Constant(Apply(nodeType, c, d));
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">double</span> Apply(ExpressionType nodeType, <span style="color:#0000ff;">double</span> c, <span style="color:#0000ff;">double</span> d)
{
<span style="color:#0000ff;">switch</span> (nodeType)
{
<span style="color:#0000ff;">case</span> ExpressionType.Add: <span style="color:#0000ff;">return</span> c + d;
<span style="color:#0000ff;">case</span> ExpressionType.Subtract: <span style="color:#0000ff;">return</span> c - d;
<span style="color:#0000ff;">case</span> ExpressionType.Multiply: <span style="color:#0000ff;">return</span> c * d;
<span style="color:#0000ff;">case</span> ExpressionType.Divide:
<span style="color:#0000ff;">if</span> (d == 0)
<span style="color:#0000ff;">throw</span> <span style="color:#0000ff;">new</span> DivideByZeroException();
<span style="color:#0000ff;">return</span> c / d;
<span style="color:#0000ff;">case</span> ExpressionType.Power:
<span style="color:#0000ff;">if</span> (c == 0 && d == 0)
<span style="color:#0000ff;">throw</span> <span style="color:#0000ff;">new</span> InvalidOperationException();
<span style="color:#0000ff;">return</span> Math.Pow(c, d);
}
<span style="color:#0000ff;">throw</span> <span style="color:#0000ff;">new</span> InvalidOperationException();
}
</code></pre><br />
This <i>Simplify()</i> method does little more than apply some arithmetic identity, associativity and commutativity results, to remove some of the redundancy littering the differentiator's output. That's why I've implemented it here as simple, direct logic. There are many more simplification rules to consider, and as such rules are added, it becomes clear that a rule application engine is needed, allowing extension of the list without spaghettification of the logic.<br />
<br />
Now we can at least start writing an acceptable suite of unit tests. Here are some examples:<br />
<pre style="margin:0em; overflow:auto; background-color:#ffffff;"><code style="font-family:Consolas,"Courier New",Courier,Monospace; font-size:10pt; color:#000000;">
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">void</span> TestSimplify(Expression e, <span style="color:#0000ff;">string</span> expected) => Check(expected, Simplify(e).AsString());
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">void</span> TestSimplifications()
{
TestSimplify(x.Plus(6).Plus(2), <span style="color:#a31515;">"(x + 8)"</span>);
TestSimplify(6.Plus(x).Plus(2), <span style="color:#a31515;">"(x + 8)"</span>);
TestSimplify(6.Plus(x.Plus(2)), <span style="color:#a31515;">"(x + 8)"</span>);
TestSimplify(x.Plus(6).Minus(2), <span style="color:#a31515;">"(x + 4)"</span>);
TestSimplify(6.Plus(x).Minus(2), <span style="color:#a31515;">"(x + 4)"</span>);
TestSimplify(x.Minus(6).Plus(2), <span style="color:#a31515;">"(x - 4)"</span>);
TestSimplify(x.Minus(6).Minus(2), <span style="color:#a31515;">"(x - 8)"</span>);
TestSimplify(x.Times(6).Times(2), <span style="color:#a31515;">"(x * 12)"</span>);
TestSimplify(6.Times(x).Times(2), <span style="color:#a31515;">"(x * 12)"</span>);
TestSimplify(6.Times(x.Times(2)), <span style="color:#a31515;">"(x * 12)"</span>);
TestSimplify(x.Times(6).Over(2), <span style="color:#a31515;">"(x * 3)"</span>);
TestSimplify(6.Times(x).Over(2), <span style="color:#a31515;">"(x * 3)"</span>);
TestSimplify(x.Over(2).Times(8), <span style="color:#a31515;">"(x / 0.25)"</span>);
TestSimplify(x.Over(2).Over(8), <span style="color:#a31515;">"(x / 16)"</span>);
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> TestDerivative(Expression e, <span style="color: blue;">string</span> expected) => Check(expected, Differentiate(e).AsString());
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">void</span> TestFunctionDerivatives()
{
TestDerivative(Abs(x), <span style="color:#a31515;">"(x / Abs(x))"</span>); <span style="color:#008000;">// d(|x|)/dx = x/|x|</span>
TestDerivative(Sqrt(x), <span style="color:#a31515;">"((x ^ -0.5) / 2)"</span>); <span style="color:#008000;">// d(√x)/dx = 1/(2√x)</span>
TestDerivative(Exp(x), <span style="color:#a31515;">"Exp(x)"</span>); <span style="color:#008000;">// d(eˣ)/dx = eˣ</span>
TestDerivative(Log(x), <span style="color:#a31515;">"(x ^ -1)"</span>); <span style="color:#008000;">// d(ln(x))/dx = 1/x</span>
TestDerivative(Log10(x), <span style="color:#a31515;">"((x ^ -1) * 0.434294481903252)"</span>); <span style="color:#008000;">// d(log₁₀(x))/dx = log₁₀(e)/x</span>
TestDerivative(Sin(x), <span style="color:#a31515;">"Cos(x)"</span>); <span style="color:#008000;">// d(sin(x))/dx = cos(x)</span>
TestDerivative(Cos(x), <span style="color:#a31515;">"-Sin(x)"</span>); <span style="color:#008000;">// d(cos(x))/dx = -sin(x)</span>
TestDerivative(Tan(x), <span style="color:#a31515;">"(Cos(x) ^ -2)"</span>); <span style="color:#008000;">// d(tan(x))/dx = sec²(x)</span>
TestDerivative(Asin(x), <span style="color:#a31515;">"((1 - (x ^ 2)) ^ -0.5)"</span>); <span style="color:#008000;">// d(arcsin(x))/dx = 1/√(1-x²)</span>
TestDerivative(Acos(x), <span style="color:#a31515;">"-((1 - (x ^ 2)) ^ -0.5)"</span>); <span style="color:#008000;">// d(arccos(x))/dx = -1/√(1-x²)</span>
TestDerivative(Atan(x), <span style="color:#a31515;">"(1 / ((x ^ 2) + 1))"</span>); <span style="color:#008000;">// d(arctan(x))/dx = 1/(x²+1)</span>
TestDerivative(Sinh(x), <span style="color:#a31515;">"Cosh(x)"</span>); <span style="color:#008000;">// d(sinh(x))/dx = cosh(x)</span>
TestDerivative(Cosh(x), <span style="color:#a31515;">"Sinh(x)"</span>); <span style="color:#008000;">// d(cosh(x))/dx = sinh(x)</span>
TestDerivative(Tanh(x), <span style="color:#a31515;">"(1 - (Tanh(x) ^ 2))"</span>); <span style="color:#008000;">// d(tanh(x))/dx = 1-tanh²(x)</span>
}
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">void</span> TestPolynomialDerivative()
{
TestDerivative(x.Power(4).Minus(3.Times(x.Cubed())).Plus(6.Times(x.Squared())).Minus(3.Times(x)).Plus(1),
<span style="color:#a31515;">"(((((x ^ 3) * 4) - ((x ^ 2) * 9)) + (x * 12)) - 3)"</span>); <span style="color:#008000;">// d(x⁴-3x³+6x²-3x+1)/dx = 4x³-9x²+12x-3</span>
}
<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> <span style="color:#0000ff;">void</span> TestChainRule()
{
TestDerivative(Exp(x.Squared()), <span style="color:#a31515;">"(Exp((x ^ 2)) * (x * 2))"</span>); <span style="color:#008000;">// d(exp(x²))/dx = exp(x²)*2x</span>
TestDerivative(Log(Sin(x)), <span style="color:#a31515;">"((Sin(x) ^ -1) * Cos(x))"</span>); <span style="color:#008000;">// d(ln(sin(x)))/dx = cot(x)</span>
TestDerivative(Tan(x.Cubed().Plus(8.Times(x))), <span style="color:#008000;">// d(tan(x³+8x))/dx = sec²(x³+8x)*(3x²+8)</span>
<span style="color:#a31515;">"((Cos(((x ^ 3) + (x * 8))) ^ -2) * (((x ^ 2) * 3) + 8))"</span>);
}
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> TestAll()
{
TestSimplifications();
TestCompoundExpression();
TestTrigonometricExpression();
TestFunctionDerivatives();
TestPolynomialDerivative();
TestChainRule();
}
}
}
</code></pre><br />
<a href="http://mycodehere.blogspot.com/2019/04/graphing-derivatives.html">Next time</a>, by overwhelming public demand (one request has flooded in), I'll be exercising these extension methods in a simple differential graphing application.<br />
<br />
<b>Acknowledgements</b><br />
<ul><li>Thanks are due to my colleague Stuart Stein for his code review with useful comments, particularly the suggestion that I should <i>embrace the expression body syntax</i>.</li>
<li>Thanks also to Pavel Vladov for the free use of his <a href="https://www.pvladov.com/p/syntax-highlighter.html" target="_blank">C# Syntax Highlighter</a> to format the code snippets in this article.</li>
</ul>John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-90856518410157327922019-03-21T22:00:00.001+00:002019-03-27T16:47:43.638+00:00Differentiating f(x)^g(x)<b>Black Pixel Red Pixel</b><br />
<br />
My favourite mathematical "YouTutor" is Steve Chow, the guy behind the superlative <a href="https://www.youtube.com/user/blackpenredpen" target="_blank">blackpenredpen</a> collection of mathematics videos on YouTube. Steve has developed a way of explaining proofs using two or more pen colours, hence the name of his YouTube channel. He holds multiple marker pens in one hand while writing on a whiteboard, deftly switching colours so as to highlight the essential differences between each pair of lines in the mathematical exposition. It's a very effective expository technique.<br />
<br />
Until recently, my number one blackpenredpen video was this one, where Steve uses logarithmic differentiation and the chain rule to prove the power, product, and quotient rules of derivatives. The reason it's so high on my list is because of the additional bonus content at the end, kind of a "hidden track" type deal, where he goes on to solve for the derivative of a function raised to the power of another function.<br />
<br />
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/sP4vqks16cs?start=803" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br />
<br />
Just recently Steve, a keen runner of physical marathons, published a truly marathon mathematical video - my new favourite - in which he performs a hundred and one integration proofs in a single take. I'll let you search for that 6 hour video yourself, and if you feel like sitting through it, may I suggest you limit your accompanying playlist to just these three songs: Steely Dan's "My Old School", Supertramp's "School", and Bowling For Soup's "High School Never Ends". This will help you digest the integration marathon in healthy 15 minute chunks.<br />
<br />
Getting back to the bonus differentiation, I remember thinking, this procedure has the effect of subtracting one from the power of one of the functions. Yet it doesn't use the power rule in its own derivation. Aha, I bet this could be used to derive the power rule itself! And since I was looking for a suitable subject for my first foray into maths typesetting, using LaTeX in the <a href="https://www.overleaf.com/" target="_blank">Overleaf</a> environment, that became my target.<br />
<br />
Here is the resultant PDF. You'll notice the homage to blackpenredpen in some of the text colouring.<br />
<br />
<iframe height="480" src="https://drive.google.com/file/d/16NxY1fW2F1fEqf_JkLi7r1q50YQqhDP1/preview" width="640"></iframe><br />
<br />
The actual Overleaf project which produced this PDF can be viewed <a href="https://www.overleaf.com/read/mpxvdxdbxcfs" target="_blank">here</a>, if you're interested in examining the source (LaTeX is a markup language, just as HTML is a markup language).<br />
<br />
This is the first in a series of articles looking at aspects of differentiation. <a href="https://mycodehere.blogspot.com/2019/03/the-differentiator.html">Next time</a> I'll be presenting a neat & simple differentiator, utilising the Expression Trees feature of Microsoft C#.John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-63726481902810902472019-03-16T21:01:00.000+00:002019-03-17T19:10:37.401+00:00Kedgeree<b>My Curry Here</b><br />
<br />
Smoked haddock kedgeree, with added petits pois and raisins why. Because they seem to go nicely. This recipe has been refined for more than a quarter century <i>(I made it once in 1993, then suddenly again today)</i>. Wife says it's terrific, and she's a fantastic cook, so there's always that.<br />
<br />
Preparation time: 15 minutes.<br />
Cook time: 40 minutes.<br />
Serves 4.<br />
Main Ingredients:<br />
<ul><li><i>300g smoked fillet of haddock</i></li>
<li><i>4 eggs</i></li>
<li><i>100g frozen petits pois</i></li>
<li><i>300 ml milk</i></li>
<li><i>bunch of coriander</i></li>
<li><i>2 bay leaves</i></li>
</ul>Rice Ingredients:<br />
<ul><li><i>300g long grain easy cook rice (well rinsed)</i></li>
<li><i>1 large onion (finely chopped)</i></li>
<li><i>30g raisins</i></li>
<li><i>1 tsp ground coriander</i></li>
<li><i>1 tsp ground turmeric</i></li>
<li><i>2 tsp roasted curry powder</i></li>
<li><i>1 chicken or vegetable stock cube</i></li>
<li><i>2 tbsp rapeseed oil</i></li>
</ul>Method:<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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6r5A13arZDhkYpNo-jb3-avkzbjvClxAyh_mpkChBPEJLZDvRU05L3CyvCw84FkQqTp45Phrr7Zoy9awMNGxCwmHgi7lTUbt3RCe7nEYbc2tkqCE3LfNPClLIQqUzh-qJ6xsU54kT79KN/s1600/20190316_175407.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6r5A13arZDhkYpNo-jb3-avkzbjvClxAyh_mpkChBPEJLZDvRU05L3CyvCw84FkQqTp45Phrr7Zoy9awMNGxCwmHgi7lTUbt3RCe7nEYbc2tkqCE3LfNPClLIQqUzh-qJ6xsU54kT79KN/s400/20190316_175407.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">1. Very important: partition your ingredients into rice and non-rice work areas!</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0UQn_hFw7I3zPa89p4EKFuHZUCL3QRO06Zvhswsi8qRVO7wLZz0d-1kqX8jqnFasvic4sIeBMN2yOfqYyLCZ_hR3VpHED5nIeaH3GztFxDoIx2EU7lsaBM-i903exu6-cILzs7SvwD2s-/s1600/20190316_181212.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0UQn_hFw7I3zPa89p4EKFuHZUCL3QRO06Zvhswsi8qRVO7wLZz0d-1kqX8jqnFasvic4sIeBMN2yOfqYyLCZ_hR3VpHED5nIeaH3GztFxDoIx2EU7lsaBM-i903exu6-cILzs7SvwD2s-/s400/20190316_181212.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">2. Fry the onion in rapeseed oil until translucent (about 5 minutes).</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4GCQgdZBckitv9EW2kIeZDhyphenhyphenXIxZFJsGN_7t5jF-SepsNdf-VMSNxQBDcLL5kenL-s7rmjfridW-vlUaVfYRU7JzgGfyHBPcdhsb6iwF_dbk-208kzbqwZCmeSQaVIhrbGmEyt-fW3RRV/s1600/20190316_182444.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4GCQgdZBckitv9EW2kIeZDhyphenhyphenXIxZFJsGN_7t5jF-SepsNdf-VMSNxQBDcLL5kenL-s7rmjfridW-vlUaVfYRU7JzgGfyHBPcdhsb6iwF_dbk-208kzbqwZCmeSQaVIhrbGmEyt-fW3RRV/s400/20190316_182444.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">3. Add the spices & salt seasoning; stir and continue frying for a couple of minutes.</span><br />
<span style="font-size: small;">4. Add the rice, raisins, crumbled stock cube, and 300ml of water.</span><br />
<span style="font-size: small;">5. Cover and simmer for 5 minutes, while...</span><br />
<span style="font-size: small;">6. ...poaching the haddock and bay in 300ml of milk, until flaky.</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipSRBcDjh4WXwtqMU0Mj_Qc0WClbZ_K9gMqtwI_KLvZ3M2nutcKtrFJb4eLe4u-rOkNEmo1FrohO0ZO-q_Hxwk-WoWUYU5MiNT0wfXWuAoIg-V8h8e1mv79R2TpfmeiR9_7ynVjx9IGTAl/s1600/20190316_183255.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipSRBcDjh4WXwtqMU0Mj_Qc0WClbZ_K9gMqtwI_KLvZ3M2nutcKtrFJb4eLe4u-rOkNEmo1FrohO0ZO-q_Hxwk-WoWUYU5MiNT0wfXWuAoIg-V8h8e1mv79R2TpfmeiR9_7ynVjx9IGTAl/s400/20190316_183255.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">7. Drain the milk from the fish pan into the rice, then roughly flake the fish.</span><br />
<span style="font-size: small;">8. Cover the rice pan and remove from the heat.</span><br />
<span style="font-size: small;">9. Add the petits pois to the fish (they'll defrost almost instantly).</span><br />
<span style="font-size: small;">10. Boil the eggs for 4½ minutes, then plunge them into cold water.</span><br />
<span style="font-size: small;">11. After 5 minutes, peel and quarter the eggs, then add to the fish along with the rice and coriander.</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyw8AGMudHiEGorvogI7NDakQrIrFfIaqr-FDe7F-7xNsOXwwRlX3_oZkmCUSyPK40ZFkj7XAsXg4M1qyHlzN2S0OoausDUg1PG_jI3OCsbHJLAAExFw4JSC7lwjr_5CyPwDgYSzjuYbQr/s1600/20190316_191502.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="1600" data-original-width="900" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyw8AGMudHiEGorvogI7NDakQrIrFfIaqr-FDe7F-7xNsOXwwRlX3_oZkmCUSyPK40ZFkj7XAsXg4M1qyHlzN2S0OoausDUg1PG_jI3OCsbHJLAAExFw4JSC7lwjr_5CyPwDgYSzjuYbQr/s400/20190316_191502.jpg" width="225" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">12. Mix gently, season with black pepper, then serve with any additional herbage</span><br />
<span style="font-size: small;">(e.g. parsley, if you like that sort of thing).</span></td></tr>
</tbody></table>John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-59146198908455028522019-03-01T00:53:00.001+00:002019-03-17T18:49:21.459+00:00Smiddy Shank Soup<b>A Photo Essay</b><br />
<br />
A very hearty and rustic <b><i>Ham Hough Red Lentil Soup</i></b>. Seriously, if they had this recipe in Zelda: Breath of the Wild, it would break the cookery system. It brings health, stamina, attack and defence boosts, while simultaneously providing resistance to heat, cold and electricity. One plate of it would fetch at least two hundred and eighty rupees. Pro tip: for an additional speed boost, replace the carrots with "swift carrots".<br />
<br />
Preparation time: 20 minutes.<br />
Cook time: 3 hours 15 minutes.<br />
Serves 8.<br />
Ingredients:<br />
<ul><li><i>800g <a href="https://www.blairdrummondsmiddy.co.uk/butchery" target="_blank">Blair Drummond Smiddy</a> smoked ham hough (shank)</i></li>
<li><i>2 baking potatoes</i></li>
<li><i>1 large onion</i></li>
<li><i>3 celery stalks with leaves</i></li>
<li><i>6 carrots</i></li>
<li><i>250g red split lentils (well rinsed)</i></li>
<li><i>4 garlic cloves</i></li>
<li><i>2 stock cubes (vegetable or chicken)</i></li>
<li><i>6-8 black peppercorns</i></li>
<li><i>1 teaspoon dried oregano</i></li>
<li><i>1 bouquet garni</i></li>
<li><i>2-3 bay leaves</i></li>
</ul>Method:<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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPTP-zUmcd6SW4BO4VLTXXHABj_jXAWCNwhSAgXE-HagPBClYhT8DMFKpKCxpaicQJKN2jWvolC-4rHNbyI0VF5rRD2waKejZrHd47aF08RWAwayUaIYLnG_8Bvyq_73eBIDLXBohY5_yT/s1600/20190228_122440.jpg" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPTP-zUmcd6SW4BO4VLTXXHABj_jXAWCNwhSAgXE-HagPBClYhT8DMFKpKCxpaicQJKN2jWvolC-4rHNbyI0VF5rRD2waKejZrHd47aF08RWAwayUaIYLnG_8Bvyq_73eBIDLXBohY5_yT/s400/20190228_122440.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">1. To 3 litres of boiling water in a pot, add the ham, bouquet garni, peppercorns and bay.</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbVRBOL3giixh0u_nu384agwwXnwc74aJsRHBh1vHWjLjKFs_WPC78XKpvPfEq6uG9s9oVcdoNGt7Qst3DpZ2evwfEjUzlXYqNFH4M2wzT_qRAGa1Ata11SXzOWJx9ovxxYzdjF6FxyaZe/s1600/20190228_122738.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbVRBOL3giixh0u_nu384agwwXnwc74aJsRHBh1vHWjLjKFs_WPC78XKpvPfEq6uG9s9oVcdoNGt7Qst3DpZ2evwfEjUzlXYqNFH4M2wzT_qRAGa1Ata11SXzOWJx9ovxxYzdjF6FxyaZe/s400/20190228_122738.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">2. Boil for 2½ hours.</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyOaAnxZDk7_ZeYM7M4WjAk69nsP5p34Qp1-K-THDPBX4HqsM6O_t12O5Ca_NPUnPwL58YdaR3DqhJmlgMeWiss1W0kvl7VRFvZncrRzH3zDSfk2xLKa-hH20hTy6PFihf1oocJjbBiaxi/s1600/20190228_122809.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyOaAnxZDk7_ZeYM7M4WjAk69nsP5p34Qp1-K-THDPBX4HqsM6O_t12O5Ca_NPUnPwL58YdaR3DqhJmlgMeWiss1W0kvl7VRFvZncrRzH3zDSfk2xLKa-hH20hTy6PFihf1oocJjbBiaxi/s400/20190228_122809.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">3. Meanwhile, back at the vegetable station...</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhe-f0GLx-FYw4Q-Lf5UBpeSWK624XmXDU2EGCCK6a95DvqlgPOeqsentW8Bu-bYGjxQV2cx5h2v6PhS9JPTCZ9kcDtYZZjnNTlOVFAcQEwQsRaKvon7WZ3x8hPH6qk-XzXEM04kEdhMs4I/s1600/20190228_124435.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhe-f0GLx-FYw4Q-Lf5UBpeSWK624XmXDU2EGCCK6a95DvqlgPOeqsentW8Bu-bYGjxQV2cx5h2v6PhS9JPTCZ9kcDtYZZjnNTlOVFAcQEwQsRaKvon7WZ3x8hPH6qk-XzXEM04kEdhMs4I/s400/20190228_124435.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">...peel the potatoes, onion, celery, carrots & garlic.</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNzkPMNjQZwUbhBbQH1yLRCTNrlsySb7RSGS4Ikuh_ZQCraJg6UbDqRCTq228Bu33fZmMb1awo-ZkLq9FtRdUqO-7j6YuwdpDWq7rRn7UZHX-z0K7U0F-DH0RlJAsG7rxDP8Jkx240NC6m/s1600/20190228_130336.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNzkPMNjQZwUbhBbQH1yLRCTNrlsySb7RSGS4Ikuh_ZQCraJg6UbDqRCTq228Bu33fZmMb1awo-ZkLq9FtRdUqO-7j6YuwdpDWq7rRn7UZHX-z0K7U0F-DH0RlJAsG7rxDP8Jkx240NC6m/s400/20190228_130336.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">4. Dice the potatoes, onion, celery, and four of the carrots.</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnTjiSnaWrcq1KX3mvcckqIM2tyz1xseNWc1xmqovNeF2AOcXEw_H32uHI_f6ziCuFPXUvhEhexGCPduTkM4AosXnJJo87yPOd8RJvlMDNbpJ2dE6Z30EtVqz0Whjb0eg8jz67k4Qx0dWV/s1600/20190228_140849.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnTjiSnaWrcq1KX3mvcckqIM2tyz1xseNWc1xmqovNeF2AOcXEw_H32uHI_f6ziCuFPXUvhEhexGCPduTkM4AosXnJJo87yPOd8RJvlMDNbpJ2dE6Z30EtVqz0Whjb0eg8jz67k4Qx0dWV/s400/20190228_140849.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">5.Crush the garlic and grate the remaining two carrots.</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhEQZMt4t-ssaMbxv19u-m8SiF7JKznaOpR04vAEm9_n5MUYHRWCrBiPK1o82hPKfjlFj4BZdlPsYuRDDyvqez1IYkMV9RxvljM_1LeFWvf_snwRLmV3FgEFo3LhkTeGaIFYutjNl-4_J1i/s1600/20190228_140525.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhEQZMt4t-ssaMbxv19u-m8SiF7JKznaOpR04vAEm9_n5MUYHRWCrBiPK1o82hPKfjlFj4BZdlPsYuRDDyvqez1IYkMV9RxvljM_1LeFWvf_snwRLmV3FgEFo3LhkTeGaIFYutjNl-4_J1i/s400/20190228_140525.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">6. At the 2½ hour mark, remove the ham from the pot.</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOl8dcurj_sAuYuTPjdFX7kx9ALUFhUGVQzXUvW92ETx34e7hJ6whB_uLkJaD71b2QBIOA7I_l-xXr6Go33ZuV34yklsHGcRu2G9KWAjvmIo9Pl5I6K18PPZjtw3KxWDiTEuvxBp4nDMtw/s1600/20190228_140436.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOl8dcurj_sAuYuTPjdFX7kx9ALUFhUGVQzXUvW92ETx34e7hJ6whB_uLkJaD71b2QBIOA7I_l-xXr6Go33ZuV34yklsHGcRu2G9KWAjvmIo9Pl5I6K18PPZjtw3KxWDiTEuvxBp4nDMtw/s400/20190228_140436.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">7. Skim the water, then add the potatoes...</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiV208HsbLxRs3BH-bFb7NMLpVnsw4tIhTzaW_W4aucGtEt4975o3HSt_dRPbXpYM6D9p64Nroe_KQthRj68dn8w7FIC7gpUx4CwBJ7st4aH3Ivi8g_Ed245N2SSZoQ50w_m4BIjvpHo9j_/s1600/20190228_140454.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiV208HsbLxRs3BH-bFb7NMLpVnsw4tIhTzaW_W4aucGtEt4975o3HSt_dRPbXpYM6D9p64Nroe_KQthRj68dn8w7FIC7gpUx4CwBJ7st4aH3Ivi8g_Ed245N2SSZoQ50w_m4BIjvpHo9j_/s400/20190228_140454.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">...the chopped carrots...</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuu5-HGzHpfr6TlLvvmJKVDpMIHW6AW2fbM8Pe-5KfniseuL7ANZbbyYutrpbrtYRDQC-J1rY2F21CQoJ99yGoauCBO5a2s2q5tCtG-Zz7eVN1XHa2pfk_mPUhUYzFbAyOxM8ii7bWaAaP/s1600/20190228_140511.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuu5-HGzHpfr6TlLvvmJKVDpMIHW6AW2fbM8Pe-5KfniseuL7ANZbbyYutrpbrtYRDQC-J1rY2F21CQoJ99yGoauCBO5a2s2q5tCtG-Zz7eVN1XHa2pfk_mPUhUYzFbAyOxM8ii7bWaAaP/s400/20190228_140511.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">...the celery...</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFRpPCYnoHd6DU9wBPorlNJAr_HrVLkmkZ64_vrM-kyl9i9W4NZWuzOksE4R5GCgnv2tuGPQUKGFrOTbVouW-39kgLakfjt5bgbOp3B-TDT2hYdfPHm1JZ4SrtxRWhoudkqjyo5SIf3s3C/s1600/20190228_140529.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFRpPCYnoHd6DU9wBPorlNJAr_HrVLkmkZ64_vrM-kyl9i9W4NZWuzOksE4R5GCgnv2tuGPQUKGFrOTbVouW-39kgLakfjt5bgbOp3B-TDT2hYdfPHm1JZ4SrtxRWhoudkqjyo5SIf3s3C/s400/20190228_140529.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">...the onion...</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8zF9-HLHqDw5OnAQWxWd8v2IzS5TBT06CNNCr-Lc6iO4Q7x0yC-3-fT0qmRH-0MkKRnBAMQAXXNxBPoUFbSCpEjk5JI3pCV8XnrGfKky4oouEgrN8zM1mqSRpfUo0hAr_jSq2DC9S6mxC/s1600/20190228_140900.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8zF9-HLHqDw5OnAQWxWd8v2IzS5TBT06CNNCr-Lc6iO4Q7x0yC-3-fT0qmRH-0MkKRnBAMQAXXNxBPoUFbSCpEjk5JI3pCV8XnrGfKky4oouEgrN8zM1mqSRpfUo0hAr_jSq2DC9S6mxC/s400/20190228_140900.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">...the grated carrots...</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKprcrU7a6MoA-hR82VDbxuZhWDlyoqMM-dyjl90EBPNBcpR2uA1iKf7GtJbu5rlc5K5RTLOjFPCalv7U-4EyWvxORsNee_eO6RA820u9PgRC8QK3fBLaO6t9jccnvAZKJ8hYxR_mq2E_o/s1600/20190228_140912.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKprcrU7a6MoA-hR82VDbxuZhWDlyoqMM-dyjl90EBPNBcpR2uA1iKf7GtJbu5rlc5K5RTLOjFPCalv7U-4EyWvxORsNee_eO6RA820u9PgRC8QK3fBLaO6t9jccnvAZKJ8hYxR_mq2E_o/s400/20190228_140912.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">...the crushed garlic...</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFyOHppRNf7ShRPldjPDBwJS_vXPv71LS2_ifcTt_ChxSS6FwBKNOsLC4ezqF6es0UwA-sPvou3gL21JIv4B7XO9JL0d5n6hlkGQlnkGpbQySXdZvLw0iZpvci0YPoHS4LPn5laEBXwt0y/s1600/20190228_141252.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFyOHppRNf7ShRPldjPDBwJS_vXPv71LS2_ifcTt_ChxSS6FwBKNOsLC4ezqF6es0UwA-sPvou3gL21JIv4B7XO9JL0d5n6hlkGQlnkGpbQySXdZvLw0iZpvci0YPoHS4LPn5laEBXwt0y/s400/20190228_141252.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">...the oregano and stock cubes...</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgt53t4evGeKoAd45Xty9J-77yccUD_yX7xgKgptsPab1o6YZIlgGWEK7AamP9X4A4vNTyue5FuFygXqV4wfJh5fVsW7wYdSGqWQ1BwFFS2myY4ynkl1AVxo2hbCBwZKQrkdQJvr9iQxIbu/s1600/20190228_145558.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgt53t4evGeKoAd45Xty9J-77yccUD_yX7xgKgptsPab1o6YZIlgGWEK7AamP9X4A4vNTyue5FuFygXqV4wfJh5fVsW7wYdSGqWQ1BwFFS2myY4ynkl1AVxo2hbCBwZKQrkdQJvr9iQxIbu/s400/20190228_145558.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">...and finally the rinsed lentils. Simmer for 45 minutes.</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgB2UfPaNavKUs0tm0-8aruwIE2LnkIT6GF8RqahiKcSTZDT6ZE6rXJRG8w6B8at5QvQbVNCPL1nFKNmHD6hjVht9-ZwxVMJKMD_sNklI-FpBI143G96eKm-h6dFmpp-W1zmZs5esKbrRYd/s1600/20190228_150549.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgB2UfPaNavKUs0tm0-8aruwIE2LnkIT6GF8RqahiKcSTZDT6ZE6rXJRG8w6B8at5QvQbVNCPL1nFKNmHD6hjVht9-ZwxVMJKMD_sNklI-FpBI143G96eKm-h6dFmpp-W1zmZs5esKbrRYd/s400/20190228_150549.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">8. Strip the ham off the shank and add back into the soup before serving.</span></td></tr>
</tbody></table><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://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQCB08U6XFZceuasrZqRrHAIj48NwydzYZXk-uOpMadkCkg3GgXGgHnqQazuGO_Xj4UAAMnPzyliWhQnBl83kZj1qIGhkMLROKaE2fk_Ygl7MmkwpJ_imqogxSjM9H23uhV0Xs1Pu0_vQe/s1600/20190228_174957.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="900" data-original-width="1600" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQCB08U6XFZceuasrZqRrHAIj48NwydzYZXk-uOpMadkCkg3GgXGgHnqQazuGO_Xj4UAAMnPzyliWhQnBl83kZj1qIGhkMLROKaE2fk_Ygl7MmkwpJ_imqogxSjM9H23uhV0Xs1Pu0_vQe/s400/20190228_174957.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: small;">9. It sticks tae yer ribs. Serve with a twist of pepper.</span></td></tr>
</tbody></table><b>Acknowledgements</b><br />
<br />
I am gratefully indebted to my beautiful assistant and arm model <a href="https://twitter.com/catsjamas">@catsjamas</a> for her help with the visual production aspects of this project.John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-74383526519448888942018-06-18T00:10:00.003+01:002018-06-18T17:53:11.391+01:00Japanese Numbers<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhU6ruhzoRgwRRht_Kxgq2d1qQ6YICBcE8czxrxbjitqx3PM2VlGkzcWz93nnvm9X2u2uNDGell7LZ8tDLEg366bxDpc77CPo_Rg5I_8LPwBd8bKbLgYi4Ru0IAoJCorsncTbLnSACVLNI9/s1600/Japanese+Numbers+rev+8.3.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="851" data-original-width="1204" height="452" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhU6ruhzoRgwRRht_Kxgq2d1qQ6YICBcE8czxrxbjitqx3PM2VlGkzcWz93nnvm9X2u2uNDGell7LZ8tDLEg366bxDpc77CPo_Rg5I_8LPwBd8bKbLgYi4Ru0IAoJCorsncTbLnSACVLNI9/s640/Japanese+Numbers+rev+8.3.png" width="640" /></a><b>The New Job</b><br />
<br />
Nowadays I'm working in medical research. One recent task was to internationalise some UI, then localise it into Japanese. With little in the way of a clue how to do this, but having built up some Audible audiobook credits over the spring months, I decided to take advantage of my three hour daily commute to learn a little about the Japanese language and culture. After all, there's every chance I'll soon have the opportunity to visit Japan on the company ticket, and I'd like to be able to speak to people in their first language, without causing humongous offence. So I bought a couple of Teach Yourself Japanese audiobooks.<br />
<br />
<b>Linguistic Regularity</b><br />
<br />
Boy, did they do a job on me. You would never think, looking at the Japanese writing system, reputedly one of the most horrendously complex in the world, that this language could be a clean and tidy, logically lovable, stone fox. But I was soon smitten. I mean, just look at some of these features. There are no singular or plural nouns, there's just the noun. The verb <i>to be</i>, one of the most irregular in my native tongue, doesn't conjugate at all; it's just like <i>I be, you be, he she or it be</i>. Month names are like <i>first month</i>, <i>second month</i>, and so on. Dates are in perfect ANSI order: the greater subdivision always comes first, so it's <i>year-month-day</i>, or <i>yyyy-mm-dd</i>. Ideal for sorting by computer! There are only 47 syllables in the language, and every vowel is always pronounced the same.<br />
<br />
Of course that's just the first flush of romance; eventually reality and entropy make themselves heard. There might indeed be so many welcome regularities in Japanese, but then they go and spoil it all by saying something stupid like "there are three levels of formality for every sentence", or "men and women speak with subtly different inflections." This last, together with the fact that most teachers of Japanese are female, has the interesting consequence that male learners usually, unwittingly, start off sounding quite effeminate.<br />
<br />
<b>Numeric Logicality</b><br />
<br />
But then there are the numbers. The Japanese counting system is beautifully simple and regular, with very few exceptions in structure and pronunciation. Again, the first idealistic impression is somewhat spoiled by reality, this time by the introduction of "counters". These are little particles which must be appended to numbers, when counting objects. Surprisingly, the particular counter used is often based upon the physical appearance of the thing being counted. Then again, certain numbers cannot be used in counting certain types of thing, but must be substituted with other special purpose counters...<br />
<br />
Still, the basic counting system is quite easy to learn, interesting too, and if nothing else can at least be used while you count through the steps of kata in your chosen martial art (mine is press-ups-do). I considered it potentially a big win, both mathematically and scientifically, to count as high as I possibly could using this system. As a study aid I made a little text file, delighted to realise that all the kanji characters and superscript numerals available in unicode were supported in Notepad. Printed out and laminated in A6, it became a pocket reference card. Later in A4 format, it would become a placemat suitable, used in conjunction with a transparent plate, for learning your ichi-ni-san while eating your sushi.<br />
<br />
<b>Original Text Version</b><br />
<br />
The version below can be copied to the clipboard as text, then saved in Notepad. Just remember to select one of the Unicode options, such as UTF-8, from the Encoding drop-down list.<br />
<br />
<pre>0 〇/零 zero/rei JAPANESE NUMBERS REFERENCE CARD * Learn these exceptions!
1 10 100 1,000 10,000</pre>
<pre>1 一 ichi 十 jū 百 hyaku 千 sen 一万 ichi-man
2 二 ni 二十 ni-jū 二百 ni-hyaku 二千 ni-sen 二万 ni-man
3 三 san 三十 san-jū 三百 san-byaku * 三千 san-zen * 三万 san-man
4 四 yon 四十 yon-jū 四百 yon-hyaku 四千 yon-sen 四万 yon-man
5 五 go 五十 go-jū 五百 go-hyaku 五千 go-sen 五万 go-man
6 六 roku 六十 roku-jū 六百 ro-ppyaku * 六千 roku-sen 六万 roku-man
7 七 nana 七十 nana-jū 七百 nana-hyaku 七千 nana-sen 七万 nana-man
8 八 hachi 八十 hachi-jū 八百 ha-ppyaku * 八千 ha-ssen * 八万 hachi-man
9 九 kyū 九十 kyū-jū 九百 kyū-hyaku 九千 kyū-sen 九万 kyū-man
10⁵ 十万 jū-man 10⁶ 百万 hyaku-man 10⁷ 千万 sen-man
10⁸ 10¹² 10¹⁶ 10²⁰ 10²⁴ 10²⁸ 10³² 10³⁶ 10⁴⁰ 10⁴⁴ 10⁴⁸
億 兆 京 垓 𥝱/秭 穣 溝 澗 正 載 極
oku chō kei gai jo/shi jō kō kan sei sai goku
10¹² 一兆 itchō 8x10¹² 八兆 hatchō 10¹³ 十兆 jutchō †
10¹⁶ 一京 ikkei 6x10¹⁶ 六京 rokkei 8x10¹⁶ 八京 hakkei
10¹⁷ 十京 jukkei † † Multiples of 10: change -jū to -jutchō or -jukkei
10¹⁸ 百京 hyakkei ‡ ‡ Multiples of 100: change -ku to -kkei
10⁵²⸍⁵⁶ 10⁵⁶⸍⁶⁴ 10⁶⁰⸍⁷² 10⁶⁴⸍⁸⁰ 10⁶⁸⸍⁸⁸
恒河沙 阿僧祇 那由他/那由多 不可思議 無量大数
gōgasha asōgi nayuta fukashigi muryōtaisū
</pre>
<br />
<b>How It Works</b><br />
<br />
The first thing to do is memorise the top left column, digits 1 to 9, and 10: <i>ichi, ni, san, yon, go, roku, nana, hachi, kyū, jū</i>. Note that the horizontal bar along the top of a vowel just makes it sound "long" - some guides use double vowels for this purpose.<br />
<br />
Next, to form the numbers 11 to 19, just use literally ten-one, ten-two, ten-three, etc. So that's <i>jū-ichi, jū-ni, jū-san</i>, etc.<br />
<br />
Multiples of ten up to ninety are found in the second column: <i>ni-jū</i> (20) is literally two tens, <i>san-jū</i> (30) three tens, and so on. Intermediate numbers are simply concatenated or added together, so 42 is "four tens two", or <i>yon-jū-ni</i>.<br />
<br />
It's a similar story for multiples of 100, found in the third column, although there are three little exceptions to be aware of - more or less subtle variations in pronunciation. I've marked these with asterisks. So although 3 is <i>san</i> and 100 is <i>hyaku</i>, 300 is not <i>san-hyaku</i> but <i>san-byaku</i>; note the hard 'b' sound here, and also the hard 'pp' in 600 (<i>ro-ppyaku</i> instead of <i>roku-hyaku</i>) and 800 (<i>ha-ppyaku</i> rather than <i>hachi-hyaku</i>).<br />
<br />
Multiples of 1,000, as seen in the fourth column, also have exceptions marked at 3,000 and 8,000, although there isn't an exception for 6,000, which is just business as usual: <i>roku-sen</i>.<br />
<br />
<b>Chinese Roots</b><br />
<br />
Reaching 10,000 there's another little surprise in store, and quite an important one. It might help a bit to think first about English powers of 10. Notice that 100 (one hundred) and 1,000 (one thousand) both begin with the word "one", whereas 10 (ten) and 10,000 (ten thousand) don't. But actually, there is an old English number "myriad", one of whose older meanings is literally ten thousand. We might therefore think of 10,000 as "one myriad", and in fact that's similar to what the Japanese, following on from Chinese tradition, actually do. Ten thousand is best translated into Japanese via "one myriad", hence <i>ichi-man</i>.<br />
<br />
With that in mind, column five otherwise proceeds much as before: 20,000 is <i>ni-man</i> (two myriad), 30,000 is <i>san-man</i>, and so on without further exceptions. But we have just passed an important milestone here. Just as our big numbers tend to group themselves into powers of 1,000 such as thousands, millions, billions, trillions, and so on, each time multiplying by 10³ and adding another comma, so the oriental tradition is to partition into powers of 10,000, and groups of four digits. The importance of this structure is reflected in the use of the <i>ichi</i> (one) prefix for 10,000: not just <i>man</i> (myriad), but <i>ichi-man</i> (one myriad).<br />
<br />
The same caveat will apply to all subsequent powers of 10,000. But before getting to those, look at the next few powers which are <i>not</i> multiples of 4, and <i>don't</i> need the <i>ichi</i> prefix. As shown immediately below the main table, we already knew how to construct:<br />
<ul>
<li>one hundred thousand (100,000 = 10⁵ = ten myriad = <i>jū-man</i>),</li>
<li>one million (1,000,000 = 10⁶ = hundred myriad = <i>hyaku-man</i>), and</li>
<li>ten million (10,000,000 = 10⁷ = thousand myriad = <i>sen-man</i>).</li>
</ul>
Next, we shall alight upon the Big Sequence, the powers of 10⁴ spanning 10⁸ to 10⁴⁸ (one quindecillion!), listed in the second horizontal table.<br />
<br />
<div style="text-align: right;">
</div>
<b>To Infinity And Beyond (almost)</b><br />
<br />
So, now we avail ourselves of the long line of Powers of Myriad <i>(that's definitely the name of my next witching and wizarding trilogy, watch out JK)</i>. We have reached 10⁸ which is labelled <i>oku</i>, but since 8 is a multiple of 4, we have to prepend this with <i>ichi</i>. Thus, 10⁸, or a hundred million, becomes <i>ichi-oku</i>. Two hundred million is <i>ni-oku</i>. And so on.<br />
<br />
Things chug along quite nicely until we reach one trillion, or 10¹². Everything we've covered so far leads to the expectation <i>ichi-chō</i>, until we stop and consider the clumsiness of that utterance. Surely that is the reason the wise Japanese have decided to contract it to <i>itchō</i>. Similarly, eight trillion becomes not <i>hachi-chō</i> but <i>hatchō</i>, while ten trillion is not <i>jū-chō</i> but <i>jutchō</i> (with the shorter <i>u</i>).<br />
<br />
Similar exceptions apply to the next big multiplier, 10¹⁶ (which becomes not <i>ichi-kei</i> but <i>ikkei</i>), as well as multiples of this by 6, 8, 10 and 100. There is a pattern to the modifications in these cases, codified by the daggered comments in the bottom half of the reference card (or placemat).<br />
<br />
Finally, we reach the bottom 3 rows of the card, where the only reliable historical sources contradict each other. In a denouement reminiscent of the war between American and British billions (see Wikipedia for details, <a href="https://en.wikipedia.org/wiki/Names_of_large_numbers">https://en.wikipedia.org/wiki/Names_of_large_numbers</a>), there's a forking schism in which the same terms are used to count, simultaneously and incompatibly, using multiples of 10⁴ and/or 10⁸, into an uncaring infinity. Of limited scientific use admittedly, but why not take advantage of the Creative Commons licence, and print out and laminate your own set of Japanese Numbers placemats?John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-6021538004651138382016-11-17T11:40:00.000+00:002016-11-17T13:28:56.644+00:00My Collie Here<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1PHvenEomIo93DoGGV5A4ytzlxFcNeTvPyPj97arn6OZj6tSk16rAkx5WM_J28gIo2UDgpwrZD98n6UYvvikgbfjmGA6LW1Rdc-D4Tm8LNzN7b38evuc3FHrEqpzOPMxR68ryavgCyMTf/s1600/20161016_154820_cropped.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="280" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1PHvenEomIo93DoGGV5A4ytzlxFcNeTvPyPj97arn6OZj6tSk16rAkx5WM_J28gIo2UDgpwrZD98n6UYvvikgbfjmGA6LW1Rdc-D4Tm8LNzN7b38evuc3FHrEqpzOPMxR68ryavgCyMTf/s320/20161016_154820_cropped.jpg" width="320" /></a><b>Meet our Archie</b><br />
<br />
Every cat needs a collie, and this is Cleo's new wee brother, Archie.<br />
<br />
"Wee" brother is right. Although he's grown to double her size, in the 4½ weeks he's been with us - compare the picture on the right with the one below, taken 4½ weeks later - he still gets "Wee Archie" due to his determination to cover the whole house in a uniform coat of wee!<br />
<br />
Actually, that's a bit unfair. When he came to us at exactly 8 weeks old, he was already trained to go on the puppy training pads, and had more or less a 100% record on those. But that was only for wee; anything more demanding would be deposited instantly and immediately, just wherever he happened to be, as the mood took him. Now that we've started trying to take away the training pads, the result - with apologies to Kevin Bacon - has been a return to Everything Everywhere.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4n5yazEBET16asUY1u9QQ8wZzlYQTp3L8Vfb-EIvS98JZjrStYMsKtnY5LHt5SQgML8ft4juzhPtMaB9nK4bJciKtls_oUxxbIbLN1XtCmo-O-eADJqluJJ8PBas1XA7UVxxQPR8svFFD/s1600/20161117_085628_cropped.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4n5yazEBET16asUY1u9QQ8wZzlYQTp3L8Vfb-EIvS98JZjrStYMsKtnY5LHt5SQgML8ft4juzhPtMaB9nK4bJciKtls_oUxxbIbLN1XtCmo-O-eADJqluJJ8PBas1XA7UVxxQPR8svFFD/s320/20161117_085628_cropped.jpg" width="317" /></a><b>End of the Tunnel</b><br />
<br />
Yesterday he attended his first puppy socialising class at The Dogs Trust, where he did us proud with his precocious knowledge of the Sit, Stay, and Down commands. All credit to Linda there, for her decades of experience training very intelligent border collies.<br />
<br />
Socialising <i>per se</i> was less successful. Archie has had to be kept away from other dogs for longer than is usual, because <b>his breeder failed to immunise him</b> against deadly diseases such as <a href="https://en.wikipedia.org/wiki/Parvovirus">Parvovirus</a>. We didn't find out about this until we took Archie to our own vet, for what we thought would be his second and final inoculation, but which turned out to be a complete restart at day one.<br />
<br />
So yesterday, two weeks later than he should have, he finally got to meet other pups. He was timid and reluctant, and basically failed to do so. However the instructor expressed his confidence that this delayed development can be put right over the coming month.<br />
<br />
Archie and Cleo, everybody!John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-40316294457283370922016-11-10T15:39:00.000+00:002017-02-19T23:27:08.092+00:00FLAC Forensics<b>Frequency Fingerprints</b><br />
<br />
I was asked by a friend if it was possible to check whether some downloaded files were actually faithful to the original WAV format entities, or whether they had in fact been rehydrated from an unfortunate, lossy intermediate MP3 excursion. The files in question were FLAC compressions of the original 3½ hour, 42 track <a href="https://en.wikipedia.org/wiki/Analord" target="_blank">Analord</a> series of electronic pieces by Richard D. James, most of which were only ever issued on 12" vinyl by the artist's now defunct Rephlex Records outlet.<br />
<br />
<i><span style="color: red;">Update: My personal RD James expert informs me that these original 42 tracks were also released in lossless digital format on Rephlex, and that some (or all?) of the subsequently released additional, digital-only tracks were also added to these by Rephlex.</span></i><br />
<br />
I loaded the first of these files, <i>SteppingFilter 101</i>, into <a href="http://www.audacityteam.org/">Audacity</a>, and took a look at the frequency domain graph <i>(Analyze|Plot Spectrum...)</i>. After a brief complaint about only being able to analyze 237.8 seconds of audio at a time, the result was this:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzLS1x74QBCF9Ma7riInOtsZcHMERSYTZ-JYLmTTWaYCtk6XXu-gU-HA9hxx8OdCUC6EuBA__aFYMfVZVRPiXJhUiQVx_6t9kXz8YRlboSDaoIVr6d5dWj2zXFLM00eLgQQwfZvqleRlE1/s1600/WavFromFLAC.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzLS1x74QBCF9Ma7riInOtsZcHMERSYTZ-JYLmTTWaYCtk6XXu-gU-HA9hxx8OdCUC6EuBA__aFYMfVZVRPiXJhUiQVx_6t9kXz8YRlboSDaoIVr6d5dWj2zXFLM00eLgQQwfZvqleRlE1/s1600/WavFromFLAC.png" /></a></div><br />
Notice the slight uptick at the extreme right (high frequency) end, around 22kHz. This represents extraneous noise generated by the digital sampling process, which in this case appears to have been set naturally enough to the CD standard stereo setting of 44.1kHz. This effect will be present in any rip, at some frequency or other, and is of a different kind from the artefacts introduced by MP3 processing.<br />
<br />
Next, I used FooBar2000/LAME to convert this file to MP3 format, using the highest available quality, Constant Bit Rate standard preset, namely 320kbps CBR (actually LAME can handle non-ISO bit rates of up to 640kbps via its <i>freeformat</i> option, but very few MP3 players can handle such files).<br />
<br />
The result of this 320kbps conversion has a very obvious steep cutoff at about 20kHz:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjev3Hl92PRvg2dlpRr4lUMfqeU5oAKZBQSrsAS7-ibDpdkr1jDbGdzeWM5UKE7Te6fIgkVFSaEyZPuKLqD9D9HJCLZV_2aZMFZ4zOWhQ4NZIW20STRa9djWRF2y1LNiS5zwKXaMB64A1_w/s1600/WavFromMP3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjev3Hl92PRvg2dlpRr4lUMfqeU5oAKZBQSrsAS7-ibDpdkr1jDbGdzeWM5UKE7Te6fIgkVFSaEyZPuKLqD9D9HJCLZV_2aZMFZ4zOWhQ4NZIW20STRa9djWRF2y1LNiS5zwKXaMB64A1_w/s1600/WavFromMP3.png" /></a></div><br />
Any attempt to convert this back to the WAV format will preserve this telltale high frequency cutoff. Does this mean we can be confident that the source file represents a good, high quality, uncompressed rip from the original vinyl? I would say <i>confident</i>, yes; <i>certain</i>, well, that's another bottle of kippers. We haven't ruled out nonstandard MP3 or other shenanigans with this test alone, but those compression antics are at worst <i>extremely</i> unlikely.John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-17364963375778840192016-09-03T23:13:00.000+01:002018-03-05T15:55:29.913+00:00Sundry Surround Sound Recordings<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiar32a1SkDKZ81HVsZTq6TWS_hRBvZ6dokAYWAbtq9DCzVZPCmgc2eYmr1f7JP2Td8tGxR008X49V9wiCIN2tCzHgDNf60g_oDCoKShh9zdMWNeRLWuNUFnXs6lcnS3_jTMN74Z7g43JiT/s1600/bdp-450-k.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="110" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiar32a1SkDKZ81HVsZTq6TWS_hRBvZ6dokAYWAbtq9DCzVZPCmgc2eYmr1f7JP2Td8tGxR008X49V9wiCIN2tCzHgDNf60g_oDCoKShh9zdMWNeRLWuNUFnXs6lcnS3_jTMN74Z7g43JiT/s200/bdp-450-k.jpg" width="200" /></a></div><b>Planned Obsolescence</b><br />
<br />
At the time of purchase in May 2015, my current universal <i>(DTS/SACD/DVD-A/Blu-ray)</i> player, a Pioneer BDP450, cost me a featherweight £159. Just over a year later it's "unavailable", unless I'm willing to settle for a refurbished model from Ebay. Reasonably priced multichannel DTS/SACD and DVD-Audio players are becoming really hard to find, even from the manufacturers who invented these formats. At present the best available options appear to be:<br />
<ul><li>PIONEER BDPLX58 (£449 at Richer Sounds)</li>
<li>CAMBRIDGE CXU (£799.95)</li>
<li>OPPO BDP105D (£1,099)</li>
<li>PIONEER BDPLX88 (£1,099)</li>
</ul>No wonder then that collectors of surround sound DVD-Audio recordings, and even more so those of multichannel DTS media or SACDs, are feeling increasingly under siege these days. For how much longer will we be able to play these purchases? How many more times will we be able to afford a new player, when the last one packs in and its perishing rubber wheels and belts and lasers can't be replaced?<br />
<br />
This is a partial list of my surround sound recordings by format. It will let me sit and grieve as first DTS, then SACD, then DVD-A, and finally (sooner than you'd think!) Blu-ray playback become impossible. Then I will at least be able to see at a glance each month, exactly what fraction of my collection has now become permanently unavailable to me. It's also a work in progress, since I have no intention of losing the bad habit of buying these wonderful recordings; currently I'm looking forward to the much-delayed Steven Wilson remixes of the early Roxy Music albums.<br />
<style>
#sssr table {
border-collapse: collapse;
border: 1px solid;
color: black;
font-family: sans-serif;
font-size: 14px;
line-height: 22.4px;
margin: auto;
text-align: center;
}
#sssr td {
border: 1px solid;
padding: 0.0em 0.4em;
}
</style><br />
<b>Top 100+ Speciality Multichannel Studio Mixes</b><br />
<br />
This first table lists recordings where a studio engineer (usually Steven Wilson or Jakko Jakszyk ;-) has carefully pored over the source material, and arranged things in space to produce a curated, meticulously arranged, surround sound experience.<br />
<br />
<div id="sssr"><table><tbody>
<tr><td rowspan="2"><b>Artist or Composer</b></td><td rowspan="2"><b>Album Title</b></td><td rowspan="2"><b>Year</b></td><td colspan="3"><b>Physical Media</b></td></tr>
<tr><td><b>SACD</b></td><td><b>DVD‑A</b></td><td><b>Blu‑ray</b></td></tr>
<tr><td>Ian <b>Anderson</b></td> <td><a href="https://www.discogs.com/Ian-Anderson-Homo-Erraticus/release/5633009">Homo Erraticus</a></td><td>2014</td><td></td><td>✓</td><td></td></tr>
<tr><td><b>Bach</b></td> <td><a href="https://www.discogs.com/Bach-Don-Jackson-3-London-Symphony-Orchestra-Bach-Classics/release/3183589">Bach Classics</a></td> <td>17xx</td><td></td><td>✓</td><td></td></tr>
<tr><td><b>Bass Communion</b></td><td><a href="https://www.discogs.com/Bass-Communion-Loss/release/829593">Loss</a></td><td>2006</td><td></td><td>✓</td><td></td></tr>
<tr><td>The <b>Beatles</b></td> <td><a href="https://www.discogs.com/The-Beatles-Love/release/1170155">Love</a></td> <td>2006</td><td></td><td>✓</td><td></td></tr>
<tr><td rowspan="2"><b>Beethoven</b></td> <td><a href="https://www.discogs.com/Beethoven-Don-Jackson-3-London-Symphony-Orchestra-Beethoven-Classics/release/3175894">Beethoven Classics</a></td> <td>18xx</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="http://aixrecords.com/catalog/dvd_av_prem/beethoven_njso_dvd_av_prem.html">Symphony No. 6 "Pastorale"</a></td> <td>1808</td><td></td><td>✓</td><td></td></tr>
<tr><td><b>Blackfield</b></td><td><a href="https://www.discogs.com/Blackfield-V/master/1132699">Blackfield V</a></td><td>2017</td><td></td><td></td><td>✓</td></tr>
<tr><td>David <b>Bowie</b></td><td><a href="https://www.discogs.com/David-Bowie-The-Rise-And-Fall-Of-Ziggy-Stardust-And-The-Spiders-From-Mars/release/1912786">The Rise and Fall of Ziggy Stardust...</a></td><td>1972</td><td>✓</td><td></td><td></td></tr>
<tr><td><b>Caravan</b></td><td><a href="https://www.discogs.com/Caravan-In-The-Land-Of-Grey-And-Pink/release/2943905">In the Land of Grey and Pink</a></td> <td>1971</td><td></td><td>✓</td><td></td></tr>
<tr><td rowspan="2"><b>Eagles</b></td> <td><a href="https://www.discogs.com/Eagles-Hotel-California/release/1591901">Hotel California</a></td> <td>1976</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Eagles-Hell-Freezes-Over/release/2098251">Hell Freezes Over</a></td> <td>1994</td><td>✓</td><td></td><td></td></tr>
<tr><td rowspan="3"><b>ELP</b></td> <td><a href="https://www.discogs.com/Emerson-Lake-Palmer-Tarkus/release/3871920">Tarkus</a></td> <td>1971</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Emerson-Lake-Palmer-Trilogy/release/6980947">Trilogy</a></td> <td>1972</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Emerson-Lake-Palmer-Brain-Salad-Surgery/release/5688332">Brain Salad Surgery</a></td><td>1973</td><td></td><td>✓</td><td></td></tr>
<tr><td><b>Flaming Lips</b></td> <td><a href="https://www.discogs.com/Flaming-Lips-Yoshimi-Battles-The-Pink-Robots-51-DVD/release/3293067">Yoshimi Battles the Pink Robots</a></td> <td>2002</td><td></td><td>✓</td><td></td></tr>
<tr><td><b>Fleetwood Mac</b></td> <td><a href="https://www.discogs.com/Fleetwood-Mac-Rumours/release/8216126">Rumours</a></td> <td>1977</td><td></td><td>✓</td><td></td></tr>
<tr><td rowspan="13"><b>Genesis</b></td> <td><a href="https://www.discogs.com/Genesis-Trespass/release/3904896">Trespass</a></td> <td>1970</td><td>✓</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Genesis-Nursery-Cryme/release/4012706">Nursery Cryme</a></td> <td>1971</td><td>✓</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Genesis-Foxtrot/release/4234604">Foxtrot</a></td> <td>1972</td><td>✓</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Genesis-Selling-England-By-The-Pound/release/3006458">Selling England By The Pound</a></td> <td>1973</td><td>✓</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Genesis-The-Lamb-Lies-Down-On-Broadway/release/2274786">The Lamb Lies Down on Broadway</a></td> <td>1974</td><td>✓</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Genesis-A-Trick-Of-The-Tail/release/4094029">A Trick of the Tail</a></td> <td rowspan="2">1976</td><td>✓</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Genesis-Wind-Wuthering/release/4094047">Wind & Wuthering</a></td> <td>✓</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Genesis-And-Then-There-Were-Three/release/3036251">...And Then There Were Three...</a></td> <td>1978</td><td>✓</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Genesis-Duke/release/7407448">Duke</a></td><td>1980</td><td>✓</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Genesis-Abacab/release/2662543">Abacab</a></td><td>1981</td><td>✓</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Genesis-Genesis/release/1097542">Genesis</a></td><td>1983</td><td>✓</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Genesis-Invisible-Touch/release/1108101">Invisible Touch</a></td> <td>1986</td><td>✓</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Genesis-We-Cant-Dance/release/1281744">We Can't Dance</a></td><td>1991</td><td>✓</td><td>✓</td><td></td></tr>
<tr><td rowspan="3"><b>Gentle Giant</b></td> <td><a href="https://www.discogs.com/Gentle-Giant-Three-Piece-Suite/release/10928015">Three Piece Suite</a></td> <td>1970-2</td><td></td><td></td><td>✓</td></tr>
<tr><td><a href="https://www.discogs.com/Gentle-Giant-Octopus/release/8773513">Octopus</a></td> <td>1972</td><td></td><td></td><td>✓</td></tr>
<tr><td><a href="https://www.discogs.com/Gentle-Giant-The-Power-And-The-Glory/release/5911042">The Power and the Glory</a></td> <td>1974</td><td></td><td></td><td>✓</td></tr>
<tr><td><b>Handel</b></td> <td><a href="https://www.discogs.com/Handel-Don-Jackson-3-London-Symphony-Orchestra-Handels-Water-Garden/release/3179877">Handel's Water Garden</a></td> <td>17xx</td><td></td><td>✓</td><td></td></tr>
<tr><td>Gavin <b>Harrison</b></td> <td><a href="https://www.discogs.com/Gavin-Harrison-Cheating-The-Polygraph/release/6889418">Cheating the Polygraph</a></td> <td>2015</td><td></td><td>✓</td><td></td></tr>
<tr><td rowspan="10"><b>Jethro Tull</b></td> <td><a href="https://www.discogs.com/Jethro-Tull-Stand-Up-The-Elevated-Edition/release/9393575">Stand Up <i>(The Elevated Edition)</i></a></td> <td>1969</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Jethro-Tull-Benefit-A-Collectors-Edition-New-51-Stereo-Mixes-With-Associated-Recordings-1969-1970/release/5481699">Benefit <i>(A Collector's Edition)</i></a></td> <td>1970</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Jethro-Tull-Aqualung-40th-Anniversary-Adapted-Edition/release/8427017">Aqualung</a></td> <td>1971</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Jethro-Tull-Thick-As-A-Brick/release/4024114">Thick as a Brick</a></td><td>1972</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Jethro-Tull-A-Passion-Play-An-Extended-Performance/release/5847604">A Passion Play</a></td> <td>1973</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Jethro-Tull-WarChild-The-40th-Anniversary-Theatre-Edition/release/6349325">War Child</a></td> <td>1974</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Jethro-Tull-Minstrel-In-The-Gallery-40th-Anniversary-La-Grande-%C3%89dition/release/6982491">Minstrel in the Gallery</a></td> <td>1975</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Jethro-Tull-Too-Old-To-Rock-n-Roll-Too-Young-To-Die-The-TV-Special-Edition-Deluxe/release/7828296">Too Old to Rock'n'Roll: Too Young to Die!</a></td> <td>1976</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Jethro-Tull-Songs-From-The-Wood-40th-Anniversary-Edition-The-Country-Set/release/10331165">Songs from the Wood</a></td> <td>1977</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Jethro-Tull-Heavy-Horses-New-Shoes-Edition/release/11643819">Heavy Horses</a></td> <td>1978</td><td></td><td>✓</td><td></td></tr>
<tr><td rowspan="12"><b>King Crimson</b></td> <td><a href="https://www.discogs.com/King-Crimson-In-The-Court-Of-The-Crimson-King-An-Observation-By-King-Crimson/release/1967769">In the Court of the Crimson King</a></td><td>1969</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/King-Crimson-In-The-Wake-Of-Poseidon/release/2506802">In the Wake of Poseidon</a></td> <td rowspan="2">1970</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/King-Crimson-Lizard/release/2030549">Lizard</a></td> <td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/King-Crimson-Islands/release/2490596">Islands</a></td><td>1971</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/King-Crimson-Larks-Tongues-In-Aspic/release/3989194">Larks' Tongues in Aspic</a></td><td>1973</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/King-Crimson-Starless-And-Bible-Black/release/3163858">Starless and Bible Black</a></td> <td rowspan="2">1974</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/King-Crimson-Red/release/2053801">Red</a></td> <td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/King-Crimson-Discipline/release/3156561">Discipline</a></td><td>1981</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/King-Crimson-Beat/release/9343972">Beat</a></td><td>1982</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/King-Crimson-Three-Of-A-Perfect-Pair/release/9355163">Three of a Perfect Pair</a></td><td>1984</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/King-Crimson-THRAK/release/7829797">THRAK</a></td><td>1995</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/King-Crimson-Radical-Action-To-Unseat-The-Hold-Of-Monkey-Mind/release/9004180">Radical Action <i>(To Unseat The Hold...)</i></a></td> <td>2016</td><td></td><td></td><td>✓</td></tr>
<tr><td><b>Marillion</b></td> <td><a href="https://www.discogs.com/Marillion-Misplaced-Childhood-Deluxe-Edition-/release/10583867">Misplaced Childhood</a></td><td>1985</td><td></td><td></td><td>✓</td></tr>
<tr><td rowspan="4">The <b>Moody Blues</b></td> <td><a href="https://www.discogs.com/The-Moody-Blues-With-The-London-Festival-Orchestra-Conducted-By-Peter-Knight-Days-Of-Future-Passed/release/1483969">Days of Future Passed</a></td> <td>1967</td><td>✓</td><td></td><td></td></tr>
<tr><td><a href="https://www.discogs.com/The-Moody-Blues-On-The-Threshold-Of-A-Dream/release/1067629">On the Threshold of a Dream</a></td> <td rowspan="2">1969</td><td>✓</td><td></td><td></td></tr>
<tr><td><a href="https://www.discogs.com/The-Moody-Blues-To-Our-Childrens-Childrens-Children/release/1780373">To Our Children's Children's Children</a></td> <td>✓</td><td></td><td></td></tr>
<tr><td><a href="https://www.discogs.com/The-Moody-Blues-Seventh-Sojourn/release/1790679">Seventh Sojourn</a></td> <td>1972</td><td>✓</td><td></td><td></td></tr>
<tr><td><b>Mozart</b></td> <td><a href="https://www.discogs.com/Mozart-Don-Jackson-3-London-Symphony-Orchestra-Mozart-Classics/release/3179864">Mozart Classics</a></td><td>17xx</td><td></td><td>✓</td><td></td></tr>
<tr><td rowspan="4">Mike <b>Oldfield</b></td> <td><a href="https://www.discogs.com/Mike-Oldfield-Tubular-Bells/release/2394363">Tubular Bells</a></td> <td>1973</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Mike-Oldfield-Hergest-Ridge/release/2381864">Hergest Ridge</a></td> <td>1974</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Mike-Oldfield-Ommadawn/release/2389091">Ommadawn</a></td> <td>1975</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Mike-Oldfield-Five-Miles-Out/release/4874105">Five Miles Out</a></td> <td>1982</td><td></td><td>✓</td><td></td></tr>
<tr><td rowspan="4"><b>Opeth</b></td><td><a href="https://www.discogs.com/Opeth-Still-Life/release/1298612">Still Life</a></td> <td>1999</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Opeth-Deliverance-Damnation/release/7665822">Deliverance & Damnation</a></td> <td>2002/3</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Opeth-Watershed/release/1792978">Watershed</a></td> <td>2008</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Opeth-Heritage/release/3114073">Heritage</a></td> <td>2011</td><td></td><td>✓</td><td></td></tr>
<tr><td rowspan="3">Anthony <b>Phillips</b></td><td><a href="https://www.discogs.com/Anthony-Phillips-The-Geese-The-Ghost/release/6792130">The Geese & the Ghost</a></td> <td>1977</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Anthony-Phillips-Wise-After-The-Event-Deluxe-Edition/release/8504133">Wise After the Event</a></td> <td>1978</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Anthony-Phillips-Slow-Dance/release/10461113">Slow Dance</a></td> <td>1990</td><td></td><td>✓</td><td></td></tr>
<tr><td rowspan="5"><b>Pink Floyd</b></td> <td><a href="https://www.discogs.com/Pink-Floyd-The-Early-Years-1970-Deviation/release/10068967">Atom Heart Mother <i>(Devi/Ation, quad)</i></a></td> <td>1970</td><td></td><td>✓</td><td>✓</td></tr>
<tr><td><a href="https://www.discogs.com/Pink-Floyd-The-Early-Years-1971-Reverberation/release/10068450">Echoes <i>(Reverber/Ation, quad)</i></a></td> <td>1971</td><td></td><td>✓</td><td>✓</td></tr>
<tr><td><a href="https://www.discogs.com/Pink-Floyd-The-Early-Years-1965-1972/release/9218307">Meddle <i>(Reverber/Ation, 5.1)</i></a></td> <td>1971</td><td></td><td></td><td>✓</td></tr>
<tr><td><a href="https://www.discogs.com/Pink-Floyd-The-Dark-Side-Of-The-Moon-Immersion-Box-Set/release/3137117">The Dark Side of the Moon</a></td> <td>1973</td><td></td><td>✓</td><td>✓</td></tr>
<tr><td><a href="https://www.discogs.com/Pink-Floyd-Wish-You-Were-Here-Immersion-Box-Set/release/3205416">Wish You Were Here</a></td> <td>1975</td><td></td><td>✓</td><td>✓</td></tr>
<tr><td rowspan="5"><b>Porcupine Tree</b></td> <td><a href="https://www.discogs.com/Porcupine-Tree-Stupid-Dream/release/884950">Stupid Dream</a></td> <td>1999</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Porcupine-Tree-Lightbulb-Sun/release/2299032">Lightbulb Sun</a></td> <td>2000</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Porcupine-Tree-Deadwing/release/1517943">Deadwing</a> <i>(2 copies, 1 signed)</i></td> <td>2005</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Porcupine-Tree-Fear-Of-A-Blank-Planet/release/1789025">Fear of a Blank Planet</a></td><td>2007</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Porcupine-Tree-The-Incident/release/1929601">The Incident</a></td><td>2009</td><td></td><td>✓</td><td></td></tr>
<tr><td><b>Riverside</b></td> <td><a href="https://www.discogs.com/Riverside-Love-Fear-and-the-Time-Machine-/release/8786183">Love, Fear and the Time Machine</a></td> <td>2015</td><td></td><td>✓</td><td></td></tr>
<tr><td rowspan="2"><b>Roxy Music</b></td> <td><a href="https://www.discogs.com/Roxy-Music-Roxy-Music/release/11499177">Roxy Music</a></td> <td>1972</td><td></td><td>✓</td><td></td></tr>
<tr></td> <td><a href="https://www.discogs.com/Roxy-Music-Avalon/release/709566">Avalon</a></td> <td>1982</td><td>✓</td><td></td><td></td></tr>
<tr><td><b>Schubert</b></td> <td><a href="https://www.discogs.com/Schubert-Don-Jackson-3-London-Symphony-Orchestra-Schubert-Classics/release/3165380">Schubert Classics</a></td><td>18xx</td><td></td><td>✓</td><td></td></tr>
<tr><td rowspan="3"><b>Simple Minds</b></td> <td><a href="https://www.discogs.com/Simple-Minds-New-Gold-Dream-81-82-83-84/release/8836904">New Gold Dream <i>(81-82-83-84)</i></a></td> <td>1982</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Simple-Minds-Sparkle-In-The-Rain/release/6795537">Sparkle in the Rain</a></td> <td>1984</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Simple-Minds-Once-Upon-A-Time/release/7820507">Once Upon a Time</a></td> <td>1985</td><td></td><td>✓</td><td></td></tr>
<tr><td rowspan="2"><b>Steely Dan</b></td> <td><a href="https://www.discogs.com/Steely-Dan-Gaucho/release/2110497">Gaucho</a></td> <td>1980</td><td>✓</td><td></td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Steely-Dan-Everything-Must-Go/release/1865258">Everything Must Go</a></td> <td>2003</td><td></td><td>✓</td><td></td></tr>
<tr><td><b>T. Rex</b></td><td><a href="https://www.discogs.com/T-Rex-Electric-Warrior/release/4202671">Electric Warrior</a></td><td>1971</td><td>✓</td><td></td><td></td></tr>
<tr><td rowspan="2"><b>Tchaikovsky</b></td> <td><a href="https://www.discogs.com/Tchaikovsky-Don-Jackson-3-London-Philharmonic-The-Tchaikovsky-Classics/release/6142852">Tchaikovsky Classics</a></td> <td>18xx</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Tchaikovsky-Don-Jackson-3-London-Symphony-Orchestra-Tchaikovskys-The-Nutcracker/release/2102614">The Nutcracker</a></td> <td>1892</td><td></td><td>✓</td><td></td></tr>
<tr><td><b>Tears for Fears</b></td> <td><a href="https://www.discogs.com/Tears-For-Fears-Songs-From-The-Big-Chair/release/6324313">Songs from the Big Chair</a></td> <td>1985</td><td></td><td></td><td>✓</td></tr>
<tr><td rowspan="2"><b>Trondheim Solistene</b></td> <td><a href="https://www.discogs.com/Trondheimsolistene-Divertimenti/release/3543371">Divertimenti</a></td> <td>2008</td><td>✓</td><td></td><td>✓</td></tr>
<tr><td><a href="https://www.amazon.co.uk/gp/product/B003IP2Y1M">In Folk Style</a></td> <td>2010</td><td>✓</td><td></td><td>✓</td></tr>
<tr><td rowspan="2">Rick <b>Wakeman</b></td> <td><a href="https://www.discogs.com/Rick-Wakeman-The-Six-Wives-Of-Henry-VIII/release/7564258">The Six Wives of Henry VIII</a></td> <td>1973</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Rick-Wakeman-The-Myths-And-Legends-Of-King-Arthur-And-The-Knights-Of-The-Round-Table/release/7402311">The Myths and Legends of King Arthur...</a></td> <td>1975</td><td></td><td>✓</td><td></td></tr>
<tr><td rowspan="2">The <b>Who</b></td> <td><a href="https://www.discogs.com/The-Who-Tommy/release/2028449">Tommy</a></td> <td>1969</td><td>✓</td><td></td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Who-Quadrophenia/release/5939673">Quadrophenia</a></td> <td>1973</td><td></td><td></td><td>✓</td></tr>
<tr><td rowspan="7">Steven <b>Wilson</b></td> <td><a href="https://www.discogs.com/Steven-Wilson-Insurgentes/release/1548534">Insurgentes</a></td> <td>2009</td><td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Steven-Wilson-Grace-For-Drowning/release/3131155">Grace for Drowning</a></td> <td>2011</td><td></td><td></td><td>✓</td></tr>
<tr><td><a href="https://www.discogs.com/Steven-Wilson-The-Raven-That-Refused-To-Sing-And-Other-Stories/release/4318160">The Raven that Refused to Sing</a></td> <td rowspan="2">2013</td><td></td><td>✓</td><td>✓</td></tr>
<tr><td><a href="https://www.discogs.com/Steven-Wilson-Drive-Home/release/5016154">Drive Home</a></td> <td></td><td></td><td>✓</td></tr>
<tr><td><a href="https://www.discogs.com/Steven-Wilson-Hand-Cannot-Erase/release/6707231">Hand. Cannot. Erase.</a></td> <td>2015</td><td></td><td>✓</td><td>✓</td></tr>
<tr><td><a href="https://www.discogs.com/Steven-Wilson-4%C2%BD/release/8013152">4½</a></td> <td>2016</td><td></td><td></td><td>✓</td></tr>
<tr><td><a href="https://www.discogs.com/Steven-Wilson-To-The-Bone/release/10721402">To The Bone</a></td> <td>2017</td><td></td><td>✓</td><td>✓</td></tr>
<tr><td rowspan="4"><b>XTC</b></td> <td><a href="https://www.discogs.com/XTC-Drums-And-Wires/release/6229310">Drums and Wires</a></td> <td>1979</td><td></td><td></td><td>✓</td></tr>
<tr><td><a href="https://www.discogs.com/XTC-Skylarking/release/9205203">Skylarking</a></td> <td>1986</td><td></td><td></td><td>✓</td></tr>
<tr><td><a href="https://www.discogs.com/XTC-Oranges-Lemons/release/7668009">Oranges & Lemons</a></td> <td>1989</td><td></td><td></td><td>✓</td></tr>
<tr><td><a href="https://www.discogs.com/XTC-Nonsuch/release/5089101">Nonsuch</a></td> <td>1992</td><td></td><td></td><td>✓</td></tr>
<tr><td rowspan="6"><b>Yes</b></td> <td><a href="https://www.discogs.com/Yes-The-Yes-Album/release/6624559">The Yes</a> <a href="https://www.discogs.com/Yes-The-Yes-Album/release/5706684">Album</a></td> <td rowspan="3">1971</td><td></td><td>✓</td><td>✓</td></tr>
<tr><td><a href="https://www.discogs.com/Yes-Fragile/release/2779845">Fragile</a> <i>(2002 - Rhino)</i></td> <td></td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Yes-Fragile/release/7858287">Fra</a>g<a href="https://www.discogs.com/Yes-Fragile/release/7680648">ile</a> <i>(2015 - Panegyric)</i></td> <td></td><td>✓</td><td>✓</td></tr>
<tr><td><a href="https://www.discogs.com/Yes-Close-To-The-Edge/release/5342766">Close to</a> <a href="https://www.discogs.com/Yes-Close-To-The-Edge/release/5090002">the Edge</a></td> <td>1972</td><td></td><td>✓</td><td>✓</td></tr>
<tr><td><a href="https://www.discogs.com/Yes-Tales-From-Topographic-Oceans/release/9241648">Tales from</a> <a href="https://www.discogs.com/Yes-Tales-From-Topographic-Oceans/release/9170167">Topographic Oceans</a></td> <td>1973</td><td></td><td>✓</td><td>✓</td></tr>
<tr><td><a href="https://www.discogs.com/Yes-Relayer/release/6624313">Rela</a>y<a href="https://www.discogs.com/Yes-Relayer/release/6263318">er</a></td> <td>1974</td><td></td><td>✓</td><td>✓</td></tr>
</tbody></table><br />
Notice that most of the <i>Yes</i> titles are double entries (in fact <i>Fragile</i> is a triple): the DVD-Audio and Blu-ray editions of these are of course equally essential.<br />
<br />
<b>The Loneliness of the Long Interval Musicologist</b><br />
<br />
The <i>Year</i> column is problematic. Every recording has its place on multiple chronologies; for example, the life and development of its composer, its conductor, and its performing artist(s). The difficulty is that different performances naturally emphasise disparate chronologies, and a single date field in a table or database struggles to accommodate these variations.<br />
<br />
For the historical study of musical development, the time stamp of primary interest is the date of composition. Sadly this is not recorded in typical media metadata, such as the ID3 tags on MP3 files. The "Year" recorded there represents the release date - when the particular edition was issued. Now, for most "popular" music (in the strict Amazon.com sense of "anything non-classical"), this is close enough to the date of composition; but for all of the recordings listed here, without exception, the release date for the multichannel edition is many years later than that of first publication of the original material, and the problem is only worse in the case of classical works.<br />
<br />
Generally I've tried to get as close as possible to the date of composition. That means for popular works using the year of first issue, and for classical, the historical year (or century, for compilations) of composition. Still there remain intractable inconsistencies. For example, all of the material on the Beatles' "Love" album was recorded and issued long before Cirque du Soleil went shopping for a soundtrack in 2006. Finally, there's Daniel Barenboim. When the focus of a classical recording is neither composer nor performer, but instead a famous conductor, then I use the performance date.<br />
<br />
<b>Live Surround Sound Music Videos</b><br />
<br />
This second table lists live video recordings of musical performances that just happen to have a surround sound component, often limited to a feeling of ambience in the hall where the recording was made. These are the second class citizens of the surround sound community, and this collection listing will be incomplete.<br />
<br />
<table><tbody>
<tr><td rowspan="2"><b>Artist or Composer</b></td><td rowspan="2"><b>Album Title</b></td><td rowspan="2"><b>Year</b></td><td colspan="3"><b>Physical Media</b></td></tr>
<tr><td><b>DVD</b></td><td><b>Blu‑ray</b></td></tr>
<tr><td><b>Ash</b></td><td><a href="https://www.discogs.com/Ash-Tokyo-Blitz-2001/release/4678741">Tokyo Blitz</a></td><td>2001</td><td>✓</td><td></td></tr>
<tr><td rowspan="5">Daniel <b>Barenboim</b></td> <td><a href="http://www.musicalcriticism.com/recordings/dvd-knowledge-0908.shtml">Knowledge is the Beginning / The Ramallah Concert</a></td> <td>2008</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Daniel-Barenboim-Wiener-Philharmoniker-Neujahrskonzert-2009-New-Years-Concert-2009/release/2378719">Neujahrskoncert 2009</a></td> <td>2009</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.amazon.co.uk/Various-Europakonzert-Concerto-Symphony-Meistersinger/dp/B0040Y7EV0">Europakonzert 10</a></td> <td rowspan="2">2010</td><td></td><td>✓</td></tr>
<tr><td><a href="http://www.prestoclassical.co.uk/r/C%2BMajor/703708">Mahler Symphony No. 9</a></td> <td>✓</td><td></td></tr>
<tr><td><a href="https://www.amazon.co.uk/Barenboim-West-Eastern-Orchestra-Salzburg-Concerts/dp/B0052IGM0M">The Salzburg Concerts</a></td> <td>2011</td><td></td><td>✓</td></tr>
<tr><td><b>Blackfield</b></td><td><a href="https://www.discogs.com/Blackfield-NYC-Blackfield-Live-In-New-York-City/release/1148904">NYC - Blackfield Live In New York City</a></td><td>2007</td><td>✓</td><td></td></tr>
<tr><td rowspan="2"><b>Dream Theater</b></td><td><a href="https://www.discogs.com/Dream-Theater-Live-At-Budokan/release/885441">Live at Budokan</a></td><td>2004</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Dream-Theater-Score/release/3938866">Score</a></td><td>2006</td><td>✓</td><td></td></tr>
<tr><td>David <b>Gilmour</b></td><td><a href="https://www.discogs.com/David-Gilmour-Remember-That-Night-Live-At-The-Royal-Albert-Hall/release/1081483">Remember That Night</a></td> <td>2007</td><td>✓</td><td></td></tr>
<tr><td rowspan="2"><b>Led Zeppelin</b></td> <td><a href="https://www.discogs.com/Led-Zeppelin-The-Song-Remains-The-Same/release/3779561">The Song Remains the Same</a></td><td>1999</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Led-Zeppelin-Led-Zeppelin/release/469775">Led Zeppelin</a></td><td>2003</td><td>✓</td><td></td></tr>
<tr><td rowspan="3"><b>Opeth</b></td><td><a href="https://www.discogs.com/Opeth-Lamentations-Live-At-Shepherds-Bush-Empire-2003/release/741711">Lamentations</a></td> <td>2006</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Opeth-The-Roundhouse-Tapes/release/8811769">The Roundhouse Tapes</a></td> <td>2007</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Opeth-In-Live-Concert-At-The-Royal-Albert-Hall/release/2944558">Live at the Royal Albert Hall</a></td> <td>2010</td><td>✓</td><td></td></tr>
<tr><td><b>Orphaned Land</b></td> <td><a href="https://www.discogs.com/Orphaned-Land-The-Road-To-Or-Shalem-Live-At-The-Reading-3-Tel-Aviv-/release/3303743">The Road to Or Shalem</a></td> <td>2011</td><td>✓</td><td></td> </tr>
<tr><td rowspan="2"><b>Porcupine Tree</b></td> <td><a href="https://www.discogs.com/Porcupine-Tree-Arriving-Somewhere/release/949277">Arriving Somewhere...</a></td> <td>2006</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Porcupine-Tree-Anesthetize-Live-In-Tilburg-Oct-2008/release/4254603">Anesthetize</a></td> <td>2010</td><td>✓</td><td>✓</td></tr>
<tr><td>Steven <b>Wilson</b></td><td><a href="https://www.discogs.com/Steven-Wilson-Get-All-You-Deserve/release/3931791">Get All You Deserve</a></td> <td>2012</td><td>✓</td><td>✓</td> </tr>
<tr><td rowspan="4"><b>Yes</b></td> <td><a href="https://www.discogs.com/Yes-Symphonic-Live/release/2973428">Symphonic Live</a></td><td>2002</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Yes-YesSpeak/release/2973546">YesSpeak</a></td><td>2003</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Yes-Acoustic-Guaranteed-No-Hiss/release/2598512">Acoustic</a></td><td>2004</td><td>✓</td><td></td></tr>
<tr><td><a href="https://www.discogs.com/Yes-Live-At-Montreux-2003/release/2973507">Live at Montreux 2003</a></td><td>2007</td><td>✓</td><td></td></tr>
<tr><td><b>Youssou N'Dour</b></td><td><a href="https://www.discogs.com/Youssou-NDour-Live-At-Montreux-1989/release/5177718">Live at Montreux 1989</a></td><td>2005</td><td>✓</td><td></td></tr>
</tbody></table><br />
</div>John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com3tag:blogger.com,1999:blog-8338396833705157791.post-70514813709795703082016-05-16T00:10:00.000+01:002016-06-14T16:11:23.833+01:00Surround Sound Switch #7: Wrapping Up<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZ2ogKKu1wsSvxrkdL2wy6Ynp_hSDirewOwEjy0zzMKhyphenhyphen5p78XQzkRbk_B87IhAlrPB-UQ086ujAjwJAgdaCoQoSLUnQxOYVqgNi17AsOm0P_QqgpBe7VjqXOKUTPhmH5BfjWp0Zuz3jqa/s1600/room1i.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZ2ogKKu1wsSvxrkdL2wy6Ynp_hSDirewOwEjy0zzMKhyphenhyphen5p78XQzkRbk_B87IhAlrPB-UQ086ujAjwJAgdaCoQoSLUnQxOYVqgNi17AsOm0P_QqgpBe7VjqXOKUTPhmH5BfjWp0Zuz3jqa/s200/room1i.png" width="169" /></a><b>Spinning The Room</b><br />
<br />
This is a quick summary of the contents and conclusions reached in my recent series of six articles on the subject of <i>Surround Sound Stage Rotation</i> switch designs and prototypes. The series is about various ways of <i>rotating the sound stage</i> of a surround sound / home cinema audio system, so as to make any chosen wall or corner the focus of the action.<br />
<br />
Said action takes place in an arena I've dubbed the <i>octoroom</i>. This is a bit like a normal rectangular room, but with a satellite speaker in every corner, and another in the centre of each wall.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdlCSjjvvCo1CIYRHBlHkWdi7GuxkY_UVKyBFo1isrtBTYbeB_SBhtOW2mpg5LUM3SYVudaopzOletOjcujjXSu9v1TTJ0jpB1v_IlT2xP8kRl-CK3BpzlAhFNBZQc2VpNk2WGnEwCJC1W/s1600/morb_1.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="156" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdlCSjjvvCo1CIYRHBlHkWdi7GuxkY_UVKyBFo1isrtBTYbeB_SBhtOW2mpg5LUM3SYVudaopzOletOjcujjXSu9v1TTJ0jpB1v_IlT2xP8kRl-CK3BpzlAhFNBZQc2VpNk2WGnEwCJC1W/s200/morb_1.jpg" width="200" /></a><b><a href="http://mycodehere.blogspot.co.uk/2016/02/surround-sound-reorientation-part-1.html">Part 1: The Mother Of all Relay Boxes</a></b><br />
<br />
I start out by examining the ready-made solutions available in the market. This doesn't take too long, as there are none. The hopelessness of seeking help from the audio kit manufacturers is bemoaned.<br />
<br />
I spend most of our session together longing for an earlier time, when things like the MORB-1 were available in shops.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpo8OfNpGWxY3LNrV6qRs22XU2TVArego4xFGY9sCEX-D95Uh56q8SMjt3RxGQYfv3xrsqa-rJ2aFU5LdVdmE3JwYk4q5kYuEkB7zkfadD1RaRoPx4caWCh9yaghfP9JzeUJ2bhGg9VDxE/s1600/Commutator.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="161" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpo8OfNpGWxY3LNrV6qRs22XU2TVArego4xFGY9sCEX-D95Uh56q8SMjt3RxGQYfv3xrsqa-rJ2aFU5LdVdmE3JwYk4q5kYuEkB7zkfadD1RaRoPx4caWCh9yaghfP9JzeUJ2bhGg9VDxE/s200/Commutator.png" width="200" /></a><b><a href="http://mycodehere.blogspot.co.uk/2016/02/surround-sound-reorientation-part-2.html">Part 2: Rolling your own Commutator</a></b><br />
<br />
I detail my personal colour scheme for octoroom wiring, explaining its minor deviations from the relevant standards. Then it's on to another pipe dream, this time involving acres of pristine copper plated (or more likely brass, or other alloy) substrate. An imaginary comb made of brushes is used to illustrate the ideal to which our prototypes can hopefully converge.<br />
<br />
I spend most of our session together longing for an earlier time, when such <i>commutators</i> were available in shops.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQ6u-w3tbzyc_5CQmPT-M9aef7qivJHAJk9KLvIYvaUiUWyhmrChOl6QZe2rJFgdJSGB9ck5o4Y8ulCs3QfdQcCv16krdIj20AJZ2ZhBRElDLHPRQn-4Dg9hKYoiHNj4KVKgktXDLNHiaW/s1600/Rotary+Switch+Cropped.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQ6u-w3tbzyc_5CQmPT-M9aef7qivJHAJk9KLvIYvaUiUWyhmrChOl6QZe2rJFgdJSGB9ck5o4Y8ulCs3QfdQcCv16krdIj20AJZ2ZhBRElDLHPRQn-4Dg9hKYoiHNj4KVKgktXDLNHiaW/s200/Rotary+Switch+Cropped.png" width="126" /></a><b><a href="http://mycodehere.blogspot.co.uk/2016/03/surround-sound-switch-3-bulgaria.html">Part 3: Bulgaria (rotary switches)</a></b><br />
<br />
The ideal 8-pole commutator can be simulated by helically wiring a suitable stack of wafer switches. I discover 7P8T palladium contact rotaries for sale in Bulgaria, and buy them for research. They turn out to be ex-telecomms system components, too fragile, difficult to wire, and otherwise unsuitable for audio use. But they inspire a passive rotary switch design, which eventually becomes my first successful prototype.<br />
<br />
A new feature dubbed <i>Mode 5</i> is introduced, for the specialist who needs to analyse custom curated surround sound music recordings. It allows a 5.x remix to be "stretched out" over the whole 7.x room, without adding in any sound processing by the receiver.<br />
<br />
Now that concrete prototypes are beginning to emerge, I describe a scheme for quickly and conveniently swapping them in and out of the home cinema system. The scheme is based on Bulgin 8-pin, cable- and panel-mounting, plugs and sockets.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcLri_xcWgYZeWOAnjTL99Q0EN6iDxp6WcRa_v6I6dM-zLxRUabMbzzSAwmkwuup3-ZIVmUroy9HunG9ehJSEwt1AOZuzP7h5ei0mPFlkZTKTDpXBedPFNAEXFKpfW6ud7NjBXo2Hzayg2/s1600/20160406_172650.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcLri_xcWgYZeWOAnjTL99Q0EN6iDxp6WcRa_v6I6dM-zLxRUabMbzzSAwmkwuup3-ZIVmUroy9HunG9ehJSEwt1AOZuzP7h5ei0mPFlkZTKTDpXBedPFNAEXFKpfW6ud7NjBXo2Hzayg2/s200/20160406_172650.jpg" width="200" /></a><b><a href="http://mycodehere.blogspot.co.uk/2016/04/surround-sound-switch-4-group-theory.html">Part 4: Group Theory (toggle switches)</a></b><br />
<br />
The mathematical area of permutations teaches that a single 8PDT switch, suitably wired, can rotate our sound stage through any single angle that's a multiple of 45°. Such rotations can also be <i>composed</i>, or applied one after the other, simply by stringing two or more such switches in series, in any order. So, we can choose a suitable chain of three "basis rotators", say 45°, 90° and 180°, and by selectively turning certain ones on and off, achieve any multiple of the atomic 45° rotation.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjo2fbti0KuRnahRgWWlfw5NXECfR0RO_mnSNL9Lx624z0h5-Ahp20TxlTf4hoOrsPENrZqBLBEL6lMfEfvsh4h3p-isPrZ092edCU_Vsl_rBVGP3ey4CCFIDUB3jHDRmg5pqMFthTRJ0r5/s1600/All+Switches.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="177" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjo2fbti0KuRnahRgWWlfw5NXECfR0RO_mnSNL9Lx624z0h5-Ahp20TxlTf4hoOrsPENrZqBLBEL6lMfEfvsh4h3p-isPrZ092edCU_Vsl_rBVGP3ey4CCFIDUB3jHDRmg5pqMFthTRJ0r5/s200/All+Switches.png" width="200" /></a>Eight pole toggle switches exist, albeit outside the unspoken, hobbyist budgetary scope of this series. But usefully, permutation theory also shows that a <i>safe</i> implementation of the 90° rotation can equally be achieved by splitting our 8PDT switch into two more readily and cheaply available 4PDT units ganged together, and that the 180° can similarly be reached by this means, or even by ganging together four DPDT units.<br />
<br />
What is meant by <i>safe</i> in this context, is that under failure conditions, when one or more of the component switches fails to operate, no damage other than a seriously mixed up surround sound image will be caused. Amplifier outputs will not become cross-connected, nor asked to drive two or more loudspeakers in parallel. Sadly, the same can't be said about the 45° rotation stage.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZUdYWUFRO0rareSiMrM_qJkM4dbbXjFFD8cJFpoHk2U9Y1f5IT0IGRQEaOFmZM_rc8w5hyphenhyphen0sDW7hhU_p7Wul2Cl9SbDrVNR0gpR6xtHcPPWpXsQ1UTxAjJVoZhY4Y_pukDqcRd8xASkw3/s1600/tiny+toggle.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZUdYWUFRO0rareSiMrM_qJkM4dbbXjFFD8cJFpoHk2U9Y1f5IT0IGRQEaOFmZM_rc8w5hyphenhyphen0sDW7hhU_p7Wul2Cl9SbDrVNR0gpR6xtHcPPWpXsQ1UTxAjJVoZhY4Y_pukDqcRd8xASkw3/s1600/tiny+toggle.png" /></a>There's a brief, unintelligible diversion, something about binary clocks, I dunno...<br />
<br />
I make two more successful passive prototypes based entirely on 4PDT toggle switches - first some big Hong Kong ones with screw terminals, then smaller switches with solder lugs. Each prototype contains a 90° and a 180° rotator, as well as the new <i>Mode 5</i> feature, which takes up one further 4PDT switch for a total of five. The 45° rotator has been dropped temporarily, as there's no easy way to guarantee that its two associated 4PDT switches will always be operated simultaneously and kept forever out of the potentially destructive <i>one on, one off</i> state.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjLpiqKqsq5N-3zXrzcm6dW9aciSkt9FixvwLwJUQ0Cl4yA_yIsW8tFX2gcvRbtGLZibS-AuQF5dtawnXZkzL1kzy3fJPx4ThRX8lwbt0qX6dAOuXc5SZ8w5RJ9ol4tABjGsR9Z6Uiyqid/s1600/20160502_190009_cropped.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="128" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjLpiqKqsq5N-3zXrzcm6dW9aciSkt9FixvwLwJUQ0Cl4yA_yIsW8tFX2gcvRbtGLZibS-AuQF5dtawnXZkzL1kzy3fJPx4ThRX8lwbt0qX6dAOuXc5SZ8w5RJ9ol4tABjGsR9Z6Uiyqid/s200/20160502_190009_cropped.jpg" width="200" /></a><b><a href="http://mycodehere.blogspot.co.uk/2016/04/surround-sound-switch-5-relayer.html">Part 5: Relayer (electromagnetic relays)</a></b><br />
<br />
Essentially the same audio circuit can be transcribed from the toggle switches in part 4 to the 4PDT electromagnetic relays in this part. With the addition of a 12V PSU and a 4-bit hexadecimal thumbwheel switch, prototype number 4 - the first active device in the series - is born.<br />
<br />
The 45° rotator is reintroduced, since the two 4PDT relays that constitute it can now be guaranteed driven together and kept synchronised. Even under rare fault conditions, e.g. a relay coil burning out, the risk of damage can at least be mitigated by assessing which failure mode - amplifier outputs shorted together, or loudspeakers becoming paralleled up - is the less serious, and wiring the switch contacts accordingly. A free online circuit simulator is used to pre-verify the audio wiring schematic.<br />
<br />
I finally have a full 8-position, manually operated, prototype sound stage rotator.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvltceH7w3gfU3NdLv9W8kljMsF67IhKIps6xhsU2GLjDoH6gW81Sk2DE7g0qIyH8pbtT_W90RN2Bd_vIGQQBAMtEkaX86yiuYECBl2rPnaMtofa-p-p4MUGKiotAwQ_vaToI5sI1ogERb/s1600/20160515_235754.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvltceH7w3gfU3NdLv9W8kljMsF67IhKIps6xhsU2GLjDoH6gW81Sk2DE7g0qIyH8pbtT_W90RN2Bd_vIGQQBAMtEkaX86yiuYECBl2rPnaMtofa-p-p4MUGKiotAwQ_vaToI5sI1ogERb/s200/20160515_235754.jpg" width="112" /></a><b><a href="http://mycodehere.blogspot.co.uk/2016/05/surround-sound-switch-6-arduino-remote.html">Part 6: Arduino (remote control)</a></b><br />
<br />
No sooner has it arrived, than the hex thumbwheel switch is replaced by an Arduino Uno, driving the relays and relay pairs through bipolar npn transistors. A wiring self-test program is written, seen operating in a <a href="https://www.youtube.com/watch?v=Liv3SB0L2R4" target="_blank">YouTube video</a>. This verifies again that all audio pathways are switched correctly, as the compass orientation rotates, and as Mode 5 is switched on and off.<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhO_1cC8VNkXJEkPYIFES5KiLBLIpbIXWXMnFvmJ3kGV8BRQfwouQ5uQtnLYGMVMtsuIqMcaYUyVdFIEjOR7mX_VYlEKio6ipYkOJUwpTJRHwVJW6-5zyynppNIYTqaKEeAKbYKQHEe48FE/s1600/Project+Box.jpg" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="151" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhO_1cC8VNkXJEkPYIFES5KiLBLIpbIXWXMnFvmJ3kGV8BRQfwouQ5uQtnLYGMVMtsuIqMcaYUyVdFIEjOR7mX_VYlEKio6ipYkOJUwpTJRHwVJW6-5zyynppNIYTqaKEeAKbYKQHEe48FE/s200/Project+Box.jpg" width="200" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Home is an <a href="http://www.maplin.co.uk/p/plastic-box-mb4-matt-black-261x130x85mm-lh23a" target="_blank">MB4</a> project box</td></tr>
</tbody></table>
The test wiring is removed, and an IR receiver module is interfaced to the Arduino. Suitable IR codes are obtained by "sniffing" an old Sony BD player remote; these are then embedded into the code, and verified to operate as expected.<br />
<br />
Some speculation about future development occurs, but prototype number 5 feels like the logical end of this road. There's life after prototyping, of course. I still have to design a suitable custom PCB, using just the bare ATmega328P chip and a 16MHz crystal, so I can keep my Arduino Uno board for future projects. Still have to stick it all in a box. And so on and on...<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6l92SQ4a9ayaJenxUNSA4gpLYrUZdxTrfG3WGLxrWdd8zn_153jqylEwiqAiR5YyVdOJaywpf3f1FAjUDg6llUljWEGB_a4NTbgsoROqU1UJ__7skCuW3SIe8HjpfoshBKR5FpueM3D5c/s1600/Linda+%2526+Cleo.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6l92SQ4a9ayaJenxUNSA4gpLYrUZdxTrfG3WGLxrWdd8zn_153jqylEwiqAiR5YyVdOJaywpf3f1FAjUDg6llUljWEGB_a4NTbgsoROqU1UJ__7skCuW3SIe8HjpfoshBKR5FpueM3D5c/s200/Linda+%2526+Cleo.png" width="196" /></a><b><u>Acknowledgements</u></b><br />
<br />
Thanks to my wife for putting up with (a) so many odd deliveries of random munitions from Amazon, Ebay, Maplin <i>(hi Scott!)</i> and RS Components, not to mention international arms shipments from USA, Hong Kong, Germany and Bulgaria; and (b) the too many hours I spent locked away in the man-cave, playing with screwdrivers, soldering irons, and ticking devices bristling with hundreds of multicoloured wires.<br />
<br />
<a href="https://ghadzhigeorgiev.wordpress.com/" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;" target="_blank"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOoKHg4_bEJNqUS5jiuuvQefwjozqc6rsXPlabArQkI4G2MKxg9UTIYPmp8Ih2ozhOPTp1KL9hlB7onCwAIij1LxD0vfXdqOQb95_0cfmfMKMSU5rsbvXOVecL9ldozfw-X5uyc_3QQW70/s1600/Georgi.jpg" /></a>Special mention to <a href="https://ghadzhigeorgiev.wordpress.com/" target="_blank">Georgi</a>, my Bulgarian rocket scientist colleague, for pushing me to the Arduino limit, and convincing me there would be merit in these investigations. Without his input, I'd have contented myself with a twisting plug and socket manual solution.<br />
<br />
<b>The End</b>John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-49720369752604239732016-05-10T20:49:00.000+01:002016-09-13T15:02:36.583+01:00My Cat Here<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYv8WRZu20z7xpAjHALcRUFlZlCaGxPLZAF83w2UNmKnaxw8Y773Y8-i4ZCf2prX1se6YRgZirNzioFBVhHjG9Jtoex4-j9-QFb0zXDWu9TDeW3ufiuYeiJqsANaK-PacS48OMCevArsEL/s1600/20160510_133037.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYv8WRZu20z7xpAjHALcRUFlZlCaGxPLZAF83w2UNmKnaxw8Y773Y8-i4ZCf2prX1se6YRgZirNzioFBVhHjG9Jtoex4-j9-QFb0zXDWu9TDeW3ufiuYeiJqsANaK-PacS48OMCevArsEL/s320/20160510_133037.jpg" /></a></div><b>Meet our Cleo</b><br />
<br />
Of course the Internet is made of cats, everybody knows that. So here is our latest contribution, and indeed our latest family member: Cleo, aged almost one and a quarter.<br />
<br />
Actually she's still at the <a href="https://www.scottishspca.org/" target="_blank">Scottish SPCA</a> rehoming centre right now, just awaiting a little dental scale and polish, and whatever other surgical interventions might be appropriate (ssshhh). But she has been duly reserved, her new home is ready and waiting, and we've been busy planning and buying her new toys and other worldly chattels.<br />
<br />
Cleo strikes you at first as a quiet wee lass - indeed, "A very timid little lady" is the first entry on her vet's record. She's recently lost her owner(s), sadly no longer able to take care of her. And just at this time, with all she's been through and the upset in her life, she can appear a little reluctant to make new friends. But she didn't have to spend too long as a rescue kitty. As soon as she appeared in her temporary accommodation quarters, as soon as she came forward to get tickled, as soon as she miaowed, she was all ours.<br />
<br />
Insofar as any cat can ever be said to belong to any of us... anyway.<br />
<br />
The very lovely Cleo Kerr, everybody!<br />
<br />
John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-54003810334342569322016-05-05T20:34:00.000+01:002016-05-17T14:19:56.833+01:00Surround Sound Switch #6: Arduino (remote control)<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody>
<tr><td style="text-align: center;"><a href="https://upload.wikimedia.org/wikipedia/commons/3/38/Arduino_Uno_-_R3.jpg" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="200" src="https://upload.wikimedia.org/wikipedia/commons/3/38/Arduino_Uno_-_R3.jpg" width="200" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">SparkFun Electronics Arduino Uno R3</td></tr>
</tbody></table>
<i>Previously:</i><br />
<blockquote class="tr_bq">
<i><a href="http://mycodehere.blogspot.co.uk/2016/02/surround-sound-reorientation-part-1.html">Mother Of all Relay Boxes</a><br />
<a href="http://mycodehere.blogspot.co.uk/2016/02/surround-sound-reorientation-part-2.html">Rolling your own Commutator</a><br />
<a href="http://mycodehere.blogspot.co.uk/2016/03/surround-sound-switch-3-bulgaria.html">Bulgaria (rotary switches)</a><br />
<a href="http://mycodehere.blogspot.co.uk/2016/04/surround-sound-switch-4-group-theory.html">Group Theory (toggle switches)</a><br />
<a href="http://mycodehere.blogspot.co.uk/2016/04/surround-sound-switch-5-relayer.html">Relayer (electromagnetic relays)</a></i></blockquote>
Adding remote control to the relay-based prototype can be fairly trivial, since quite often the work has mostly been done for us already by others. One such easy route is via Arduino and infra-red, for which many IR receiver modules are cheaply available. Also available incidentally are <a href="https://www.youtube.com/watch?v=9QZkCQSHnko" target="_blank">WiFi</a> and <a href="https://www.youtube.com/watch?v=x3KAXjnP06o" target="_blank">Bluetooth</a> modules for Arduino, not to mention the fully Wi-Fi integrated MKR1000 and Uno WiFi, so there's no shortage of options. But today, I'll just be looking at IR.<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrJMW1BT-FOJymlPnOZrwCUd4y8kuwACg0U4KG5mZ3WbJiqtkzYbMz0hXMZ8jukq4AmGoh2YK3G_ZgEcMBiNL0d51xtI1Jhx9_X9l0Jes-OcMxZqnGov0YBP3U1E96gIC5qpaE8Bd_D-Hq/s1600/Arduino+Control.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrJMW1BT-FOJymlPnOZrwCUd4y8kuwACg0U4KG5mZ3WbJiqtkzYbMz0hXMZ8jukq4AmGoh2YK3G_ZgEcMBiNL0d51xtI1Jhx9_X9l0Jes-OcMxZqnGov0YBP3U1E96gIC5qpaE8Bd_D-Hq/s1600/Arduino+Control.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Arduino pins driving transistors driving relays.</td></tr>
</tbody></table>
<b>The Arduino Interface</b><br />
<br />
Regardless of the connectivity solution adopted, the first requirement is for the Arduino device to take over operation of the four signals controlling the seven relays. At the moment these terminate at the thumbwheel switch, which can selectively operate one or more coils by connecting their lower ends to 0V. In the case of a relay pair, this results in a current of 150mA sinking through the switch contact - much more than an Arduino digital output can either sink or source (20mA continuous recommended, 40mA absolute max).<br />
<br />
The solution is to use a transistor, as shown in this circuit diagram, to amplify the current capacity between each Arduino output and its associated relay coil(s). Here I've used my old favourite, the silicon bipolar npn device; MOSFETs are another option. The relay drivers are on pins 2 <i>(45°)</i>, 4 <i>(90°)</i>, 7 <i>(180°)</i>, and 8 <i>(Mode 5)</i>. The four so-called <i>freewheeling</i> diodes (e.g. 1N4007), slung across the relay coils in reverse, protect the transistors from the back EMF generated when the highly inductive load is switched. Now when one of these four Arduino outputs goes high (+5V), current flows through a resistor into the transistor base, switching it on. This allows a larger current to flow from the +12Vdc rail through the relay coil(s) and the transistor to 0V. Any general purpose npn transistor with the following specifications will do:<br />
<blockquote class="tr_bq">
min DC current gain <b>h<sub>FE</sub> ≥ 40</b><br />
max DC collector current <b>I<sub>C</sub> ≥ 200mA</b><br />
max collector-emitter voltage <b>V<sub>CEO</sub> ≥ 20V</b><br />
max total power dissipation <b>P<sub>tot</sub> ≥ 100mW</b></blockquote>
The popular 2N2222 is one example of a suitable component, but <i>note that its frequently cited European "functional equivalent" BC548 is actually ruled out by having too low a maximum collector current (100mA)</i>. Now let's choose an appropriate resistor value:<br />
<blockquote class="tr_bq">
R<sub>max</sub> = V<sub>cc</sub> / (I<sub>max</sub> / h<sub>FE</sub>) = 5V / (150mA / 40) = 5V / 3.75mA = 1.3kΩ.</blockquote>
I'd probably recommend using 1kΩ or so for that extra 30% safety margin. If the base resistor value is too high, the base current will be too low to ensure the transistor saturates and remains outside its high dissipation, potentially destructive "linear" mode. By contrast, when in digital mode, the transistor is either fully off (collector current is zero) or fully on (collector-emitter voltage is essentially zero), so in each case, the power P = I * V ≈ 0.<br />
<br />
<b>The IR Library</b><br />
<br />
The Arduino microcontroller development system has access to an excellent free IR control library, <a href="http://z3t0.github.io/Arduino-IRremote/" target="_blank"><i>Arduino IRremote</i></a>, by <a href="http://www.righto.com/2009/08/multi-protocol-infrared-remote-library.html" target="_blank">Ken Shirriff</a>. Thanks Ken! This code resource both sends and receives infra-red signals. Many people have made use of this, including <a href="http://makezine.com/projects/diy-hacks-how-tos-remote-control-with-an-arduino/" target="_blank">Jason Poel Smith</a>, who very reasonably asks,<br />
<blockquote class="tr_bq">
<i>Most of the buttons on a remote control are never used.<br />
So why not use them to control appliances and other electronics around your house?</i></blockquote>
then goes on to do just that - repurposing any unused command on any of your IR remotes, to control an electrical outlet switch. He even includes a simple and easy-to-use learning mode, whereby a single additional button press is all you need to teach your electronics which new signal it has to respond to.<br />
<br />
<b>The Command Set</b><br />
<br />
Our requirements are a little more complicated than controlling the state of a single relay, but not by that much. We have to drive three transistors to control the orientation, and a fourth for Mode 5. So, four digital outputs, rather than one? No big deal.<br />
<br />
Now for our UI commands. We'd like buttons to take us directly to a particular orientation, numbered maybe 0-7, maybe 1-8, or maybe mapped to the physical layout of a numeric pad - whatever you prefer. Two more buttons, to rotate from the current orientation by 45° increments, either left or right. A toggle, and/or two separate commands, to engage/disengage Mode 5. A reset button to set the orientation back to 0 <i>and</i> disengage Mode 5.<br />
<br />
<b>Sketch</b><br />
<br />
Can't remember the last time the blog known as My Code Here contained any <i>actual computer code</i>, but anyway, here is the full Arduino sketch source for the project:<br />
<br />
<div style="background-color: gold; height: 253px; overflow-y: scroll; width: 600px;">
<pre><code>/*
RoomSpin
Audio soundstage rotation switch for 7.x surround sound system with 8 satellites
http://mycodehere.blogspot.co.uk/2016/05/surround-sound-switch-6-arduino-remote.html
This code is in the public domain - created 6 March 2016 by John Michael Kerr
*/
#include <irremote.h>
#include <irremoteint.h>
//#define TEST
void setup()
{
setupRelays();
#ifdef TEST
setupTest();
#else
setupMain();
#endif
}
void loop()
{
#ifdef TEST
loopTest();
#else
loopMain();
#endif
}
// Main program setup & loop
void setupMain()
{
setupReceiver();
}
void loopMain()
{
long code = readReceiver();
if (code)
performCode(code);
}
// IR receiver handling
const int pinIR = A5;
IRrecv* receiver;
decode_results code;
void setupReceiver()
{
Serial.begin(9600);
receiver = new IRrecv(pinIR);
receiver->enableIRIn();
}
long readReceiver()
{
long result = 0;
if (receiver->decode(&code))
{
result = code.value;
Serial.println(result, HEX);
receiver->resume();
}
return result;
}
// Command codes
const long
codeDigits[8] =
{
0xbeef0000,
0xbeef0001,
0xbeef0002,
0xbeef0003,
0xbeef0004,
0xbeef0005,
0xbeef0006,
0xbeef0007
},
codeLeft = 0xbeef0008,
codeRight = 0xbeef0009,
codeMode5_ON = 0xbeef000A,
codeMode5_OFF = 0xbeef000B,
codeMode5_TOGGLE = 0xbeef000C,
codeReset = 0xbeef000D;
// Command codes for Sony BD (RMT-B119P)
//
//const long
// codeDigits[8] =
// {
// 0x00090B47, // 0
// 0x00000B47, // 1
// 0x00080B47, // 2
// 0x00040B47, // 3
// 0x000C0B47, // 4
// 0x00020B47, // 5
// 0x000A0B47, // 6
// 0x00060B47 // 7
// },
// codeLeft = 0x000DCB47, // Left arrow
// codeRight = 0x0003CB47, // Right arrow
// codeMode5_ON = 0x000E0B47, // 8
// codeMode5_OFF = 0x00010B47, // 9
// codeMode5_TOGGLE = 0x00066B47, // Blue
// codeReset = 0x000E6B47; // Red
int compass = 0;
bool mode5 = false;
bool codeToMode(long code)
{
switch (code)
{
case codeMode5_ON:
return true;
case codeMode5_OFF:
return false;
}
return !mode5;
}
int performCode(long code)
{
for (int c = 0; c < 8; c++)
if (code == codeDigits[c])
return rotateTo(c);
switch (code)
{
case codeLeft:
return rotateBy(-1);
case codeRight:
return rotateBy(+1);
case codeMode5_ON:
case codeMode5_OFF:
case codeMode5_TOGGLE:
return setMode5(codeToMode(code));
case codeReset:
mode5 = false;
return rotateTo(0);
}
return 0;
}
int rotateBy(int eighths)
{
return rotateTo(compass + eighths);
}
int rotateTo(int eighths)
{
compass = eighths & 7;
setRelays();
return compass;
}
int setMode5(bool value)
{
mode5 = value;
setRelays();
return 0;
}
// Drive the relays
const int pinCTRL[4] = {7, 8, 12, 13};
void setRelayMask(int pin, int mask)
{
setPinMask(pin, compass, mask);
}
void setRelays()
{
for (int p = 0; p < 3; p++)
setRelayMask(pinCTRL[p], 1 << p);
setPinIf(pinCTRL[3], mode5);
delay(100); // Let the relays settle.
}
void setupRelays()
{
for (int p = 0; p < 4; p++)
pinMode(pinCTRL[p], OUTPUT);
}
// Low level I/O support
void setPinIf(int pin, bool condition)
{
digitalWrite(pin, condition ? HIGH : LOW);
}
void setPinMask(int pin, int value, int mask)
{
setPinIf(pin, (value & mask) != 0);
}
// End of tab
</code></pre>
</div>
<br />
This listing shows placeholders for the actual IR remote codes generated by your remote. Run the program with the Serial Monitor enabled, then blast it with your own remote, making note of the hex code generated by each of your chosen command buttons. Then search my source for the string 0xbeef, and replace these hex constants with your own. The numeric keys (here numbered 0 to 7) are stored in order in the codeDigits array, and the command names following these should be self-explanatory.<br />
<br />
I'm currently using this prototype with codes for a Sony BDPS590 Blu-Ray player (remote control model number <a href="https://esupport.sony.com/LA/p/model-home.pl?mdl=RMTB119P" target="_blank">RMT-B119P</a>), these are the codes in the commented-out section below the placeholders. Known affectionately to my wife and me as as "stubby buttons", this is a well-behaved remote - most buttons generate a single code followed by a stream of 0xFFFFFFFF, as long as they're held down. The only exceptions are volume up/down, mute, and the other TV buttons, whose output depends entirely upon which make & model of TV you've programmed it for. With other remote brands, be prepared to do a little C++ protocol tweaking to handle alternate and/or repeating code complications.<br />
<br />
<b>Wire Test</b><br />
<br />
Last time I promised you a fully automated wiring test using just the Arduino Uno with no additional hardware. How are we going to achieve that with only 14 digital I/O pins available on the development board, when there are 19 or 20 terminations on our relay loom? Count them: 4 control inputs, and on the audio side, 7 or 8 inputs plus 8 outputs. Answer: by pressing the Arduino's six <i>analog</i> inputs A0-A5 into service. These work just as well as digital inputs, and bring the available total to exactly plenty. In fact I've already used A5 to interface the IR receiver module (<i>pinIR</i> in the code), rather than the default pin 11.<br />
<br />
Say we keep the existing pins 2/4/7/8 attached to the four coil controls, as in the diagram above. Now associate pins 3/5/6/9/10/11/12/13 respectively with the eight audio amplifier outputs. For test purposes these will take the place of the physical amplifier outputs in real life.<br />
<br />
Next, for the loudspeaker inputs, associate analog inputs A0-A5, operating in digital mode, with the first six, and pins 0/1 with the remaining two. <i>The IR receiver module must be disconnected from pin A0 during this test.</i> Now all our test program needs to do is drive the coil controls with every binary pattern from 0 to 15, and for each pattern, walk a single bit (actually a logic zero) from the first audio amplifier output through to the last, checking that it appears only on the expected loudspeaker input pin, if any.<br />
<br />
Note the change in I/O terminology here. While designing the relay network, we called the amplifier signals <i>inputs</i> and the loudspeaker destinations <i>outputs</i>. That made sense from the viewpoint of the switch. Now in the Arduino software, <i>from the perspective of the system testing the switch</i>, our ins & outs are swapped around.<br />
<br />
Here is the source code for the wire test, which should be added as a new tab to the main code above. Then in the main sketch, remove the double slashes from the line <i>//define TEST</i>. Remember to undo this edit (and reconnect the IR receiver module) once the wire test is complete.<br />
<br />
<div style="background-color: gold; height: 253px; overflow-y: scroll; width: 600px;">
<pre><code>/*
WireTest
A wiring test utility for the RoomSpin project
http://mycodehere.blogspot.co.uk/2016/05/surround-sound-switch-6-arduino-remote.html
This code is in the public domain - created 6 March 2016 by John Michael Kerr
*/
const int
pinIN[8] = {A0, A1, A2, A3, A4, A5, 0, 1},
pinOUT[8] = {3, 5, 6, 9, 10, 11, 12, 13};
int
output,
expected,
actual;
void setupTest()
{
for (int p = 0; p < 8; p++)
{
pinMode(pinIN[p], INPUT_PULLUP);
pinMode(pinOUT[p], OUTPUT);
}
writeOutput(0xFF);
}
void loopTest()
{
for (compass = 0; compass < 8; compass++)
{
setRelays();
for (int mask = 1; mask < 0x100; mask <<= 1)
{
writeOutput(mask ^ 0xFF);
delay(50); // Let the outputs settle.
readExpected();
readActual();
while (actual != expected); // Crash!
}
writeOutput(0xFF);
}
mode5 = !mode5;
}
void readActual()
{
actual = 0;
for (int p = 0, mask = 1; p < 8; p++, mask <<= 1)
if (digitalRead(pinIN[p]))
actual |= mask;
else
actual &= ~mask;
}
void readExpected()
{
int mask = output ^ 0xFF;
if (mode5)
mask = useMode5(mask);
mask <<= compass;
if (mask > 0xFF)
mask >>= 8;
expected = mask ^ 0xFF;
}
int useMode5(int mask)
{
switch (mask)
{
case 0x01:
case 0x40:
return 0;
case 0x02:
return 0x01;
case 0x20:
return 0x40;
}
return mask;
}
void writeOutput(int value)
{
output = value;
for (int p = 0, mask = 1; p < 8; p++, mask <<= 1)
setPinMask(pinOUT[p], output, mask);
}
// End of tab</code></pre>
</div>
<br />
There's one headache with using up all 20 I/O pins in this way. Serial communications normally proceed via Arduino pins 0 and 1. With these tied up, how are we to glean any diagnostic information form the wire test?<br />
<br />
My simple solution is first to add LEDs with series current limiting resistors to all twelve output pins (four relay drivers and eight audio channels). Now run the test, and jump into an infinite loop as soon as any unexpected result occurs. That's the function of this rather suspect looking line of code, with its barely noticeable <i>empty loop</i> statement:<br />
<blockquote class="tr_bq">
while (actual != expected); // Crash!</blockquote>
All being well, these LEDs will flash binary patterns and masks, repeating one full test cycle every eight seconds. When the unthinkable happens, the LEDs become frozen, displaying in an unambiguous snapshot the state of all output signals, at the instant of fault detection. Yay diagnostics!<br />
<br />
Here's a short video of the wire test in action. It's a bit less dramatic than its title suggests. But if you've read this far, you know that already.<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="315" src="https://www.youtube-nocookie.com/embed/Liv3SB0L2R4?rel=0" width="560"></iframe><br />
<br />
In a past life, I worked with embedded systems and microcontroller projects, based on hardware such as the Motorola MC68HC705 series [<a href="http://www.cryptomuseum.com/df/kolibrie/files/MC68HC705.pdf" target="_blank">pdf</a>], for over 15 years (1980-1995). This is the first time I've used a high level language which I didn't have to design and implement entirely on my own. Okay, it's only C++ with a little preprocessor supplied syntactic sugar, but I'm still impressed. I like your brave new world!<br />
<br />
<b>Future Expansion</b><br />
<br />
Hmm, so back into normal operation, and the Arduino Uno still has a bunch of those analog input pins free, eh. It's tempting to drive them with signals derived from the actual audio waveforms, suitably rectified and limited, then perhaps using some custom automatic gain control (AGC) code, translate those input levels to PWM brightnesses feeding a retro ring of eight front panel LEDs. When the audio is quiet, these LEDs could pull double duty by indicating the currently selected orientation.<br />
<br />
In fact that was the thinking behind the quirky output pin selections for the relay drivers and the IR receiver module. Since outputs PD3/5/6 and PB1/2/3 are capable of PWM operation, they're reserved for future LED driving duty. I'd be happy enough driving these LEDs in pairs just like the relays, but if you demand one LED per audio channel, you might want to reassign some I/O and use the Arduino Leonardo. That board offers an additional PWM output on pin 13, as well as extending analog input capability to several of the digital I/O pins.<br />
<br />
Any other additional features? Maybe we'd also like the switch to revert automatically to the default, powered-down state, after a few hours of inactivity - just so we don't accidentally leave the relay coils needlessly burning up the watts for weeks on end when not in use.<br />
<br />
<b>Two Distinct Defaults</b><br />
<br />
Typically a switch like this will spends most of its life in just one particular orientation, with an occasional foray into a second, still less frequently a third, and so on. Obviously it's worth wiring the most frequently used orientation as the default one, which has been called "North" in my descriptions to date, and in which all seven relays are de-energised. Then for most of the time you can simply have the device unplugged or switched off, saving power and component life.<br />
<br />
Less obviously, the <i>second</i> most popular switch state might benefit from being stored in non-volatile memory, and selected automatically on power up. That way, whenever movie night, holiday projector time, or whatever other occasion rocks up, you need only power up, and the sound stage rotates instantly to the secondary setting, ready for the evening's entertainment.<br />
<br />
Such a fixed "secondary default" could easily be programmed with a few seconds' work. A better solution however might be to introduce a new command, allowing the current switch state to be saved in the Arduino microcontroller's non-volatile <a href="https://www.youtube.com/watch?v=jQoqpDTtIx4" target="_blank">EEPROM</a> memory with the press of a button, and subsequently, to be retrieved from there upon power up. Or to automate the process completely, write the state to EEPROM every time it's changed, so the switch effectively remembers its setting through a power cycle. Just be aware of the EEPROM erase/write limit of nominally 100,000 operations.<br />
<br />
The EEPROM storage requirements of this design are reasonably low, at one half of a byte.<br />
<br />
<i>Next time: <a href="http://mycodehere.blogspot.co.uk/2016/05/surround-sound-switch-7-wrapping-up.html">wrapping up</a>.</i>John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-7186964517250748602016-04-21T23:21:00.002+01:002016-05-20T18:43:34.666+01:00Surround Sound Switch #5: Relayer (electromagnetic relays)<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2xTNPUWUGKsJ1ZqPX9XWi_JMswu7DsPJ36eU21OGhzHXxjxQXCaKkbkaPQMA-UhH1xz_Xo7jvu5gDT9v8upKT7yq8T2PnwvcKkKylEjoniaGhCcj1S5YZViAv717VG-l18blzfYd9HDuT/s1600/20160502_190009_cropped.jpg" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2xTNPUWUGKsJ1ZqPX9XWi_JMswu7DsPJ36eU21OGhzHXxjxQXCaKkbkaPQMA-UhH1xz_Xo7jvu5gDT9v8upKT7yq8T2PnwvcKkKylEjoniaGhCcj1S5YZViAv717VG-l18blzfYd9HDuT/s320/20160502_190009_cropped.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Messy business, prototyping.</td></tr>
</tbody></table>
<i>Previously:</i><br />
<blockquote class="tr_bq">
<i><a href="http://mycodehere.blogspot.co.uk/2016/02/surround-sound-reorientation-part-1.html">Mother Of all Relay Boxes</a><br />
<a href="http://mycodehere.blogspot.co.uk/2016/02/surround-sound-reorientation-part-2.html">Rolling your own Commutator</a><br />
<a href="http://mycodehere.blogspot.co.uk/2016/03/surround-sound-switch-3-bulgaria.html">Bulgaria (rotary switches)</a><br />
<a href="http://mycodehere.blogspot.co.uk/2016/04/surround-sound-switch-4-group-theory.html">Group Theory (toggle switches)</a></i></blockquote>
Today I'm going to modify the toggle switch prototype design to use 12 volt relays in place of mechanical switches. I'm talking about electromagnetic relays, not the solid state kind, which are ruled out of analog signal graft by their zero crossing distortion. The use of relays will have certain advantages, which I'll come to in a moment. First, for me personally, there's a significant down side to consider.<br />
<br />
All the prototypes seen so far have been completely <i>passive</i>, in that the only electrical energy passing through the switch circuits has been the amplified audio signal, on its way to the speakers. This feature has had a certain attraction. At home, in our living room, the TV unit conceals under its skirt two six-way mains extension strips, both of which are fully populated and occupied feeding sundry gadgets. Four additional, adjacent wall sockets - each with an integrated USB charge port! - are similarly stuffed with kit that resists further fanout. Finding a power source for an <i>active</i> switch is going to be tricky, but it'll have to be done.<br />
<br />
As to the advantages:<br />
<ol>
<li>A single rotary control can be used to select any of the available speaker configurations.</li>
<li>That manual input itself can later be replaced with an Arduino-based remote control system.</li>
<li>When that great day dawns, I can retire the word "prototype" and take tequila shots.</li>
</ol>
Yes, this has been about the tequila all along. Let's get started!<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPy1SqC49epU1AJto6vcRfHNC4EPGG8Gp_3wiPqfQCmmpvA1iFZM01jfaKaVn6TJY0CJelnxees6zk3S8PURh7n5FBSyOsvos79Xk7veF6HGTLQwHf8PvVsXwabd_kc5NcZT4AafX1EChg/s1600/switch45a.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPy1SqC49epU1AJto6vcRfHNC4EPGG8Gp_3wiPqfQCmmpvA1iFZM01jfaKaVn6TJY0CJelnxees6zk3S8PURh7n5FBSyOsvos79Xk7veF6HGTLQwHf8PvVsXwabd_kc5NcZT4AafX1EChg/s1600/switch45a.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">45° clockwise twist from two 4PDT relays (2 ways).</td></tr>
</tbody></table>
<b>45° Clockwise Rotator</b><br />
<br />
Here on the right is the last of the necessary sub-assemblies, the 45° rotator stage, required to complete our 8-direction relay switch. As was the case with toggle switches, 4PDT is the largest generally available electromechanical relay size, before prices start to escalate, so once again we'll be using them in ganged pairs.<br />
<br />
Two alternative realisations are shown. In the top version, the <i>input</i> signals are connected to the switch commons, while in the bottom version, the <i>outputs</i> are taken from the commons. Now notice the central thin black vertical line, cutting across red, brown, yellow and green wires. These horizontal links between toggles 4/5 and 1/8, in both cases, illustrate that for the first time, the two 4PDT switches (the left and the right halves of the module) are not entirely independent. Both must always be open, or closed, <i>together</i>.<br />
<br />
Let's look at the possible fault conditions. In the top version, activation of the left hand side alone results in amplifier outputs 4/5 becoming shorted together; with the right hand side, it's outputs 1/8. But in the bottom version, left-only activation connects output 8 to speakers 1/8 simultaneously, leaving output 4 open; while right-only connects output 4 to speakers 4/5, leaving 8 open.<br />
<br />
As mentioned previously, the second schema might be preferred when, under conditions of switch sync failure, the consequences of one amplifier being connected to two speakers in parallel are less serious than two amplifier outputs becoming shorted together. The lower of these two versions might appear the more complex, but that's just an artefact of the drawing convention. In reality, and under normal (non-fault) operating conditions, they are physically, electrically, and topologically equivalent.<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUq9eZhegM1bKkMpmxOTPL3xvHCEnZvfMh0RLLDlNyIrbUbPCndlUHGgqUf1rLY37bJpnNNtrqsIiXlaiVedlYwaCbbxu5p2EvgL9khAGlOJiaaHABacHZcfSj6mlDxes7OmyKoutFB2tt/s1600/All+Relays.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUq9eZhegM1bKkMpmxOTPL3xvHCEnZvfMh0RLLDlNyIrbUbPCndlUHGgqUf1rLY37bJpnNNtrqsIiXlaiVedlYwaCbbxu5p2EvgL9khAGlOJiaaHABacHZcfSj6mlDxes7OmyKoutFB2tt/s1600/All+Relays.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">8-Way Switch with Mode 5 support - full cascade diagram - version 1.</td></tr>
</tbody></table>
<b>8 Way Switch with Mode 5 Support</b><br />
<br />
And, semi-finally, here on the left is one complete cascade diagram for the full 8-way rotator, complete with unrestricted Mode 5 support. This version uses the first of the 45° stage implementations just described.<br />
<br />
As before, reading the diagram from the top: four of the seven amplifier outputs (1/2/6/7, those carrying the surround sound information) first arrive at relay RL1, which supplies the option to switch out of 7.x and into Mode 5. The outputs of this stage, together with the three remaining (non-surround related) signals 3/4/5, then reach relays RL2/RL3, which as before, operate in tandem to allow an optional rotation of 180°. Subsequent relays RL4/RL5 again offer a 90° clockwise rotation, while the new, last stage, RL6/RL7, offers a further 45° clockwise twist.<br />
<br />
<table style="border-collapse: collapse; border: 1px solid; color: black; font-family: sans-serif; font-size: 14px; line-height: 22.4px; margin: auto; text-align: center;"><tbody>
<tr><td colspan="3"><b>Relays</b></td><td rowspan="2" style="border: 1px solid; padding: 0.2em 0.4em;"><b>Orientation</b></td><td colspan="8" style="border: 1px solid; padding: 0.2em 0.4em;"><b>Signal Destinations</b></td></tr>
<tr><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>RL2+RL3</b></td><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>RL4+RL5</b></td><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>RL6+RL7</b></td><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>1</b></td><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>2</b></td><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>3</b></td><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>4</b></td><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>5</b></td><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>6</b></td><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>7</b></td><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>-</b></td></tr>
<tr><td style="border: 1px solid; padding: 0.2em 0.4em;">-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">N</td><td style="border: 1px solid; padding: 0.2em 0.4em;">1/-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">2/1</td><td style="border: 1px solid; padding: 0.2em 0.4em;">3</td><td style="border: 1px solid; padding: 0.2em 0.4em;">4</td><td style="border: 1px solid; padding: 0.2em 0.4em;">5</td><td style="border: 1px solid; padding: 0.2em 0.4em;">6/7</td><td style="border: 1px solid; padding: 0.2em 0.4em;">7/-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">8</td></tr>
<tr><td style="border: 1px solid; padding: 0.2em 0.4em;">-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">ON</td><td style="border: 1px solid; padding: 0.2em 0.4em;">NE</td><td style="border: 1px solid; padding: 0.2em 0.4em;">2/-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">3/2</td><td style="border: 1px solid; padding: 0.2em 0.4em;">4</td><td style="border: 1px solid; padding: 0.2em 0.4em;">5</td><td style="border: 1px solid; padding: 0.2em 0.4em;">6</td><td style="border: 1px solid; padding: 0.2em 0.4em;">7/8</td><td style="border: 1px solid; padding: 0.2em 0.4em;">8/-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">1</td></tr>
<tr><td style="border: 1px solid; padding: 0.2em 0.4em;">-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">ON</td><td style="border: 1px solid; padding: 0.2em 0.4em;">-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">E</td><td style="border: 1px solid; padding: 0.2em 0.4em;">3/-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">4/3</td><td style="border: 1px solid; padding: 0.2em 0.4em;">5</td><td style="border: 1px solid; padding: 0.2em 0.4em;">6</td><td style="border: 1px solid; padding: 0.2em 0.4em;">7</td><td style="border: 1px solid; padding: 0.2em 0.4em;">8/1</td><td style="border: 1px solid; padding: 0.2em 0.4em;">1/-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">2</td></tr>
<tr><td style="border: 1px solid; padding: 0.2em 0.4em;">-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">ON</td><td style="border: 1px solid; padding: 0.2em 0.4em;">ON</td><td style="border: 1px solid; padding: 0.2em 0.4em;">SE</td><td style="border: 1px solid; padding: 0.2em 0.4em;">4/-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">5/4</td><td style="border: 1px solid; padding: 0.2em 0.4em;">6</td><td style="border: 1px solid; padding: 0.2em 0.4em;">7</td><td style="border: 1px solid; padding: 0.2em 0.4em;">8</td><td style="border: 1px solid; padding: 0.2em 0.4em;">1/2</td><td style="border: 1px solid; padding: 0.2em 0.4em;">2/-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">3</td></tr>
<tr><td style="border: 1px solid; padding: 0.2em 0.4em;">ON</td><td style="border: 1px solid; padding: 0.2em 0.4em;">-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">S</td><td style="border: 1px solid; padding: 0.2em 0.4em;">5/-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">6/5</td><td style="border: 1px solid; padding: 0.2em 0.4em;">7</td><td style="border: 1px solid; padding: 0.2em 0.4em;">8</td><td style="border: 1px solid; padding: 0.2em 0.4em;">1</td><td style="border: 1px solid; padding: 0.2em 0.4em;">2/3</td><td style="border: 1px solid; padding: 0.2em 0.4em;">3/-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">4</td></tr>
<tr><td style="border: 1px solid; padding: 0.2em 0.4em;">ON</td><td style="border: 1px solid; padding: 0.2em 0.4em;">-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">ON</td><td style="border: 1px solid; padding: 0.2em 0.4em;">SW</td><td style="border: 1px solid; padding: 0.2em 0.4em;">6/-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">7/6</td><td style="border: 1px solid; padding: 0.2em 0.4em;">8</td><td style="border: 1px solid; padding: 0.2em 0.4em;">1</td><td style="border: 1px solid; padding: 0.2em 0.4em;">2</td><td style="border: 1px solid; padding: 0.2em 0.4em;">3/4</td><td style="border: 1px solid; padding: 0.2em 0.4em;">4/-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">5</td></tr>
<tr><td style="border: 1px solid; padding: 0.2em 0.4em;">ON</td><td style="border: 1px solid; padding: 0.2em 0.4em;">ON</td><td style="border: 1px solid; padding: 0.2em 0.4em;">-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">W</td><td style="border: 1px solid; padding: 0.2em 0.4em;">7/-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">8/7</td><td style="border: 1px solid; padding: 0.2em 0.4em;">1</td><td style="border: 1px solid; padding: 0.2em 0.4em;">2</td><td style="border: 1px solid; padding: 0.2em 0.4em;">3</td><td style="border: 1px solid; padding: 0.2em 0.4em;">4/5</td><td style="border: 1px solid; padding: 0.2em 0.4em;">5/-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">6</td></tr>
<tr><td style="border: 1px solid; padding: 0.2em 0.4em;">ON</td><td style="border: 1px solid; padding: 0.2em 0.4em;">ON</td><td style="border: 1px solid; padding: 0.2em 0.4em;">ON</td><td style="border: 1px solid; padding: 0.2em 0.4em;">NW</td><td style="border: 1px solid; padding: 0.2em 0.4em;">8/-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">1/8</td><td style="border: 1px solid; padding: 0.2em 0.4em;">2</td><td style="border: 1px solid; padding: 0.2em 0.4em;">3</td><td style="border: 1px solid; padding: 0.2em 0.4em;">4</td><td style="border: 1px solid; padding: 0.2em 0.4em;">5/6</td><td style="border: 1px solid; padding: 0.2em 0.4em;">6/-</td><td style="border: 1px solid; padding: 0.2em 0.4em;">7</td></tr>
</tbody></table>
<br />
<span style="font-weight: bold;">Encoding & Decoding</span><br />
<br />
The "truth table" above illustrates how the selective energisation of relay pairs 2/3, 4/5 and 6/7 is decoded by the clever wiring into one of eight possible sound stage orientations. The default starting position is labelled "North" by convention. A starter for ten might be in order...<br />
<br />
In first orientation, <b>N</b> for North, all three relay pairs (RL2 through RL7 inclusive) are off. Audio signals #1 through #7 are sent by default to speakers #1 through #7, and the silent "null" signal ("<b>-</b>") arrives at speaker #8. The next row describes orientation <b>NE</b> (North East). Here the signal from amplifier #1 has been dispatched to speaker destination #2, signal 2 to speaker #3, and so on. Speaker #8 springs into life with the output from audio signal #7, while the null signal now goes to speaker #1. So it continues, rotationally, for the remaining six rows of the table.<br />
<br />
It's worth making your default (or most popular) orientation correspond to the direction labelled "N" in this table, where all the relays are off. This should minimise the duty on both the relay coils and the power adapter, prolonging their lives. It also affords the option of powering down the unit under this default condition, and indeed under the majority of <i>fault</i> conditions, preserving a normal surround sound service when copper eventually fractures, or enamel cracks.<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtOg5Ry76yNZg48KSUuO9TjyTGGjiCMuRdPihMfY08zBc7Xu-gIO1uYLH4BkLpv4TOJnDOuUHjnNMbkfJpeBLJYmXeObLtKXTcAP74L-ifG4RzEVUfdhB7QL9uXYMW5wwrrA_Km3x7NEIa/s1600/F4250142-02.jpg" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="136" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtOg5Ry76yNZg48KSUuO9TjyTGGjiCMuRdPihMfY08zBc7Xu-gIO1uYLH4BkLpv4TOJnDOuUHjnNMbkfJpeBLJYmXeObLtKXTcAP74L-ifG4RzEVUfdhB7QL9uXYMW5wwrrA_Km3x7NEIa/s200/F4250142-02.jpg" width="200" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Hex thumbwheel switch.</td></tr>
</tbody></table>
<b>Steering the Beast</b><br />
<br />
One elegant way to drive these relay coils would be using a rotary switch with binary encoded outputs. The one on the left, an Apem SMFB16N1248 thumbwheel device (RS Components stock number <a href="http://uk.rs-online.com/web/p/thumbwheel-switches/4250142/" target="_blank">425-0142</a>), has four hexadecimally coded outputs. The encoding disc tracks are visible through the translucent PCB. We can use the first three, least significant outputs to drive the relay pairs according to the above table, so the switch positions 0 through 7 give us the cycle of eight orientations <b>N</b> through <b>NW</b> listed there.<br />
<br />
<div style="text-align: right;">
</div>
Meanwhile, we can craftily attach the fourth output to drive relay RL1 independently of the rest, so the remaining eight switch positions repeat the full cycle of 8 orientations, but this time with Mode 5 enabled. If it's all the same to you, I'll not bother adding these 8 extra rows to the table, they don't enhance the clarity one iota. <i>Update: OK you win. To facilitate testing, rather than add another 8 full rows to the table, I've changed the content of the four affected Signal Destination columns to the format <b>a/b</b>, where <b>a</b> is the normal 7.x destination, and <b>b</b> is the destination when in Mode 5.</i><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgry3l-nqBVM8-aLmUiwlLs1mSa8-NrCpteAuEKgS6Yey4ayAHANoiON2bpl5lh_6Am-BZTfF0_adx-nrLS7iV8zBG1Ikl7P84e0MtQyAxE9_lw2GG2LkgqOIZ8NZmkHD99cspQgSwZz00r/s1600/Relay+Coil+Wiring.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgry3l-nqBVM8-aLmUiwlLs1mSa8-NrCpteAuEKgS6Yey4ayAHANoiON2bpl5lh_6Am-BZTfF0_adx-nrLS7iV8zBG1Ikl7P84e0MtQyAxE9_lw2GG2LkgqOIZ8NZmkHD99cspQgSwZz00r/s1600/Relay+Coil+Wiring.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Control wiring alternatives.</td></tr>
</tbody></table>
<b>Wiring the Coils</b><br />
<br />
The two alternative PSU / relay coil / thumbwheel switch wiring diagrams on the right show the use of a negative (top) or a positive (bottom) common rail for the coils. The only logical difference between these diagrams is actually the swapping of the ± signs at the PSU, but notice the numbering of the relays, which changes to minimise wire crossovers in the diagram.<br />
<br />
The first case, known as <a href="http://www.w9xt.com/page_microdesign_pt8_pnp_switching.html" target="_blank"><i>high side switching</i></a>, might seem the obvious one to use. But if you plan eventually to dispense with or override the thumbwheel switch, in favour of operating the relays from a remote controlled Arduino or similar device with 5V switchable outputs, it's probably easier to adopt the second diagram. Then you can interface using single npn transistors with freewheeling protection diodes to drive the coils. In the common negative case, you'd need an <a href="http://www.w9xt.com/page_microdesign_pt12_hv_pnp_switching.html" target="_blank"><i>extra transistor</i></a> per relay to control the 12V supply using a 5V signal.<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhi4VVSamT39uB3syPt_GjT1fKjeNW84c_TrEE3JKWQx7PDotHiEi7lH-S-dFA75VEylsLGJR6STAchdoPN_cMuaz1v361yTw1oVVTXjo9UV8_yC8POm-3pAhnR4OoC0P3ja7ZoiPUG2D3E/s1600/20160421_160840.jpg" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhi4VVSamT39uB3syPt_GjT1fKjeNW84c_TrEE3JKWQx7PDotHiEi7lH-S-dFA75VEylsLGJR6STAchdoPN_cMuaz1v361yTw1oVVTXjo9UV8_yC8POm-3pAhnR4OoC0P3ja7ZoiPUG2D3E/s320/20160421_160840.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">What relays, DIN bases and crimps look like.</td></tr>
</tbody></table>
<b>Power Calculations</b><br />
<br />
The 4PDT relays I'm using, mounted on 35mm DIN rail sockets for maintainability, are Maplin Code <a href="http://www.maplin.co.uk/p/5a12vdc-4pdt-miniature-relay-n42aw" target="_blank">N42AW</a> (Beta Electric BMY5-4C5-S-CW) with nickel-silver contacts and 12Vdc, 160Ω coils.<br />
<br />
When looking for a suitable project <a href="http://uk.rs-online.com/web/p/general-purpose-enclosures/0104228/" target="_blank">box</a>, keep in mind that this DIN rail assembly <i>alone</i> will measure 8" x 3" x 3" (200x75x75mm). The <a href="http://mycodehere.blogspot.co.uk/2016/02/surround-sound-reorientation-part-1.html" target="_blank"><i>ADA MORB-1</i></a> that started me on this quest was itself 6.37" x 4.87" x 3" <i>externally</i>. We're going to be quite a bit bigger than that - particularly when we leave prototyping mode and the surrounding cable trunking has to be packed in! But for the extra inches, linear and volumetric, we'll be getting sixteen different sound stage orientations, compared to just two with the MORB-1.<br />
<br />
A parallel pair of these relays will draw a current of<br />
<blockquote class="tr_bq">
I<sub>pair</sub> = 2 * V<sub>dc</sub> / R = 2 * 12V / 160Ω = 150mA</blockquote>
from the 12Vdc supply (75mA might seem a lot for a single 12V relay, but remember it's a 4PDT type, so the mechanical bulk of switch that has to be moved is much greater than for a typical single pole unit). In the worst case (<b>NW</b> orientation, Mode 5 <b>ON</b>) all seven relays will be energised simultaneously, drawing a total current of<br />
<blockquote class="tr_bq">
I<sub>max</sub> = 7 * 75mA = 525mA</blockquote>
from the supply, and dissipating a total power of<br />
<blockquote class="tr_bq">
P<sub>max</sub> = I<sub>max</sub> * V<sub>dc</sub> = 0.525A * 12V = 6.3W.</blockquote>
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhl_NdQ8gHX87fhw84hkbyOUi0Am_Q6-BiWZqzUVjvI0AZHupf_AnjnlJelYJdxiC2cObdIWi9lbN2weBGUHY3k-gL9Ap0bIGZa0-sfgfZv-z5PatnDSZtyJAwf5lyGfionT5am09Y7Z9xK/s1600/F4006759-02.jpg" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhl_NdQ8gHX87fhw84hkbyOUi0Am_Q6-BiWZqzUVjvI0AZHupf_AnjnlJelYJdxiC2cObdIWi9lbN2weBGUHY3k-gL9Ap0bIGZa0-sfgfZv-z5PatnDSZtyJAwf5lyGfionT5am09Y7Z9xK/s200/F4006759-02.jpg" width="144" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">What an AC adapter looks like.</td></tr>
</tbody></table>
<b>Component Selection Ruminations</b><br />
<br />
A 12 volt, one amp wall wart should cover our power requirements comfortably. Possible candidates can be found in the Mascot <a href="http://uk.rs-online.com/web/p/plug-in-power-supply/4006670/" target="_blank">9881 (unregulated)</a> and <a href="http://uk.rs-online.com/web/p/plug-in-power-supply/4006759/" target="_blank">9883 (regulated)</a> series. Might as well use the 2.1mm plug with positive centre pin, just to maximise Arduino compatibility.<br />
<br />
Regulated supplies are almost twice the price of unregulated, but will minimise the PSU ripple, avoiding any 50Hz inductive pickup between the coil drives and (the albeit low impedance) audio leads. Also, we are going to be operating well below the PSU's maximum load at all times, and an unregulated supply can rise well above its nominal voltage under such conditions. <i>Update: bought one of each to test. The unregulated one reads almost 20V on my digital multimeter! No problem though, Arduino's on-board regulator can actually handle <a href="http://playground.arduino.cc/Learning/WhatAdapter" target="_blank">up to 20V in</a>, and like I said, this voltage will plummet once it's asked to energise a relay coil or seven.</i><br />
<br />
For this application, incidentally, the thumbwheel switch shown above is a far better choice than the most popular encoded rotary switch alternatives, which tend to be rated at 150mA on the nose. This one's gold plated contacts are rated for half an amp - at mains voltage! Sounds like a lot, until you realise that the full 525mA under those maximum load conditions will be travelling along its single common contact strip. If anything, that 500mA rating is <i>too damn low</i>. One way to tweak this might be to use a 9Vdc supply instead of a 12Vdc one; the selected 12Vdc Beta relays actually have a "must operate voltage" specification of 75%, or 9Vdc <i>(<a href="https://www.google.co.uk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0ahUKEwimwtfsiKLMAhWDlxoKHZJkAQgQFggfMAA&url=http%3A%2F%2Fwww.beta.com.tw%2Flib%2Fdl_file.php%3Fdir%3Dproducts%2Fp0060%26filename%3Dv7jcMHIqG5zIZOvlhdN5gA&usg=AFQjCNE1TwDLX1NPTyTVlzh_wGAHq80-fg&sig2=bdUCz14_NDW6Nifm_9BOLQ&bvm=bv.119745492,d.d2s" target="_blank">pdf</a>)</i>. The worst case total current then drops below 400mA, with a corresponding drop in power of almost half (down to 3.6W).<br />
<br />
Also, moving from one configuration to another causes the selector switch to visit all positions between the two. This relay power cycling ("chattering") is further exacerbated by the switch's use of plain binary rather than <a href="https://en.wikipedia.org/wiki/Gray_code" target="_blank">Gray encoding</a>. If that's a problem, the answer may be to power down, move the switch to the new target configuration, then power back up. But the thumbwheel design is less prone to this chatter vulnerability, simply because it is more difficult to "spin" rapidly between settings.<br />
<br />
<div style="text-align: right;">
</div>
<div style="text-align: right;">
</div>
<div style="text-align: right;">
</div>
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1bT_9wLVgEan4IvgqQR8bXwztcPFaj9nzVFur3_0sFDzSkGBiS79OkC0Li7k9tXTBlomM1ZWaWCB42Ur2AEbTaw9NMzmpNjDP4A44h1BHgfoRx51Q_feVLoWLftfS8NKk3m4SC7Z94WJj/s1600/RelayBaseDIN.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1bT_9wLVgEan4IvgqQR8bXwztcPFaj9nzVFur3_0sFDzSkGBiS79OkC0Li7k9tXTBlomM1ZWaWCB42Ur2AEbTaw9NMzmpNjDP4A44h1BHgfoRx51Q_feVLoWLftfS8NKk3m4SC7Z94WJj/s1600/RelayBaseDIN.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">4PDT DIN rail relay base.</td></tr>
</tbody></table>
<b>DIN Rail Constraints</b><br />
<br />
To optimise the DIN rail component and wiring layout, all relays will be mounted in a single row, and physically oriented in the same direction.<br />
<br />
However, with either of the switch interconnection conventions mentioned so far, that's a bit of a pest. Both involve a lot of wires from the commons of one relay to the non-common contacts of another. These wires generally cross over from the top side of the DIN rail to the bottom. Why? The relay commons on the DIN rail base, 9-12, are generally on the opposite side from the non-commons, viz. the normally closed 1-4 and the normally open 5-8 <i>(although terminal number 4 seems to have been displaced from its logical position by a mounting hole)</i>.<br />
<br />
In a way it would be preferable to mount the relay pairs side by side in separate ranks, or to rotate alternate pairs, so as to shorten the connecting wire runs. But there is a third alternative, which has already been alluded to. We can rotate alternate banks <i>electrically</i>, by feeding the output commons of one bank to the input commons of the next, and similarly for non-commons. Schematically, the result is that alternate banks are best drawn upside-down. And since we already know the final stage (the 45° rotator) wants its outputs to be on commons, we're led ultimately to a single unique design...<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div style="text-align: right;">
</div>
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjc8FY2UenW2GuezjCHzpudIKSa0qSdZ-nqDLJ0sgfyRelVdtwNvfS4Hs5PFa23fhf6B4SPeigQIunggKsZUi50TBm3IOrgiiSfc2OUBLr77uzk6M6X1blHVFoy6tqp33I-IeWsnN8HMfC-/s1600/DIN-all+with+pin+nos.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjc8FY2UenW2GuezjCHzpudIKSa0qSdZ-nqDLJ0sgfyRelVdtwNvfS4Hs5PFa23fhf6B4SPeigQIunggKsZUi50TBm3IOrgiiSfc2OUBLr77uzk6M6X1blHVFoy6tqp33I-IeWsnN8HMfC-/s1600/DIN-all+with+pin+nos.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">8-Way Switch with Mode 5 support - full cascade diagram - version 2.</td></tr>
</tbody></table>
<b>Relay Final</b><br />
<br />
... this one. Before we start the analysis, here's a quick reminder of the various functions of each of these relay banks:<br />
<blockquote class="tr_bq">
<b>RL1</b> - Mode 5 switch<br />
<b>RL2/RL3</b> - 180° rotator<br />
<b>RL4/RL5</b> - 90° clockwise rotator<br />
<b>RL6/RL7</b> - 45° clockwise rotator</blockquote>
Notice that all the wiring complexity has now migrated to the two places where exclusively non-common relay contacts interface, i.e. between RL1 and bank RL2/RL3, and also between banks RL4/RL5 and RL6/RL7. Meanwhile by contrast, the connections between banks RL2/RL3 and RL4/RL5 have become simple one-to-one links. This is achieved by swapping the internal channel pairs in both RL2 and RL3, so they correspond more closely with their RL4/RL5 counterparts. When wiring a DIN relay rail, especially one surrounded by slotted panel trunking, contact proximity matters less than with toggle switches.<br />
<br />
I've also included input signal #8, the yellow wire in the top right corner, for the first time. Although it's unused in the case of my 7.2 receiver, there are other formats (e.g. 6.x) where its inclusion might be of benefit and/or design guidance value to certain users. However, this rewiring exercise has meant that the green/yellow circuit on RL3 is no longer redundant. Even if you don't have a channel 8 (SB) signal, RL3 can now no longer be demoted to a 3PDT device. And as for 9.x, 11.x and Dolby Atmos users - how do you even add symmetrical front width & height speakers to an octoroom? You're on your own!<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfg6QRcv11gyVugQkmwkJAJMUYS7xe1lBF3_xxeV8MlC7C22u4kXX2uyLptHCXrTZnk90unKamVWXBzE2UbNRqjfz_Cbb3p-vQ7U-Vw33kf_dS7zYAYAzqXvpDe5IRyMqmHMWXWJGynH66/s1600/Relay+Coil+Wiring+Reduced.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfg6QRcv11gyVugQkmwkJAJMUYS7xe1lBF3_xxeV8MlC7C22u4kXX2uyLptHCXrTZnk90unKamVWXBzE2UbNRqjfz_Cbb3p-vQ7U-Vw33kf_dS7zYAYAzqXvpDe5IRyMqmHMWXWJGynH66/s1600/Relay+Coil+Wiring+Reduced.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Control wiring - reduced options.</td></tr>
</tbody></table>
<b>Trimming Down</b><br />
<br />
It might be worth repeating here that each "stage" in the design is optional. To omit Mode 5, just remove RL1 and connect its inputs and outputs using wire colours as a guide. Similarly you can omit the 45° orientations, if you have no need to centre the sound stage in a room corner. Just replace RL6/RL7 with eight links, and remember to shift the wiring of the thumbwheel switch by one position down towards the LSB end.<br />
<br />
The alternative control switch wiring to accompany both of these changes is illustrated on the left, again for both negative and positive common rails. Here, the relay coils have <i>not</i> been renumbered, so those remaining still correspond to the relay numbers in the main audio signal wiring diagram above. And although I'm still showing a 4-bit hex switch for purposes of continuity of illustration, only the two least significant bits of the switch are actually needed now. This could be replaced with a SP4T rotary plus four diodes.<br />
<br />
<b>Building Up</b><br />
<br />
There are 62 individual pieces of colour-coded wire in the audio diagram. I've added suggested relay pin numbers 1-12 to each of the seven relays, but the four circuits within each relay can of course be permuted quite arbitrarily. Using the numbers shown, the table below offers a handy wiring schedule for the audio signal paths. Inputs and outputs are labelled i1..i8 and o1..o8 respectively, and the notation <i>n/p</i> means <i>relay n, pin p</i>.<br />
<br />
<table style="border-collapse: collapse; border: 1px solid; color: black; font-family: sans-serif; font-size: 14px; line-height: 22.4px; margin: auto; text-align: left;"><tbody>
<tr><td colspan="2" style="border: 1px solid; padding: 0.2em 0.4em;"><b>Colour</b></td><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>Inputs</b></td><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>Stage 1</b></td><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>Stage 2</b></td><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>Stage 3</b></td><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>Outputs</b></td></tr>
<tr><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>Brown</b></td><td style="background: brown; border: 1px solid; padding: 0.2em 0.4em;"> </td><td style="border: 1px solid; padding: 0.2em 0.4em;">i1, 1/9</td><td style="border: 1px solid; padding: 0.2em 0.4em;">1/1, 1/6, 2/1, 2/7</td><td style="border: 1px solid; padding: 0.2em 0.4em;">2/9, 4/9</td><td style="border: 1px solid; padding: 0.2em 0.4em;">4/1, 4/8, 6/1, 6/6</td><td style="border: 1px solid; padding: 0.2em 0.4em;">6/9, o1</td></tr>
<tr><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>Blue</b></td><td style="background: blue; border: 1px solid; padding: 0.2em 0.4em;"> </td><td style="border: 1px solid; padding: 0.2em 0.4em;">i2, 1/10</td><td style="border: 1px solid; padding: 0.2em 0.4em;">1/2, 3/1, 3/7</td><td style="border: 1px solid; padding: 0.2em 0.4em;">3/9, 5/9</td><td style="border: 1px solid; padding: 0.2em 0.4em;">5/1, 5/8, 6/2, 6/7</td><td style="border: 1px solid; padding: 0.2em 0.4em;">6/10, o2</td></tr>
<tr><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>White</b></td> <td style="background: white; border: 1px solid; padding: 0.2em 0.4em;"> </td><td colspan="2" style="border: 1px solid; padding: 0.2em 0.4em;">i3, 2/2, 2/8</td><td style="border: 1px solid; padding: 0.2em 0.4em;">2/10, 4/10</td><td style="border: 1px solid; padding: 0.2em 0.4em;">4/2, 4/5, 6/3, 6/8</td><td style="border: 1px solid; padding: 0.2em 0.4em;">6/11, o3</td></tr>
<tr><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>Green</b></td><td style="background: green; border: 1px solid; padding: 0.2em 0.4em;"> </td><td colspan="2" style="border: 1px solid; padding: 0.2em 0.4em;">i4, 3/2, 3/8</td><td style="border: 1px solid; padding: 0.2em 0.4em;">3/10, 5/10</td><td style="border: 1px solid; padding: 0.2em 0.4em;">5/2, 5/5, 6/4, 7/5</td><td style="border: 1px solid; padding: 0.2em 0.4em;">6/12, o4</td></tr>
<tr><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>Red</b></td><td style="background: red; border: 1px solid; padding: 0.2em 0.4em;"> </td><td colspan="2" style="border: 1px solid; padding: 0.2em 0.4em;">i5, 2/3, 2/5</td><td style="border: 1px solid; padding: 0.2em 0.4em;">2/11, 4/11</td><td style="border: 1px solid; padding: 0.2em 0.4em;">4/3, 4/6, 7/1, 7/6</td><td style="border: 1px solid; padding: 0.2em 0.4em;">7/9, o5</td></tr>
<tr><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>Grey</b></td><td style="background: gray; border: 1px solid; padding: 0.2em 0.4em;"> </td><td style="border: 1px solid; padding: 0.2em 0.4em;">i6, 1/11</td><td style="border: 1px solid; padding: 0.2em 0.4em;">1/3, 3/3, 3/5</td><td style="border: 1px solid; padding: 0.2em 0.4em;">3/11, 5/11</td><td style="border: 1px solid; padding: 0.2em 0.4em;">5/3, 5/6, 7/2, 7/7</td><td style="border: 1px solid; padding: 0.2em 0.4em;">7/10, o6</td></tr>
<tr><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>Orange</b></td><td style="background: orange; border: 1px solid; padding: 0.2em 0.4em;"> </td><td style="border: 1px solid; padding: 0.2em 0.4em;">i7, 1/12</td><td style="border: 1px solid; padding: 0.2em 0.4em;">1/4, 1/7, 2/4, 2/6</td><td style="border: 1px solid; padding: 0.2em 0.4em;">2/12, 4/12</td><td style="border: 1px solid; padding: 0.2em 0.4em;">4/4, 4/7, 7/3, 7/8</td><td style="border: 1px solid; padding: 0.2em 0.4em;">7/11, o7</td></tr>
<tr><td style="border: 1px solid; padding: 0.2em 0.4em;"><b>Yellow</b></td><td style="background: yellow; border: 1px solid; padding: 0.2em 0.4em;"> </td><td colspan="2" style="border: 1px solid; padding: 0.2em 0.4em;">i8, 3/4, 3/6</td><td style="border: 1px solid; padding: 0.2em 0.4em;">3/12, 5/12</td><td style="border: 1px solid; padding: 0.2em 0.4em;">5/4, 5/7, 7/4, 6/5</td><td style="border: 1px solid; padding: 0.2em 0.4em;">7/12, o8</td></tr>
</tbody></table>
<br />
This is the schedule used to construct the spaghettified prototype shown in the photo at the head of this article. <i>Yes, that really was a snapshot of this project, not some random </i><i>Google image search</i><i> of bad wiring practices.</i> For reference, if you're playing along at home, the relay numbers are RL1 to RL7 left to right in that photo.<br />
<br />
<b>Testing</b><br />
<br />
It's probably a good idea to test your wiring before interfacing it directly to your fragile amplifier outputs and sensitive loudspeaker inputs. Once you've added the relay coil control wiring to the above schedule, connect the power supply, thumbwheel switch and relay coils together and terminate the audio inputs and outputs in suitable blocks. Then, for each of the 16 positions of the thumbwheel switch, connect one probe of a continuity meter to input 1/2/3/4/5/6/7/8 in turn, and scan the other probe along all 8 outputs, checking that only the intended output rings. You can read these intended output index values from the <i>Signal Destinations</i> columns in the first table at the start of this article. Where there are two values, the first corresponds to thumbwheel settings 0-7, Mode 5 <i>off</i>, and the second to 8-15, Mode 5 <i>on</i>.<br />
<br />
That's a total of 1,024 manual continuity checks. Alternatively, you could wait until we add the Arduino interface next time, and use a simple test utility routine to perform this entire sequence of checks automatically, 120 times per minute. Your call.<br />
<br />
<a href="http://www.falstad.com/circuit/circuitjs.html?cct=$+0+0.000005+382.7625821439906+50+5+43%0A178+848+160+848+192+1+4+0.2+0+0.05+1000000+0.02+160%0A178+848+384+848+352+0+8+0.2+-3.184660373116871e-22+0.05+1000000+0.02+80%0Aw+864+192+864+208+0%0Aw+864+208+880+208+0%0Aw+880+208+880+192+0%0Aw+928+192+928+208+0%0Aw+928+208+1008+208+0%0Aw+1008+208+1008+192+0%0Aw+864+304+864+352+0%0Aw+912+352+912+336+0%0Aw+912+336+976+336+0%0Aw+976+336+976+352+0%0Aw+1200+352+1200+320+0%0Aw+1184+320+1072+320+0%0Aw+1072+320+1072+352+0%0Aw+1104+352+1104+336+0%0Aw+1104+336+1168+336+0%0Aw+1168+336+1168+352+0%0Aw+880+352+880+320+0%0Aw+880+320+1008+320+0%0Aw+1008+320+1008+352+0%0Aw+832+352+832+288+0%0Aw+832+288+960+288+0%0Aw+960+288+960+352+0%0Aw+928+352+928+304+0%0Aw+928+304+864+304+0%0Aw+1024+352+1024+288+0%0Aw+1024+288+1152+288+0%0Aw+1152+288+1152+352+0%0Aw+1056+352+1056+304+0%0Aw+1120+352+1120+304+0%0Aw+1056+304+1120+304+0%0Aw+912+192+912+240+0%0Aw+912+240+1056+240+0%0Aw+1056+240+1056+304+0%0Aw+864+208+864+304+0%0Aw+960+192+960+224+0%0Aw+960+224+1024+224+0%0Aw+1024+224+1024+288+0%0Aw+1008+208+1008+320+0%0Aw+848+160+848+64+0%0Aw+896+160+896+64+0%0Aw+944+96+944+64+0%0Aw+992+112+992+64+0%0Aw+1136+144+1136+64+0%0Aw+1088+128+1088+64+0%0Aw+1040+256+1040+64+0%0Aw+1184+48+1184+320+0%0Aw+1200+320+1184+320+0%0Aw+992+160+992+144+0%0Aw+992+144+1136+144+0%0Aw+1088+128+944+128+0%0Aw+944+128+944+160+0%0Aw+1168+336+1168+112+0%0Aw+1168+112+992+112+0%0Aw+1040+256+960+256+0%0Aw+960+256+960+288+0%0Aw+944+96+1072+96+0%0Aw+1072+96+1072+272+0%0Aw+1072+272+976+272+0%0Aw+976+272+976+336+0%0AR+848+64+848+32+0+0+40+-5+0+0+0.5%0AR+896+64+896+32+0+0+40+-3.3+0+0+0.5%0AR+944+64+944+32+0+0+40+-1.7+0+0+0.5%0AR+992+64+992+32+0+0+40+0+0+0+0.5%0AR+1040+64+1040+32+0+0+40+1.7+0+0+0.5%0AR+1088+64+1088+32+0+0+40+3.3+0+0+0.5%0AR+1136+64+1136+32+0+0+40+5+0+0+0.5%0A178+848+416+848+448+0+8+0.2+-1.4756617926412584e-24+0.05+1000000+0.02+80%0Aw+848+384+848+416+0%0Aw+896+384+896+416+0%0Aw+944+384+944+416+0%0Aw+992+384+992+416+0%0Aw+1040+384+1040+416+0%0Aw+1088+384+1088+416+0%0Aw+1136+384+1136+416+0%0Aw+1184+384+1184+416+0%0Aw+832+448+832+464+0%0Aw+832+464+912+464+0%0Aw+912+448+912+464+0%0Aw+928+448+928+464+0%0Aw+928+464+1008+464+0%0Aw+1008+464+1008+448+0%0Aw+880+448+880+480+0%0Aw+880+480+960+480+0%0Aw+960+448+960+480+0%0Aw+864+448+864+496+0%0Aw+880+496+976+496+0%0Aw+976+448+976+496+0%0Aw+1024+448+1024+464+0%0Aw+1024+464+1104+464+0%0Aw+1104+448+1104+464+0%0Aw+1120+448+1120+464+0%0Aw+1120+464+1200+464+0%0Aw+1200+448+1200+464+0%0Aw+1072+448+1072+480+0%0Aw+1072+480+1120+480+0%0Aw+1152+448+1152+480+0%0Aw+1056+448+1056+496+0%0Aw+1056+496+1168+496+0%0Aw+1168+448+1168+496+0%0A178+848+608+848+576+1+8+0.2+-6.026775428103429e-16+0.05+1000000+0.02+80%0Aw+864+576+880+576+0%0Aw+912+576+928+576+0%0Aw+960+576+976+576+0%0Aw+1008+576+1024+576+0%0Aw+1056+576+1072+576+0%0Aw+1104+576+1120+576+0%0Aw+1152+576+1168+576+0%0Aw+1200+576+1200+560+0%0Aw+1200+560+832+560+0%0Aw+832+560+832+576+0%0Aw+848+608+848+656+0%0Aw+896+608+896+656+0%0Aw+944+608+944+656+0%0Aw+992+608+992+656+0%0Aw+1040+608+1040+656+0%0Aw+1088+608+1088+656+0%0Aw+1136+608+1136+656+0%0Aw+1184+608+1184+656+0%0Aw+1200+464+1200+560+0%0Aw+880+496+880+576+0%0Aw+864+496+880+496+0%0Aw+1056+496+1056+512+0%0Aw+912+464+912+544+0%0Aw+1024+464+1024+576+0%0Aw+960+480+960+528+0%0Aw+1120+576+1120+480+0%0Aw+1120+480+1152+480+0%0Aw+1008+464+1008+544+0%0Aw+1056+512+928+512+0%0Aw+928+512+928+576+0%0Aw+912+544+976+544+0%0Aw+976+544+976+576+0%0Aw+960+528+1072+528+0%0Aw+1072+528+1072+576+0%0Aw+1008+544+1168+544+0%0Aw+1168+544+1168+576+0%0A164+624+192+672+192+0+4+0+0+0+0+false%0Aw+816+560+816+576+0%0Aw+816+560+784+560+0%0Ag+592+560+592+592+0%0Aw+784+400+784+560+0%0Aw+736+576+800+576+0%0As+544+192+624+192+0+1+true%0As+544+288+624+288+0+1+true%0Aw+544+112+544+192+0%0Aw+544+288+544+192+0%0AR+544+112+544+80+0+0+40+5+0+0+0.5%0Aw+752+416+800+416+0%0Aw+768+384+800+384+0%0Aw+720+192+800+192+0%0Aw+816+384+816+400+0%0Aw+816+416+816+384+0%0Aw+816+400+784+400+0%0Aw+816+208+784+208+0%0Aw+784+208+784+400+0%0Aw+816+208+816+192+0%0Aw+768+384+768+224+0%0Aw+768+224+720+224+0%0Aw+752+416+752+256+0%0Aw+752+256+720+256+0%0Aw+736+576+736+288+0%0Aw+720+288+736+288+0%0Aw+784+560+592+560+0%0A" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;" target="_blank"><img border="0" height="189" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrbBpESjpXFcx45ypXpCaIz3DNgFv_THcd3kqUF0yISB9ppi8KszmD6w5eSuFfW0AJTViz4jUFXwnwtkjRtqJelt1VlHB7iU40m7b_-LD9El6NPiG7pXFbQtZBtIAxY08a3qum-_bo3oKd/s200/sim.png" width="200" /></a><b>Simulation</b><br />
<br />
Prior to building and testing this prototype, I did a little simulation to prove the wiring. Here's the thing. Can your browser accept a URL that's over 4KB long? If so, click on <a href="http://www.falstad.com/circuit/circuitjs.html?cct=$+0+0.000005+382.7625821439906+50+5+43%0A178+848+160+848+192+1+4+0.2+0+0.05+1000000+0.02+160%0A178+848+384+848+352+0+8+0.2+-3.184660373116871e-22+0.05+1000000+0.02+80%0Aw+864+192+864+208+0%0Aw+864+208+880+208+0%0Aw+880+208+880+192+0%0Aw+928+192+928+208+0%0Aw+928+208+1008+208+0%0Aw+1008+208+1008+192+0%0Aw+864+304+864+352+0%0Aw+912+352+912+336+0%0Aw+912+336+976+336+0%0Aw+976+336+976+352+0%0Aw+1200+352+1200+320+0%0Aw+1184+320+1072+320+0%0Aw+1072+320+1072+352+0%0Aw+1104+352+1104+336+0%0Aw+1104+336+1168+336+0%0Aw+1168+336+1168+352+0%0Aw+880+352+880+320+0%0Aw+880+320+1008+320+0%0Aw+1008+320+1008+352+0%0Aw+832+352+832+288+0%0Aw+832+288+960+288+0%0Aw+960+288+960+352+0%0Aw+928+352+928+304+0%0Aw+928+304+864+304+0%0Aw+1024+352+1024+288+0%0Aw+1024+288+1152+288+0%0Aw+1152+288+1152+352+0%0Aw+1056+352+1056+304+0%0Aw+1120+352+1120+304+0%0Aw+1056+304+1120+304+0%0Aw+912+192+912+240+0%0Aw+912+240+1056+240+0%0Aw+1056+240+1056+304+0%0Aw+864+208+864+304+0%0Aw+960+192+960+224+0%0Aw+960+224+1024+224+0%0Aw+1024+224+1024+288+0%0Aw+1008+208+1008+320+0%0Aw+848+160+848+64+0%0Aw+896+160+896+64+0%0Aw+944+96+944+64+0%0Aw+992+112+992+64+0%0Aw+1136+144+1136+64+0%0Aw+1088+128+1088+64+0%0Aw+1040+256+1040+64+0%0Aw+1184+48+1184+320+0%0Aw+1200+320+1184+320+0%0Aw+992+160+992+144+0%0Aw+992+144+1136+144+0%0Aw+1088+128+944+128+0%0Aw+944+128+944+160+0%0Aw+1168+336+1168+112+0%0Aw+1168+112+992+112+0%0Aw+1040+256+960+256+0%0Aw+960+256+960+288+0%0Aw+944+96+1072+96+0%0Aw+1072+96+1072+272+0%0Aw+1072+272+976+272+0%0Aw+976+272+976+336+0%0AR+848+64+848+32+0+0+40+-5+0+0+0.5%0AR+896+64+896+32+0+0+40+-3.3+0+0+0.5%0AR+944+64+944+32+0+0+40+-1.7+0+0+0.5%0AR+992+64+992+32+0+0+40+0+0+0+0.5%0AR+1040+64+1040+32+0+0+40+1.7+0+0+0.5%0AR+1088+64+1088+32+0+0+40+3.3+0+0+0.5%0AR+1136+64+1136+32+0+0+40+5+0+0+0.5%0A178+848+416+848+448+0+8+0.2+-1.4756617926412584e-24+0.05+1000000+0.02+80%0Aw+848+384+848+416+0%0Aw+896+384+896+416+0%0Aw+944+384+944+416+0%0Aw+992+384+992+416+0%0Aw+1040+384+1040+416+0%0Aw+1088+384+1088+416+0%0Aw+1136+384+1136+416+0%0Aw+1184+384+1184+416+0%0Aw+832+448+832+464+0%0Aw+832+464+912+464+0%0Aw+912+448+912+464+0%0Aw+928+448+928+464+0%0Aw+928+464+1008+464+0%0Aw+1008+464+1008+448+0%0Aw+880+448+880+480+0%0Aw+880+480+960+480+0%0Aw+960+448+960+480+0%0Aw+864+448+864+496+0%0Aw+880+496+976+496+0%0Aw+976+448+976+496+0%0Aw+1024+448+1024+464+0%0Aw+1024+464+1104+464+0%0Aw+1104+448+1104+464+0%0Aw+1120+448+1120+464+0%0Aw+1120+464+1200+464+0%0Aw+1200+448+1200+464+0%0Aw+1072+448+1072+480+0%0Aw+1072+480+1120+480+0%0Aw+1152+448+1152+480+0%0Aw+1056+448+1056+496+0%0Aw+1056+496+1168+496+0%0Aw+1168+448+1168+496+0%0A178+848+608+848+576+1+8+0.2+-6.026775428103429e-16+0.05+1000000+0.02+80%0Aw+864+576+880+576+0%0Aw+912+576+928+576+0%0Aw+960+576+976+576+0%0Aw+1008+576+1024+576+0%0Aw+1056+576+1072+576+0%0Aw+1104+576+1120+576+0%0Aw+1152+576+1168+576+0%0Aw+1200+576+1200+560+0%0Aw+1200+560+832+560+0%0Aw+832+560+832+576+0%0Aw+848+608+848+656+0%0Aw+896+608+896+656+0%0Aw+944+608+944+656+0%0Aw+992+608+992+656+0%0Aw+1040+608+1040+656+0%0Aw+1088+608+1088+656+0%0Aw+1136+608+1136+656+0%0Aw+1184+608+1184+656+0%0Aw+1200+464+1200+560+0%0Aw+880+496+880+576+0%0Aw+864+496+880+496+0%0Aw+1056+496+1056+512+0%0Aw+912+464+912+544+0%0Aw+1024+464+1024+576+0%0Aw+960+480+960+528+0%0Aw+1120+576+1120+480+0%0Aw+1120+480+1152+480+0%0Aw+1008+464+1008+544+0%0Aw+1056+512+928+512+0%0Aw+928+512+928+576+0%0Aw+912+544+976+544+0%0Aw+976+544+976+576+0%0Aw+960+528+1072+528+0%0Aw+1072+528+1072+576+0%0Aw+1008+544+1168+544+0%0Aw+1168+544+1168+576+0%0A164+624+192+672+192+0+4+0+0+0+0+false%0Aw+816+560+816+576+0%0Aw+816+560+784+560+0%0Ag+592+560+592+592+0%0Aw+784+400+784+560+0%0Aw+736+576+800+576+0%0As+544+192+624+192+0+1+true%0As+544+288+624+288+0+1+true%0Aw+544+112+544+192+0%0Aw+544+288+544+192+0%0AR+544+112+544+80+0+0+40+5+0+0+0.5%0Aw+752+416+800+416+0%0Aw+768+384+800+384+0%0Aw+720+192+800+192+0%0Aw+816+384+816+400+0%0Aw+816+416+816+384+0%0Aw+816+400+784+400+0%0Aw+816+208+784+208+0%0Aw+784+208+784+400+0%0Aw+816+208+816+192+0%0Aw+768+384+768+224+0%0Aw+768+224+720+224+0%0Aw+752+416+752+256+0%0Aw+752+256+720+256+0%0Aw+736+576+736+288+0%0Aw+720+288+736+288+0%0Aw+784+560+592+560+0%0A" target="_blank">this link</a> and you'll be taken to Paul Falstad's infeasibly brilliant website, and in particular, to one of the dozens of amazing projects there: the fully web-based Analog Circuit Simulator Applet. You'll see the above 8-Way Switch with Mode 5 support, version 2, fully realised.<br />
<br />
A couple of things are different. Six of the 4PDT relays have been replaced by three 8PDT devices, these virtual ones being quite a bit cheaper than similar physical relays. And instead of a thumbwheel switch there's a 4-bit binary counter driving the coils. The audio signals entering at the top are represented by a range of DC voltage levels, which appear in the simulation as a colour gradient between red and green. The idea is to click the clock pushbutton on the top left once, wait for things to settle down, then inspect the sequence of colours along the bottom. Obviously these should shift or rotate one place to the right per click, at least for the first seven clicks. Verifying the correct operation of clicks 8 through 15 is a little trickier, these being the Mode 5 orientations.<br />
<br />
Actually you'll have to hold the clock pushbutton down for a good second, until the coil energising pattern changes, then let it go. The simulation is a lot slower than real time, with a circuit of this complexity. About 40x slower by my reckoning - the 2½ minutes it takes to cycle through all 16 orientations represent just 60ms of real time. Still, it certainly did the job of verification I wanted of it, rapidly enough. Paul's little applet is a fantastic achievement.<br />
<br />
<i>Next time: <a href="http://mycodehere.blogspot.co.uk/2016/05/surround-sound-switch-6-arduino-remote.html">remote control</a>!</i>John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0tag:blogger.com,1999:blog-8338396833705157791.post-25202507049913847712016-04-10T15:08:00.000+01:002016-09-27T12:37:35.295+01:00Surround Sound Switch #4: Group Theory (toggle switches)<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjapnO5hRQZr93TXGh5bGms31NakUUkQqGPry74ixFNTCcc3-7rP9bz5bF41sorE87aKceTNRnQKrMv3BRhOJkTOJ_-guJRWZl3scMc6FWp26Q3FNWsJff68t8e2WRl6lBEbgX6dOLI2AKZ/s1600/20160407_125059.jpg" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjapnO5hRQZr93TXGh5bGms31NakUUkQqGPry74ixFNTCcc3-7rP9bz5bF41sorE87aKceTNRnQKrMv3BRhOJkTOJ_-guJRWZl3scMc6FWp26Q3FNWsJff68t8e2WRl6lBEbgX6dOLI2AKZ/s200/20160407_125059.jpg" width="200" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">These little toggle switches<br />
seem worried about something</td></tr>
</tbody></table>
<i>Previously:</i><br />
<blockquote class="tr_bq">
<i><a href="http://mycodehere.blogspot.co.uk/2016/02/surround-sound-reorientation-part-1.html">Mother Of all Relay Boxes</a><br />
<a href="http://mycodehere.blogspot.co.uk/2016/02/surround-sound-reorientation-part-2.html">Rolling your own Commutator</a><br />
<a href="http://mycodehere.blogspot.co.uk/2016/03/surround-sound-switch-3-bulgaria.html">Bulgaria (rotary switches)</a><br />
</i></blockquote>
To avoid excess mechanical wear caused by sliding contacts, we can switch our switch variety, from rotary to toggle. But then we hit another problem. Ever tried to source an affordable, five amp, 7 or 8 pole toggle switch with good contact resistance linearity?<br />
<br />
Let's just see if we really need all those ganged circuits. When switching the sound stage orientation through 45°, say clockwise from North to North East, each pole has to hand off its input signal to the next sequential output contact along the wall. All eight outputs have to work together in harmony to achieve this. But when switching through 90°, say from North to East, the 8-way permutation can be decomposed into two independent 4-way ones. Think of it this way: when rotating through 90°, every <i>corner</i> signal goes to the next <i>corner</i> along, and similarly for every <i>wall-centred</i> signal. Beginning to sense some wiggle room here?<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisdPs9Qr5mjO9_pE2enLKHTPvTCVjP0u7pafZvXUNUegoPGff7LIow2HX3VIe_Hmv2ZcpWvkN2YvFWsehhD7qV1d_zFcEayvc5SyseWlHrA7uFKE-zw-AQcPYmhuvsm0esJcQt8B1DoBZO/s1600/room1i.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisdPs9Qr5mjO9_pE2enLKHTPvTCVjP0u7pafZvXUNUegoPGff7LIow2HX3VIe_Hmv2ZcpWvkN2YvFWsehhD7qV1d_zFcEayvc5SyseWlHrA7uFKE-zw-AQcPYmhuvsm0esJcQt8B1DoBZO/s1600/room1i.png" /></a></div>
<b>Here Comes The Science</b><br />
<br />
Mathematically, we can represent the original 45° turn by the permutation<br />
<blockquote class="tr_bq">
1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 1</blockquote>
where the numbers 1 through 8 represent the speaker signals, numbered as usual starting from "1" behind your left ear, then counting round from left to right in front of you, and ending with "8" directly behind your head. The permutation moves each signal to the next position along in the list, while 8 completes the circular trip by moving into position 1. This is conventionally expressed in a more succinct "cycle" notation:<br />
<blockquote class="tr_bq">
(12345678)</blockquote>
The cycle can't be broken down into two or more shorter sub-cycles, which is why a full 8 pole switch (well okay, 7 pole with an additional "null" signal) is required to implement it. Yeah, we could use two 4-pole switches, but there'd be potential for a transient error, when one switch is temporarily up while the other is down; then one of two bad things can happen. Either (a) an amplifier output signal connects simultaneously to two separate speakers; suddenly, that one particular 8Ω amplifier output is staring down a 4Ω load, and likely none too pleased about it. Or (b) two separate amplifier outputs get shorted together. That can be still worse news.<br />
<br />
I'll talk about wiring layouts later. For now, I'll just note that the convention I follow in these prototypes (basically, <i>inputs</i> go to switch <i>commons</i>) leads to the more dangerous of these two possibilities, if we use a 45° turn. Also remember that in this context, <i>common</i> refers to the pole of a switch or relay, and not to the common negatives on the amplifier output or loudspeaker connecting wires - all of which are assumed to be permanently connected together, and otherwise go unmentioned in this series.<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJwTZ9Gq7TZFIChfY84ondA9f-Fix_F1skJK8mcYWt5P89rbhobYxRXrqZJkMlIHyylJnlmSRuPfBB6L-vZ60mvONTP3iDsFVq7toWSHWaE2zmCqcXyl7G0x3LEsTRA2J2N63E6BxJ7vpJ/s1600/switch1.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJwTZ9Gq7TZFIChfY84ondA9f-Fix_F1skJK8mcYWt5P89rbhobYxRXrqZJkMlIHyylJnlmSRuPfBB6L-vZ60mvONTP3iDsFVq7toWSHWaE2zmCqcXyl7G0x3LEsTRA2J2N63E6BxJ7vpJ/s1600/switch1.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">90° clockwise twist from two 4PDT switches</td></tr>
</tbody></table>
Now contrast the 45° case with the permutation for a 90° turn,<br />
<blockquote class="tr_bq">
1 → 3 → 5 → 7 → 1<br />
2 → 4 → 6 → 8 → 2</blockquote>
or again in the more succinct conventional notation of mathematical permutations,<br />
<blockquote class="tr_bq">
(1357) (2468)</blockquote>
Here, the corner speakers - the odd numbered ones - are permuted among themselves, and similarly for the even numbered, wall-centred ones. The two groups don't interact. This means the two-switch solution described above can work safely here, if we're willing to sacrifice 45° rotations and use only multiples of 90°. And to be honest, the steadily increasing size of display surfaces these days, both monitors and projector screens, has pushed the milieu of the corner TV unit firmly into the past. I'm fine with 90° increments for now (though I will be returning to 45° before the end of this series).<br />
<br />
On the subject of having to flick two independent toggle switches to achieve a particular rotation, and the fact that intermediate, one-up-one-down configurations have no useful purpose, I say <i>meh</i>. We've all seen cases in the field where toggle switches have been physically ganged together with mechanical clamps; go ahead and do that, if the two-switch-operation thing bugs you. I'll be happy with a line joining the two switches notionally, engraved on top of the grey ABS box lid, filled in with my favourite black crayon.<br />
<br />
<b>Are You Cereal?</b> <i>(a short diversion)</i><br />
<blockquote class="tr_bq">
Shut up.<br />
I did <i>not</i> design and build a binary clock in the 1970s.<br />
<i>You</i> designed and built a binary clock in the 1970s.</blockquote>
<iframe allowfullscreen="" frameborder="0" height="315" src="https://www.youtube-nocookie.com/embed/CEVsbk4eWt8?rel=0" width="420"></iframe><br />
<blockquote class="tr_bq">
Hey, if it's good enough for <a href="http://wilwheaton.net/2015/09/a-family-photo-of-sorts/" target="_blank">Wil Wheaton</a>... listen, having to set up your sound stage rotator by encoding binary numbers into toggle switches is a perfectly respectable thing for any nerd to do. In fact imma aggravate your disapproval, by labelling the switches with abstract symbols. Let's see, we'll use a <i><b>square</b></i> for the one that cycles the four corner speakers through 90°, and a <i><b>diamond</b></i> for the four wall-centric ones. Also, that was a <i>BCD</i> clock, not a <i>binary</i> one. <i>Sheesh</i>.</blockquote>
And, we're back in the room. Two of those 4-pole switch pairs wired in cascade give us a 180° rotation, and three pairs 270°. But we can be more economical. Consider implementing the 180° case on its own. We can rotate the room through 180° by four independent but simultaneous pairwise swaps. The associated permutation can be written as:<br />
<blockquote class="tr_bq">
(15) (37) (26) (48)</blockquote>
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfU-ZyY7w_i7kxv1LFjKTo3FPf6iXbtm4QPk0a8WR3xhjs5CgY02IBmk19ZabQPAe_oCCYr4XzAz3aIrZndNMoQC8He3XGR3mVrnnMnWGmeiG4dKacRjyeyPSwrXmpWGLd64FRm8exD_WF/s1600/switch2.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfU-ZyY7w_i7kxv1LFjKTo3FPf6iXbtm4QPk0a8WR3xhjs5CgY02IBmk19ZabQPAe_oCCYr4XzAz3aIrZndNMoQC8He3XGR3mVrnnMnWGmeiG4dKacRjyeyPSwrXmpWGLd64FRm8exD_WF/s1600/switch2.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">180° twist from two 4PDT switches (or four DPDTs)</td></tr>
</tbody></table>
These 2-cycles could be implemented with just four DPDT toggles. But since we already assume the availability of 4PDT switches, we might be better off with another couple of those instead. So now we have one pair of switches capable of providing a 90° rotation, and another pair providing 180°. We need new symbols for the two new switches. How about a big <i><b>plus sign</b></i> for the one that swaps North with South and East with West, and an <i><b>X</b></i> to swap NE with SW and NW with SE.<br />
<br />
Now for The Big Reveal: we can cascade these two solutions. When <i>both</i> switch pairs are operated, the 90° and the 180°, then voila, hey presto and alta vista, the composite permutation yields the missing 270° rotation!<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi326wP18RDQqOktzarGy7rvwqwqqbjrzii6hHlfBXnM7gDC-_uOzTuVrJpdRqtBJD6mQJjWgDmSgWEOoVx37LUjrbxhTgLn9cRQyVQe5IiQPYuByXm9nTq9BjLtXc2Ie4p7TrpGzyd5uQD/s1600/room+5.1+vanilla.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi326wP18RDQqOktzarGy7rvwqwqqbjrzii6hHlfBXnM7gDC-_uOzTuVrJpdRqtBJD6mQJjWgDmSgWEOoVx37LUjrbxhTgLn9cRQyVQe5IiQPYuByXm9nTq9BjLtXc2Ie4p7TrpGzyd5uQD/s1600/room+5.1+vanilla.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">A reminder of Mode 5</td></tr>
</tbody></table>
<b>About Compositions</b><br />
<br />
So far we have been splicing together our emerging 4- and 2-cycles in arbitrary order, without a care in the world. That's okay, because these permutations represent rotations in a 2D plane, and such transformations are commutative <i>(the same can't be said about 3D rotations - try rotating your teacup 90º about a horizontal and then a vertical axis (careful!), noting its final orientation; then, return to the starting position and apply the two rotations in the opposite order - the results are different)</i>.<br />
<div style="text-align: right;">
</div>
<div style="text-align: right;">
</div>
<br />
Taking this idea further, suppose we decide to ignore the crossed-signals danger, and implement a 45° turn using two 4PDT toggle switches after all. This is something I'll actually be doing in a later prototype, but using 4PDT electromechanical relays instead of manual toggle switches, so the danger of damage can mitigated (for example by wiring the two ganged relay coils in series, so if either one fails open-circuit, neither will operate on its own). With this new stage in place, we now have three switch pairs, implementing their respective 180°, 90° and 45° rotations, which can be cascaded <i>in any order</i> to provide all eight compass points.<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; text-align: right;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiy1Vi0cZ05idlhRSP-Ak8dX9lngkBJlCNWfg2YfzUg0lVv3FNP2HBRLI5PMckc5nHOhQMShMARtjHRqLVxwkCqJy7ed2O0OLZfVdwHveuP8NFBv96j7V1E8DbMvvOLi13Ef9O5V4mY-xrk/s1600/switch3.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiy1Vi0cZ05idlhRSP-Ak8dX9lngkBJlCNWfg2YfzUg0lVv3FNP2HBRLI5PMckc5nHOhQMShMARtjHRqLVxwkCqJy7ed2O0OLZfVdwHveuP8NFBv96j7V1E8DbMvvOLi13Ef9O5V4mY-xrk/s1600/switch3.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Mode 5 switch</td></tr>
</tbody></table>
<b>A Prepended Bend</b><br />
<br />
There's one last twist I'd like to add to this prototype, and this one's not a 2D rotation. It's the "Pure 5.x" mod mentioned at the end of the previous article, whereby the (in this case silent) SBL/SBR signals are dropped, and replaced by the SL/SR signals, in order to stretch the five channel mix across the whole available room space. We can see this isn't a rotation, because signal #2 moves counterclockwise by 45°, while signal #6 moves clockwise by the same angle. In fact, it's not even a permutation; the dropped SBL/SBR signals aren't rerouted to anywhere (though since they're presumed silent, it could in fact be implemented using two partially redundant swaps).<br />
<br />
The effect can be achieved with the help of just one more 4PDT switch, but to get the desired results, this new circuit must be the <i>first</i> transformation applied to the amplifier outputs (it doesn't <i>commute</i> with the 2D rotations). After that, we're free once more to apply the rotation stages <i>in any order</i>, and the net room rotation will be applied correctly to the new 5.x configuration. Can't think of a suitable abstract symbol for this operation, so I'll maybe just label the two switch positions <b><i>5</i></b> (normally open) and <b><i>7</i></b> (normally closed).<br />
<br />
Another example of a non-commutative transformation is the permutation that swaps left and right:<br />
<blockquote class="tr_bq">
(35) (26) (17)</blockquote>
Harder to make a case for this one, as any permutation that swaps your left and right ears, while leaving you facing the same direction, also has the unfortunate side effect of turning you upside down. But if your particular school of yoga demands it, you may note that this swap <i>does</i> commute with the 5.x, so these two can be performed in either order, prior to the rotations.<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGw5__KKUQ8-WLDCGQfW34jYqHRTX7aGxINuu04lBhgwrfP2JqMxwjB-Y-GH6j7vXOfLX7S3Zdo55dgdBW6NwICHGdUgNbb3DkoAPtHeTcSyAZGn5UP-hhLWDPvvQoA5ORkwMld2781-yY/s1600/All+Switches.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGw5__KKUQ8-WLDCGQfW34jYqHRTX7aGxINuu04lBhgwrfP2JqMxwjB-Y-GH6j7vXOfLX7S3Zdo55dgdBW6NwICHGdUgNbb3DkoAPtHeTcSyAZGn5UP-hhLWDPvvQoA5ORkwMld2781-yY/s1600/All+Switches.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Full cascade wiring diagram with<br />
nerd-appropriate switch function indicator symbols</td></tr>
</tbody></table>
<b>Altogether Now</b><br />
<br />
So here's the full cascade in a single composite circuit diagram. Seven correctly colour coded amplifier signals come in along the top row. These are fed successively through (1) a Mode 5 switch; (2) a 180° rotator (notice the optimization of the <b>+</b> branch, which has freed one whole SPDT stage); and finally (3) a 90° clockwise rotator. Eight speaker driver signals emerge at the bottom, where the illustrated colour coding assumes that all switches are left in their default positions.<br />
<br />
With all the switch contacts in their default positions, note that no two wires of different colours ever touch. This is electrically obvious; it's only when one or more switches get operated, that the interchanging of signal paths can begin. For example, there is a complete, isolated path of brown wire running all the way from input 1 to output 1, blue wire from 2 to 2, and so on. But when the 7/5 switch is operated, input 1 gets disconnected, and input 2 (blue) joins the remainder of input 1's previous path (brown).<br />
<br />
I mentioned wiring layout practices earlier. This diagram follows the convention, started with the 7P8T rotary switch built last time, that <i>incoming signals go to switch commons</i>, and <i>outputs are taken from other contacts</i>. Note however that all three component diagrams above have the same colour sequence top and bottom. Any/all of these could be rewired to move the switch commons from top to bottom, obtaining an alternative convention, such as <i>external wiring, whether carrying inputs or outputs, always connects to switch commons</i>. This might be done to tidy up the cable harnessing, or as mentioned above, to mitigate the consequences of certain failure modes.<br />
<br />
<b>Elephant Number One</b><br />
<br />
At this point, let's try to look ahead a wee bit, and address that fat elephant standing over there between the coffee table and the lava lamp. Yes, our "final" prototype might squeeze every amplifier output signal through two or more series pairs of switch contacts - losing a bit of power and linearity at every junction, on its way to its designated speaker. Worse still, it might use crimp (cold weld) spade contacts for tidiness and maintainability, so right there we'll have another four dry metal connections per contact pair.<br />
<br />
<i>Update: Every prototype switch construction in this series is undertaken as a learning experience. What this one taught me is that crimping is an underrated skill. It's also exactly like riding a bike, in the sense that I can't do it.</i><br />
<br />
Should excess contact impedance stop us? No! Our switch / relay contacts will always be beefy enough (5A minimum, remember?) to withstand a little local heating, and there will always be more juice in the tank without tilting the volume control to anything within sight of 11.<br />
<br />
As for linearity: any decent AV receiver has an automatic setup procedure using audio frequency sweeps and blasts of pink noise to analyse the acoustic properties of a room. This process will interpret any switch contact non-linearity that it finds as being just another boring old acoustic imperfection in the architecture, and will make the necessary adjustments to the sound processing profile. Of course, this means repeating that setup procedure once for every available sound stage orientation. If we're lucky, our receiver will have enough memory slots to store eight (or however many we need) such independent profiles.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyfHghyphenhyphenQ5wetkumeJqoz1ubL7VCP0NRSi2z7J_XOu2UWArUdgKWRzX6OOeCMKfNVxkO5SU-tTZoZ58EjRB34_5FrR9cJieA2XbbzH46XFYW4gVJ0tSI-PX_-JfgX8aUw0la1Nab9ssO_Bs/s1600/elephant.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyfHghyphenhyphenQ5wetkumeJqoz1ubL7VCP0NRSi2z7J_XOu2UWArUdgKWRzX6OOeCMKfNVxkO5SU-tTZoZ58EjRB34_5FrR9cJieA2XbbzH46XFYW4gVJ0tSI-PX_-JfgX8aUw0la1Nab9ssO_Bs/s1600/elephant.png" /></a></div>
<b>Elephant Number Two</b><br />
<br />
In the prototype version using those quite large screw-terminal toggle switches, there was just enough room in my 185x115mm project box to fit the four main switches in one horizontal row, with the Mode 5 switch offset vertically at one end. This meant some of the contacts from adjacent switches were quite close together, and could conceivably cause a short circuit, were the switch fitting nuts to work loose and allow rotation.<br />
<br />
The solution I chose was a bit of <a href="http://uk.rs-online.com/web/p/electrical-chemical-insulating-films/7757794/" target="_blank">elephant hide</a>, cut to the pattern shown, then folded to form insulating fins when mounted between the switches and the back of the box lid. If you want to use this approach, but find your elephant hide supplied in smaller sheets, by all means use two pieces, but do ensure that each piece incorporates at least two switch mounting holes, again to prevent rotation.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiV6yr0J2vryYG-juC4-l27j4IkHRgMpF4Bj4oUJCGIekqePBYGDnRwFjYIce7NZFEuRm3BQeasQu7QNKfc_DdZsqCKNTMAvoVHyytpW4lGWU30x6beS1crbXNFSWaAZN0l72eTNMq9Hvng/s1600/20160415_074745.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiV6yr0J2vryYG-juC4-l27j4IkHRgMpF4Bj4oUJCGIekqePBYGDnRwFjYIce7NZFEuRm3BQeasQu7QNKfc_DdZsqCKNTMAvoVHyytpW4lGWU30x6beS1crbXNFSWaAZN0l72eTNMq9Hvng/s400/20160415_074745.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Nothing to see here.<br />
Seriously, go back and have another look at the circuit diagram.<br />
It's a lot nicer.</td></tr>
</tbody></table>
<b>Lessons Learned</b><br />
<br />
Maybe I should have learned to crimp. Then there'd have been an incentive to harness up all these wires properly, all neat and tidy. But hey, it's just a prototype. And anyway, isn't tidy wire harnessing bad for crosstalk?<br />
<br />
<div style="text-align: right;">
</div>
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-f0fH_PNN1b6kPUvpTQxXWQOYSWCPe6ucjL3zWeLqwOSBGC8fpCmsW8PctYZgFnajgwrb6C0IbyB-B671mJ9CAlIY7GfyRVisTOucUOuV1L8y8-DtI9ovqoK5BxhXMagdn_YKYVynXfPm/s1600/20160420_135354.jpg" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-f0fH_PNN1b6kPUvpTQxXWQOYSWCPe6ucjL3zWeLqwOSBGC8fpCmsW8PctYZgFnajgwrb6C0IbyB-B671mJ9CAlIY7GfyRVisTOucUOuV1L8y8-DtI9ovqoK5BxhXMagdn_YKYVynXfPm/s320/20160420_135354.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Toggle switch prototypes 2a & 2b.</td></tr>
</tbody></table>
I don't think I'd use Daier 4PDT screw terminal toggle switches again - or any of their switches actually, When I tried tightening these on the project box lid, even quite gently, about half of the fixing nuts would jump threads. That's some poor mechanical construction, and there's no reason to think it won't be the same with any other design of theirs.<br />
<br />
The second toggle-based prototype "2b" (b for blue) worked out a lot better, using the smaller but sturdier <a href="http://www.maplin.co.uk/p/toggle-switch-4pdt-fh08j" target="_blank">Maplin 4PDT switch</a> with solder terminals. These have a chrome plated brass dolly, and silver plated contacts rated 6A at 125Vac. Had to change back from multi strand to the more brittle single core wire, due to terminal space restriction and the danger of loose strand shorts (which are <i>so</i> last season), but it's all good.<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzLxwP5dyXH5FU3dC1ML64mbooVhMpfvJULif1XBIh9m2WtWLTzYl8oCfbHsmmK3xd2mJx52HWMePEXW0Mdn0kRcTKhNRXId8oP4deT9JqBQBK2EEsEHRZLVgH7FgqwFme7BF5nn454Y8k/s1600/20160925_115914.jpg" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="180" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzLxwP5dyXH5FU3dC1ML64mbooVhMpfvJULif1XBIh9m2WtWLTzYl8oCfbHsmmK3xd2mJx52HWMePEXW0Mdn0kRcTKhNRXId8oP4deT9JqBQBK2EEsEHRZLVgH7FgqwFme7BF5nn454Y8k/s320/20160925_115914.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Get an octoroom.</td></tr>
</tbody></table>
<b>Finishing Off</b><br />
<br />
Here's prototype 2a, in a rather romantic pose, ready for installation. The in/out connections are fed through rubber grommets and terminated in a cable mounted plug/socket pair, as befits a prototype (production units by contrast would use panel mounted connectors, which are fussier to drill and fit).<br />
<br />
<i>Next time: <a href="http://mycodehere.blogspot.co.uk/2016/04/surround-sound-switch-5-relayer.html">Relayer</a>.</i>John M Kerrhttp://www.blogger.com/profile/09707247617873542958noreply@blogger.com0