Wednesday, 3 April 2024

Th' Loue Song o' J. Alfred Prufrock

(1917) by T. S. Eliot
Scots translation by John M. Kerr

If ah bit thought that mah response wur made
To yin mibbie returning tae th' world,
This tongue o' flame wid cease tae flicker.
But sin, up fae thae depths, nae yin haes yet
Returned alive, if whit ah hear is true,
I answer wi'oot fear o' bein' shamed.


LET us gang then, ye 'n' I,
When th' forenicht is spread oot against th' sky
Like a patient etherized upon a table;
Let us gang, thro' certain half-deserted streets,
The mutterin' retreats
O' restless nights in wan-night cheap hotels
An' sawdust restaurants wi' oyster-shells:
Streets that follow lik' a tedious argument
Of insidious intent
Tae leid ye tae an overwhelmin' quaistion. . . .
Oh, dae nae ask, "Whit is it?"
Let us gang an' mak' oor visit. 

In th' room th' wimmin come 'n' go
Talking o' Michelangelo.

The yellow rowk that rubs tis back upon th' windae-panes,
The yellow smoke that rubs tis muzzle oan th' windae-panes,
Licked tis tongue intae th' corners o' th' evenin',
Lingered upon th' pools that staun in drains,
Let faw upon tis back th' soot that faws fae chimneys,
Slipped by th' terrace, made a sudden leap,
And seein' that it wis a saft Ochtober nicht,
Curled wance aboot th' hoose, an' fell asleep. 

An' indeed thare wull be time
For th' yellow smoke that slides alang th' street,
Rubbin' tis back upon th' windae-panes;
Thare wull be time, thare wull be time
To prepare a face tae meet th' faces that ye meet;
Thare wull be time tae murder 'n' create,
And time fur a' th' wirks 'n' days o' hauns
That hurl 'n' drap a quaistion oan yer plate;
Time fur ye 'n' time fur me,
And time yit fur a hundred indecisions,
And fur a hundred visions an' revisions,
Afore th' takin' o' a toast 'n' tea. 

In th' room th' wimmin come 'n' go
Talking o' Michelangelo.

And indeed thare wull be time
Tae wonder, "Do ah dare?" an', "Do ah dare?"
Time tae caw back 'n' descend th' stair,
Wi' a bald bit in th' middle o' ma hair—
(They wull say: "How his locks is grawin thin!")
My mornin' jaiket, collar mountin' firmly tae th' chin,
My necktie rich 'n' modest, bit asserted wi' a simple pin—
(They wull say: "But how his arms 'n' legs ur thin!")
Do ah dare
Disturb the universe?
In a minute thare is time
For decisions an' revisions whilk a minute wull reverse. 

Fur ah hae kent thaim a' awready, kent thaim aw:
Have kent th' evenin's, mornin's, efternoons,
I hae maisured oot mah lee wi' coffee spoons;
I ken th' voices dyin' wi' a dyin' faw
Beneath th' strathspeys fae a farther room.
    So how shuid ah presume?

And ah hae kent th' een awready, kent thaim aw—
The een that fix ye in a formulated phrase,
And whin a'm formulated, sprawlin' oan a pin,
When a'm pinned 'n' wriggling oan th' wall,
Then how shuid ah begin
To spit oot a' th' butt-ends o' mah days 'n' ways?
    And how shuid ah presume? 

An' ah hae kent th' erms awready, kent thaim all—
Erms that ur braceleted 'n' white 'n' bare
(But in th' lamplight, downed wi' light broon hair!)
Is it perfume fae a dress
That mak's me sae digress?
Arms that lie alang a buird, or wrap aboot a shawl.
    And shuid ah then presume?
    And how shuid ah begin? 

*       *       *       *

Shall ah say, ah hae gaen at gloaming thro' narrow streets
And gawked th' smoke that rises fae th' pipes
Of lonely men in shirt-sleeves, leanin' oot o' windaes? . . .

I shuid hae bin a pair o' ragged claws
Scuttlin' across th' floors o' silent seas. 

*       *       *       *

An' th' efternoon, th' forenicht, sleeps sae peacefully!
Smoothed by lang fingers,
Asleep . . . Tired . . . Or it malingers,
Stretched oan th' flair, 'ere beside ye 'n' me.
Should ah, efter cuppa cakes 'n' ices,
Have th' braun tae force th' moment tae tis crisis?
But though ah hae wept 'n' fasted, wept 'n' prayed,
Though ah hae seen mah heid (a wee bit bald) brought in upon a platter,
I am nae prophet—and here's nae great matter;
I hae seen th' moment o' mah greatness flicker,
And ah hae seen th' eternal footman haud mah coat, 'n' snicker,
And in short, ah wis afeart. 

An' wid it hae bin worth it, efter all,
After th' cups, th' marmalade, th' tea,
Among th' porcelain, among some chat o' ye 'n' me,
Would it hae bin worth while,
To hae bitten aff th' maiter wi' a smile,
To hae squeezed th' universe intae a ball
To roll it tae some overwhelming question,
To say: "I am Lazarus, come fae th' dead,
Come back tae tell ye a', ah shall tell ye all"—
If yin, settling a pillow by her head,
    Should say: "That insae whit ah meant at all;
    That insae it, at all." 

An' wid it hae bin worth it, efter all,
Would it hae bin worth while,
After th' sunsets 'n' th' dooryards 'n' th' sprinkled streets,
After th' novels, efter th' teacups, efter th' skirts that trail alang th' flair—
An' this, an' sae mucch mair?—
It is impossible tae say juist whit ah mean!
But as if a magic lantern threw th' nerves in patterns oan a screen:
Would it hae bin worth while
If yin, settling a pillow or throwing aff a shawl,
And turning toward th' windae, shuid say:
    "That insae it at all,
    That insae whit ah meant, at all." 

*       *       *       *

Naw! Amurnay Prince Hamlet, nor wis meant tae be;
Am an attendant laird, yin that wull dae
To swell a progress, stairt a scene or twa,
Advise th' prince; na doubt, an easy tool,
Deferential, glad tae be o' use,
Politic, canny, 'n' meticulous;
Full o' heich sentence, but a wee bit obtuse;
At times, indeed, a'maist ridiculous—
A'maist, at times, the fool.

I graw auld . . . I graw auld . . .
I shall wear th' bottoms o' mah breeks a' rolled.

Shall ah pairt mah locks behind? dae ah dare tae sloch a peach?
I shall wear white flannel breeks, 'n' donder on th' beach.
I hae heard th' mermaids singing, ilk tae each.

I dae nae think that thay wull sing tae me.

I hae seen thaim riding seaward oan th' waves
Combing th' white locks o' th' waves blown back
When th' win` blows th' water white an' black.

We hae lingered in th' chambers o' th' sea
By sea-girls wreathed wi' seaweed rid 'n' broon
Till human voices wake us, an' we droon. 


Copyright

T.S. Eliot’s “The Love Song of J. Alfred Prufrock” is available as HTML, for Kindle, Plain Text and other formats, archived as a public domain text in the collection Prufrock and Other Observations on the Project Gutenberg website:

http://www.gutenberg.org/ebooks/1459

This Scots translation is posted under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) license as follows:

You are free to:

Share — copy and redistribute the material in any medium or format Adapt — remix, transform, and build upon the material The licensor cannot revoke these freedoms as long as you follow the license terms.

Under the following terms:

Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
NonCommercial — You may not use the material for commercial purposes.
ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.


Wednesday, 5 May 2021

Don't Worry (Paul O'Brien)

Song written by Paul O'Brien. Guitar & vocals by Paul O'Brien.
Additional instrument parts written & performed by John Michael Kerr.
Sound production & captioning by John Michael Kerr.

Watch on YouTube

Instrumentation

  1. Video - Paul's original iPhone recording (audio not used)
  2. Vocals - Condenser mic through Behringer UMC204HD
  3. Guitar - Fender semi-acoustic
  4. Drums - 64 Pad Kit Rock
  5. Piano - East Village Grand
  6. Violins 1 - BBC SO Discover
  7. Violins 2 - BBC SO Discover
  8. Violas - BBC SO Discover

Production Notes

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!

Critical Response

From Paul's Facebook page:

  • Nothing yet!

Friday, 30 April 2021

Stations Of The Cross (Paul O'Brien)

Song written by Paul O'Brien. Guitar & vocals by Paul O'Brien.
Additional instrument parts written & performed by John Michael Kerr.
Sound production & captioning by John Michael Kerr.

Watch on YouTube

Instrumentation

  1. Video - Paul's original iPhone recording (audio not used due to poor recording quality)
  2. Vocals - Condenser mic through Behringer UMC204HD
  3. Guitar - Fender semi-acoustic
  4. Recorders - Ableton Core "Recorder Keys"
  5. Violins - BBC SO Discover
  6. Double Bass - BBC SO Discover

Production Notes

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..."

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.

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.

Vocals have a tweaked "Vocal Presence" filter applied, and Guitar uses "Acoustic Git EQ 1".

Critical Response

From Paul's Facebook page:

  • SO beautiful!!! ❤️
  • Hey, Elenor Rigby, where's ur hair shirt? ( good song)

Previously: Midnight Sky Of Blue

Next time: Don't Worry

Thursday, 29 April 2021

Midnight Sky Of Blue (Paul O'Brien)

Song written by Paul O'Brien. Guitar & vocals by Paul O'Brien.
Additional instrument parts written & performed by John Michael Kerr.
Sound production & captioning by John Michael Kerr.


Instrumentation

  1. Video - Paul's original iPhone recording
  2. Vocals - Condenser mic through Behringer UMC204HD
  3. Guitar - Fender semi-acoustic
  4. Bass Guitar - Core Library Guitar Bass
  5. Drums - 707 Core Kit
  6. Pedal Steel Guitar, Harmony Mono - Impact Soundworks
  7. Pedal Steel Guitar, Poly Legato - Impact Soundworks

Production Notes

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.

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.

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.

Critical Response

From Paul's Facebook page:

  • A beautiful masterpiece Paul.. so full of emotion and fabulous work by John.
  • Can hear this playing on the radio. Wonderful. 👏👏👏👏❤️❤️
  • Such a rich, evocative narrative. Lingers in the mind as all great music does 🎼
  • Previously: Union Card

    Next time: Stations Of The Cross


    Sunday, 25 April 2021

    Lyric Captions

    Praise the Lord and Pass the Microphone

    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 song recordings, 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.

    Oh, and it had to be free and quick and easy. 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.

    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 this effort.

    So it has to be accurate 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.

    SubRip File Format

    Most subtitles distributed on the internet, for example those ripped from movie DVDs, use a file format called - for obvious reasons - SubRip. Since this format is one of the two most popular currently supported for videos uploaded to YouTube or Google Drive (the other being SubViewer, support for which was added later) I settled on it initially for this project.

    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):

    "Union Card" song lyrics - Copyright © 2021 Paul O'Brien

    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. Here's the code, and here's how it works: 

    1. 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 TagLibSharp.
    2. 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.
    3. The captions file appears immediately in the right panel. This text may be copied to the clipboard, or saved with a menu command.
    4. 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.

    Tweaking the Timing

    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!

    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 Union Card has a classic 12-bar blues structure. If you don't know what that is, think of Led Zeppelin's Rock And Roll. And if you don't know what that it, get off of my lawn.

    Union Card 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.

    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.

    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.

    Och That's Too Complicated

    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.

    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.

    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.

    Handy reminder from the Help menu or F1 key

    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.

    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.


    Here's the final result. 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.

    Thursday, 22 April 2021

    Union Card (Paul O'Brien)

    Song written by Paul O'Brien. Guitar & vocals by Paul O'Brien.
    Additional instrument parts written & performed by John Michael Kerr.
    Sound production & captioning by John Michael Kerr.

    Instrumentation

    1. Video - Paul's original iPhone recording
    2. Vocals - Condenser mic through Behringer UMC204HD
    3. Guitar - Fender semi-acoustic
    4. Bass - Tension AAS "Pick Bass"
    5. Drum - Mind Flux "Drums Dirty Kick"
    6. Piano - East Village "Grand Piano"
    7. Tubular Bell - BBC SO Discover
    8. Spiccato - BBC SO Discover
    9. Violins - BBC SO Discover
    10. Horns - BBC SO Discover

    My First 10 Channel Mix

    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.

    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 Bob Ross moment yields a happy accidental effect that's actually worth keeping.

    Update: see later articles. Phone audio has been removed at Paul's request. He is the boss! (no, not that Boss, though not for want of trying...)

    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.

    MIDIs

    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.

    Critical Response

    Initial comments from Paul's Facebook page:

    • Beautiful song. Beautiful melody 🎶
    • 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!!! 👏👏👏👏👏❤️❤️
    • Top class
    • Lorretta Lynn, Bryan Ferry
    Okay, the first one was actually from my sister-in-law, but still...

    Previously: Wash Away This Pain

    Next time: Midnight Sky Of Blue

    Sunday, 18 April 2021

    Wash Away This Pain (Paul O'Brien)

    Song written by Paul O'Brien. Guitar & vocals by Paul O'Brien.
    Additional instrument parts written & performed by John Michael Kerr.
    Sound production & captioning by John Michael Kerr.


    Instrumentation

    1. Video - Paul's original iPhone recording (audio not used)
    2. Vocals - Condenser mic through Behringer UMC204HD
    3. Guitar - Fender semi-acoustic
    4. Piano - Grand
    5. Violins 1 - BBC SO Discover
    6. Violins 2 - BBC SO Discover
    7. Violins Spiccato - BBC SO Discover

    Production Notes

    Revisited to remove phone audio.

    Critical Response

    From Paul's Facebook page:

    • WOW!!! Absolutely STUNNING!! You should SO have a record deal Paul 🙏👏👏👏❤️
    • get it sorted! 🙏

    Next time: Union Card

    Saturday, 3 April 2021

    Man With His Guitar - Paul O'Brien

    My Score Here

    I've dabbled previously with music recording in Audacity, and composing in MuseScore, 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 Ableton Live and the Reason Studios (formerly Propellerhead) Reason Rack.

    A Man, A Guitar...

    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 his own YouTube channel, 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.

    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.

    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 in the service of the song, while remaining resolutely in the background.

    Challenges?

    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 into the zone, 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.

    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.

    Timing

    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 Forever True below for an example of this.

    Normally however, I'll start by warping 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 so 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, automation can be used in the final stages of production, to re-apply any tempo variations felt artistically valid and vital.

    Video

    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.

    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.

    The Songs

    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 Facebook page. 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.

    March 2021

    MP3 - MP4 - If You Could Read My Mind (Gordon Lightfoot cover)
    MP3 - MP4 - You're My Soul Concern
    MP3 - MP4 - Go Softly Into the Night
    MP3 - MP4 - When I'm Gone
    MP3 - MP4 - This Land
    MP3 - MP4 - Forever True
    MP3 - MP4 - It Has To Be Tonight
    MP3 - MP4 - Case Full of Broken Dreams

    April 2021

    MP3 - MP4 - Forever Young (Bob Dylan cover)
    MP3 - MP4 - Too Much To Say
    MP3 - MP4 - Tomorrow Belongs to Yesterday 
    MP3 - MP4 - Hurt (Trent Reznor / Johnny Cash cover)
    MP3 - MP4Keep A Light On In Your Heart
    MP3 - MP4 - This Road I'm On 
    MP3 - MP4 - Wash Away This Pain 
    MP3 - MP4 - Union Card 
    MP3 - MP4 - Midnight Sky Of Blue
    MP3 - MP4 - Stations Of The Cross


    In Future

    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.

    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.

    Update (5 Apr 2021)

    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.

    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.

    Friday, 14 June 2019

    ToyGraf

    Taylored Polynomials

    Previously:
    Differentiating f(x)^g(x)
    The Differentiator
    Graphing Derivatives
    The Complexities Of The Simplifier
    Formula Translation: The Error Function
    Expression Parser
    Note: As always, the latest ToyGraf code can be found at https://github.com/dogbiscuituk/Sid/.

    What better use for a graphing differentiator, than to generate Taylor polynomials for arbitrary functions, in the fewest possible keystrokes?

    • Run ToyGraf;
    • Press F2 (or click the green + button) to add a new function;
    • Type sin x + cos x
    • Click the little yellow pencil in the top row to bring up the Trace Properties dialog;
    • Type Alt+T, or click the big friendly Taylor Polynomial... button;
    • Use the spin edit to select the desired degree of your polynomial, then click OK.
    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 if and where stuff like the toolbar, legend and property table appear - have a hunt for them!

    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).

    Next time: Using ToyGraf to make real analysis demo animations...

    Thursday, 18 April 2019

    Expression Parser

    Modus Operandi

    Previously:
    Differentiating f(x)^g(x)
    The Differentiator
    Graphing Derivatives
    The Complexities Of The Simplifier
    Formula Translation: The Error Function
    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.

    Our little language of arithmetic expressions in a single real variable x has quite the simple grammar, which in mostly-EBNF, looks like this:
    
        Parameter       = "x" ;
    
        Number          = ? \d*\.?\d*([eE][+-]?\d+)? ? ;
    
        NamedConstant   = "e" | "π" | "pi" | "ϕ" | "phi" ;
    
        UnaryOperator   = "+" | "-" | "!" | "~" | "√" ;
    
        BinaryOperator  = "||" | "&&" | "|" | "&" | "=" | "==" | "≠" | "<>" | "!=" | "<" | ">" | "≮ " | "≯ " | "<=" | ">="
                        | "+" | "-" | "*" | "/" | "^" ;
    
        Function        = "Abs" | "Acos" | "Acosh" | "Acot" | "Acoth" | "Acsc" | "Acsch" | "Asec" | "Asech" | "Asin"
                        | "Asinh" | "Atan" | "Atanh" | "Ceiling" | "Cos" | "Cosh" | "Cot" | "Coth" | "Csc" | "Csch"
                        | "Erf" | "Exp" | "Floor" | "Ln" | "Log10" | "Round" | "Sec" | "Sech" | "Sign" | "Sin"
                        | "Sinh" | "Sqrt" | "Step" | "Tan" | "Tanh" ;
    
        Operand         = Parameter
                        | Number
                        | NamedConstant
                        | Operand, "'" (* Form the derivative *)
                        | "(", Expression, ")"
                        | Function, Operand
                        | UnaryOperator, Operand ;
    
        Expression      = Operand, { BinaryOperator, Operand }
                        | Expression, "?", Expression, ":", Expression ;
    

    The mutual recursion visible between the above Expression and Operand nonterminals is the hallmark of a classic infix-notation, parentheses-laden syntax.

    Notes on the Grammar
    • The Parameter 'x' is case insensitive, but will be converted to lower case during the parsing process.
    • The Number 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 double.Parse() method, so if the tokeniser passes through any such malformed number, the error will be detected.
    • A Number 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 Constant node).
    • The NamedConstant 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.
    • The UnaryOperator elements '!' (nothing to do with factorials) and '~' both represent a logical not operation. The number zero is regarded as false, anything else as true. So if x=0, then !x (or equivalently ~x) will return 1; otherwise 0.
    • UnaryOperator '√' performs the same function as 'Sqrt'.
    • The BinaryOperator elements '&' and '&&' both perform a logical And operation, likewise '|' and '||' a logical Or. 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.
    • The available Function terminal elements are just the names of the 35 functions made available in the previous article.
    • Function names are case insensitive, but will be converted to Title Case (initial capital) during the parsing process. Notice that the Function syntax doesn't require parentheses; abs x works just as well as Abs(x).
    • The apostrophe "'" is the only postfix operator in the grammar. It has the effect of differentiating the preceding Operand, so for example, (Sin(x))' evaluates to Cos(x).
    Turning Japanese

    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 PC-1211 and PC-1500. The cool feature is implied products. TL;DR: every memory category was in such short supply back then (see: millennium bug), even BASIC variables were meted out individually, and their names were A..Z. The unexpected benefit of this rationing was that products of variables (variables which individually could only have single-character names like A or B) could now be expressed unambiguously in the form AB, saving a whole byte by omitting the multiplication symbol.

    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
    A/BC would be parsed as (A/B)*C = (A*C)/B.
    But this seems wrong! The expression looks so much more like a quantity A being divided by a quantity BC, in other words,
    A/BC = A/(B*C).
    Now the C has landed below the line; before, it was above. Which is correct?

    Utility is monarch, and the solution turns out to be the introduction of a new kind of implied multiplication with a higher precedence than the usual one. By analogy: the associativity of '^' was once settled to be right, unlike its additive and multiplicative colleagues which all tended left.
    So, while subtraction goes like this: A-B-C = (A-B)-C = A-(B+C),

    by contrast exponentiation does this: A^B^C = A^(B^C)

    rather than the much less useful (A^B)^C,
    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
    A^BC = A^(B*C), rather than (A^B)*C.
    In this way multiplication, if it just remain hidden, can leapfrog other more powerful operators, perhaps even unary operators, perhaps even function calls:
    sin AB = sin (A * B), compared to sin A * B = (sin A) * B.

    White Feather

    So why did I hesitate to implement such an operator in Parser?

    A recurring subject in the C# language designers' blogs and comment responses is cost. 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 too attractive a feature. Sure, (x+2)(x-2) 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.

    What about
    2 sin x cos x
    for example. The familiar sine of a double angle formula, obviously and unambiguously intended to be read as
    2*sin(x)*cos(x).
    But should the precedence of "implicit multiply" exceed even unary and function calls, the interpretation becomes
    2*sin(x*cos(x)),
    which, simply, ouch.

    Then again, why all this detail, if I never intended to implement such an operator? Because I knew I would. 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 clean and readily understandable 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.

    That Precedence enumeration was a proper godsend when smooshing time arrived! Anyway, here comes the parser code:
    
    namespace FormulaBuilder
    {
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Linq.Expressions;
        using System.Text.RegularExpressions;
    
        public class Parser
        {
            private enum Precedence
            {
                RightParenthesis,
                Additive,
                Multiplicative,
                Exponential,
                Functional,
                ImpliedProduct,
                SuperscriptPower
            }
    
            const char
                SquareRoot = '√';
    
            const string
                ImpliedProduct = "i*",
                SuperscriptPower = "s^",
                UnaryMinus = "u-",
                UnaryPlus = "u+";
    
            private string Formula;
            private int Index;
            private Stack<Expression> Operands;
            private Stack<string> Operators;
    
            public Expression Parse(string formula)
            {
                Formula = formula;
                Index = 0;
                Operands = new Stack<Expression>();
                Operators = new Stack<string>(new[] { "(" });
                ParseExpression();
                if (Operators.Any())
                    throw new FormatException(
                        $"Unexpected end of expression, input='{Formula}'");
                return Operands.Peek();
            }
    
            public bool TryParse(string formula, out object result)
            {
                try
                {
                    result = Parse(formula);
                    return true;
                }
                catch (Exception e)
                {
                    result = e.Message;
                    return false;
                }
            }
    
            private static double GetNamedConstantValue(string constant)
            {
                switch (constant.ToLower())
                {
                    case "e":
                        return Math.E;
                    case "π":
                    case "pi":
                        return Math.PI;
                    case "ϕ":
                    case "phi":
                        return (1 + Math.Sqrt(5)) / 2;
                }
                return 0;
            }
    
            private static ExpressionType GetExpressionType(string op)
            {
                switch (op)
                {
                    case "+":
                        return ExpressionType.Add;
                    case "-":
                        return ExpressionType.Subtract;
                    case "*":
                    case "i*":
                        return ExpressionType.Multiply;
                    case "/":
                        return ExpressionType.Divide;
                    case "^":
                    case "s^":
                        return ExpressionType.Power;
                    case UnaryPlus:
                        return ExpressionType.UnaryPlus;
                    case UnaryMinus:
                        return ExpressionType.Negate;
                }
                throw new FormatException();
            }
    
            private static Precedence GetPrecedence(string op)
            {
                switch (op)
                {
                    case ")":
                        return Precedence.RightParenthesis;
                    case "+":
                    case "-":
                        return Precedence.Additive;
                    case "*":
                    case "/":
                        return Precedence.Multiplicative;
                    case "^":
                        return Precedence.Exponential;
                    case ImpliedProduct:
                        return Precedence.ImpliedProduct;
                    case SuperscriptPower:
                        return Precedence.SuperscriptPower;
                }
                return Precedence.Functional;
            }
    
            private Expression MakeBinary(string op, Expression lhs, Expression rhs) =>
                Expression.MakeBinary(GetExpressionType(op), lhs, rhs);
    
            private Expression MakeFunction(string f, Expression operand)
            {
                f = $"{char.ToUpper(f[0])}{f.ToLower().Substring(1)}";
                var result = Expressions.Function(f, operand);
                if (operand is ConstantExpression c)
                    return result.AsDouble((double)c.Value).Constant();
                return result;
            }
    
            private Expression MakeUnary(string op, Expression operand)
            {
                if (operand is ConstantExpression c)
                    switch (op)
                    {
                        case UnaryPlus: return operand;
                        case UnaryMinus: return (-(double)c.Value).Constant();
                    }
                return Expression.MakeUnary(GetExpressionType(op), operand, null);
            }
    
            private string MatchFunction() => MatchRegex(@"^[\p{Lu}\p{Ll}\d]+").ToLower();
    
            private string MatchNumber() => MatchRegex(@"^\d*\.?\d*([eE][+-]?\d+)?");
    
            private string MatchRegex(string pattern)
            {
                var match = Regex.Match(Formula.Substring(Index), pattern);
                return Formula.Substring(Index + match.Index, match.Length);
            }
    
            private string MatchSubscript() => MatchRegex($"^[{StringUtilities.Subscripts}]+");
            private string MatchSuperscript() => MatchRegex($"^[{StringUtilities.Superscripts}]+");
    
            private char NextChar()
            {
                var count = Formula.Length;
                while (Index < count && Formula[Index] == ' ')
                    Index++;
                return Index < count ? Formula[Index] : Index == count ? ')' : '$';
            }
    
            private string NextToken()
            {
                var nextChar = NextChar();
                switch (nextChar)
                {
                    case '+':
                    case '-':
                    case '*':
                    case '/':
                    case '^':
                    case '(':
                    case ')':
                    case SquareRoot:
                        return nextChar.ToString();
                    case char c when char.IsDigit(c):
                    case '.':
                        return MatchNumber();
                    case char c when c.IsSuperscript():
                        return MatchSuperscript();
                    case char c when char.IsLetter(c):
                        return MatchFunction();
                }
                throw new FormatException(
                    $"Unexpected character '{nextChar}', input='{Formula}', index={Index}");
            }
    
            private void ParseExpression()
            {
                do
                {
                    ParseOperand();
                    var op = NextChar();
                    switch (op)
                    {
                        case '+':
                        case '-':
                        case '*':
                        case '/':
                        case '^':
                        case ')':
                            ParseOperator(op.ToString());
                            if (op == ')')
                                return;
                            break;
                        case '$' when Index == Formula.Length + 2: // End of input (normal)
                            return;
                        case '$' when Index < Formula.Length + 2: // End of input (unexpected)
                            throw new FormatException(
                                $"Unexpected end of text, input='{Formula}', index={Index}");
                        case char c when c.IsSuperscript():
                            ParseOperator(SuperscriptPower); //
                            break;
                        default:
                            if (Operands.Peek() is ConstantExpression)
                            {
                                ParseOperator(ImpliedProduct); // Implied multiplication
                                break;
                            }
                            throw new FormatException(
                                $"Unexpected character '{op}', input='{Formula}', index={Index}");
                    }
                }
                while (true);
            }
    
            private void ParseFunction(string function)
            {
                Operators.Push(function);
                ReadPast(function);
            }
    
            private bool ParseNamedConstant(string constant)
            {
                var value = GetNamedConstantValue(constant);
                if (value == 0)
                    return false;
                Operands.Push(value.Constant());
                ReadPast(constant);
                return true;
            }
    
            private void ParseNumber(string number)
            {
                try
                {
                    Operands.Push(double.Parse(number).Constant());
                }
                catch (FormatException)
                {
                    throw new FormatException(
                        $"Invalid number format '{number}', input='{Formula}', index={Index}");
                }
                catch (OverflowException)
                {
                    throw new FormatException(
                        $"Numerical overflow '{number}', input='{Formula}', index={Index}");
                }
                ReadPast(number);
            }
    
            private void ParseOperand()
            {
                var token = NextToken();
                switch (char.ToLower(token[0]))
                {
                    case 'x' when token.Length == 1:
                        ParseParameter(token);
                        break;
                    case char c when char.IsDigit(c):
                    case '.':
                        ParseNumber(token);
                        break;
                    case char c when c.IsSuperscript():
                        ParseSuperscript(token);
                        break;
                    case '(':
                        Operators.Push(token);
                        ReadPast(token);
                        ParseExpression();
                        break;
                    case '+':
                    case '-':
                        ParseUnary(token);
                        ParseOperand();
                        break;
                    case char c when char.IsLetter(c):
                        if (!ParseNamedConstant(token))
                        {
                            ParseFunction(token);
                            ParseOperand();
                        }
                        break;
                    case SquareRoot:
                        ParseSquareRoot();
                        ParseOperand();
                        break;
                    default:
                        throw new FormatException(
                            $"Missing operand, input='{Formula}', index={Index}");
                }
            }
    
            private void ParseOperator(string op)
            {
                do
                {
                    var ours = GetPrecedence(op);
                    var pending = Operators.Peek();
                    if (pending == "(")
                        break;
                    var theirs = GetPrecedence(pending);
                    // Operator '^' is right associative: a^b^c = a^(b^c).
                    if (theirs > ours || theirs == ours && op != "^")
                    {
                        Operators.Pop();
                        var operand = Operands.Pop();
                        if (theirs == Precedence.Functional)
                            switch (pending)
                            {
                                case UnaryPlus:
                                    break;
                                case UnaryMinus:
                                    operand = MakeUnary(pending, operand);
                                    break;
                                default:
                                    try
                                    {
                                        operand = MakeFunction(pending, operand);
                                    }
                                    catch (ArgumentNullException)
                                    {
                                        throw new FormatException(
                                            $"Unrecognised function '{pending}', input='{Formula}'");
                                    };
                                    break;
                            }
                        else
                        {
                            if (pending == ImpliedProduct)
                                pending = "*";
                            operand = MakeBinary(pending, Operands.Pop(), operand);
                        }
                        Operands.Push(operand);
                    }
                    else
                        break;
                }
                while (true);
                if (op == ")")
                    Operators.Pop();
                else
                    Operators.Push(op);
                if (op != ImpliedProduct && op != SuperscriptPower)
                    ReadPast(op);
            }
    
            private void ParseParameter(string token)
            {
                Operands.Push(Expressions.x);
                ReadPast(token);
            }
    
            private void ParseSquareRoot()
            {
                Operators.Push("Sqrt");
                ReadPast(SquareRoot.ToString());
            }
    
            private void ParseSuperscript(string superscript)
            {
                Operands.Push(new Parser().Parse(superscript.SuperscriptToNormal()));
                ReadPast(superscript);
            }
    
            private void ParseUnary(string unary)
            {
                Operators.Push($"u{unary}");
                ReadPast(unary);
            }
    
            private void ReadPast(string token) => Index += token.Length;
        }
    }
    

    Going Wild!

    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 "x", or a function, or "(" introducing a nested expression, or even another number (separated from the first by at least one space).

    And what of the over-eager precedence problem? This new implicit multiplication operator needs its own, new precedence level, Implied, above Unary, so stuff like sin 2x works properly. But that's also higher than Exponential precedence, so 2x^3 becomes (2*x)^3, whereas in the arena of polynomials we'd rather see 2*(x^3). This can be fixed, luckily, quickly, and quirkily: introduce Unicode superscripts, and give them their own implicit exponentiation operator! This has the highest precedence of all, and causes 2x³ to get dressed as the wanted 2*(x^3).

    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 'q'), 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 look at LaTeX - 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 x⁴-4x³+6x²-4x+1, or eᶜᵒˢ⁽ˣ⁾, into our mathematical stockpot.

    You don't have superscripts on your keyboard? Sorry, I'm just a code monkey. That's definitely a hardware problem.
    
    namespace FormulaBuilder
    {
        using System;
        using System.Text;
    
        public static class StringUtilities
        {
            public const string
                Subscripts = "₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎ₐₑₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓᵦᵧᵨᵩᵪ",
                Transcripts = "0123456789+-=()aehijklmnoprstuvxβγρψχbcdfgwyzABDEGHIJKLMNOPRTUVWαδεθιφ",
                Superscripts = "⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾ᵃᵉʰⁱʲᵏˡᵐⁿᵒᵖʳˢᵗᵘᵛˣᵝᵞρᵠᵡᵇᶜᵈᶠᵍʷʸᶻᴬᴮᴰᴱᴳᴴᴵᴶᴷᴸᴹᴺᴼᴾᴿᵀᵁⱽᵂᵅᵟᵋᶿᶥᶲ";
    
            public static bool IsSubscript(this char c) => Subscripts.IndexOf(c) >= 0;
            public static bool IsSuperscript(this char c) => Superscripts.IndexOf(c) >= 0;
    
            public static string NormalToSubscript(this string number) => Transcribe(number, Transcripts, Subscripts);
            public static string NormalToSuperscript(this string number) => Transcribe(number, Transcripts, Superscripts);
            public static string SubscriptToNormal(this string number) => Transcribe(number, Subscripts, Transcripts);
            public static string SubscriptToSuperscript(this string number) => Transcribe(number, Subscripts, Superscripts);
            public static string SuperscriptToNormal(this string number) => Transcribe(number, Superscripts, Transcripts);
            public static string SuperscriptToSubscript(this string number) => Transcribe(number, Superscripts, Subscripts);
    
            public static string Transcribe(this string number, string source, string target)
            {
                var stringBuilder = new StringBuilder(number);
                for (var index = 0; index < Math.Min(source.Length, target.Length); index++)
                    stringBuilder.Replace(source[index], target[index]);
                return stringBuilder.ToString();
            }
        }
    }
    

    But Does It Parse The Test?

    Obviously we need some tests for all this, and in fact the following lines have just appeared in Expressions.Tests.cs. 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 AsString() extension method, where we hope to see a similarly structured string emerge, but sporting at the most binary operations, and all the other minor effects and transformations expected.
    
            public static void TestParse(string input, string output) =>
                Check(output, new Parser().Parse(input).AsString());
    
            public static void TestParseFail(string input, string error)
            {
                try
                {
                    new Parser().Parse(input);
                    Check("Exception thrown", "no Exception thrown");
                }
                catch (Exception ex)
                {
                    Check(error, ex.Message);
                }
            }
    
            public static void TestParser()
            {
                TestParserFailure();
                TestParserSuccess();
            }
    
            public static void TestParserFailure()
            {
                TestParseFail("x~2", "Unexpected character '~', input='x~2', index=1");
                TestParseFail("x+123,456", "Unexpected character ',', input='x+123,456', index=5");
                TestParseFail("x+1$2", "Unexpected end of text, input='x+1$2', index=3");
                TestParseFail("x+1e999", "Numerical overflow '1e999', input='x+1e999', index=2");
                TestParseFail("x+.", "Invalid number format '.', input='x+.', index=2");
                TestParseFail("x+.E+1", "Invalid number format '.E+1', input='x+.E+1', index=2");
                TestParseFail("x+", "Missing operand, input='x+', index=2");
                TestParseFail("(x+(2*(x+(3)))", "Unexpected end of text, input='(x+(2*(x+(3)))', index=15");
            }
    
            public static void TestParserSuccess()
            {
                TestParse("0", "0");
                TestParse("e", "2.71828182845905");
                TestParse("π", "3.14159265358979");
                TestParse("pi", "3.14159265358979");
                TestParse("ϕ", "1.61803398874989");
                TestParse("phi", "1.61803398874989");
                TestParse("X+1", "(x+1)");
                TestParse("((x+1))", "(x+1)");
                TestParse("x+x*x^x/x-x", "((x+((x*(x^x))/x))-x)");
                TestParse("3*x+x/5", "((3*x)+(x/5))");
                TestParse("x-2-x", "((x-2)-x)");                             // Subtraction is left associative
                TestParse("x^2^x", "(x^(2^x))");                             // Exponentiation is right associative
                TestParse("(x-3)*(5-x)/10", "(((x-3)*(5-x))/10)");
                TestParse("sin x * cos x", "(Sin(x)*Cos(x))");
                TestParse("Ln(sin x - tanh(x)) - 1", "(Ln((Sin(x)-Tanh(x)))-1)");
                TestParse("Abs Cos Sin Tan 1.5", "0.540839774154307");
                TestParse("Abs Cos Sin Tan (x/2)", "Abs(Cos(Sin(Tan((x/2)))))");
                TestParse("2*(sin x + cos x ^ 3 - tan(x^3))/3", "((2*((Sin(x)+(Cos(x)^3))-Tan((x^3))))/3)");
                TestParse("2*(x+3*(x-4^x)-5)/6", "((2*((x+(3*(x-(4^x))))-5))/6)");
                TestParse("1/5x", "(1/(5*x))");                              // Implied products have higher precedence
                TestParse("1/2sqrt(x)", "(1/(2*Sqrt(x)))");
                TestParse("2 sin 3x", "(2*Sin((3*x)))");
                TestParse("2 sin 3x * 5 cos 7x", "((2*Sin((3*x)))*(5*Cos((7*x))))");
                TestParse("2 3", "(2*3)");
                TestParse("2(x+3)", "(2*(x+3))");
                TestParse("2x^3)", "((2*x)^3)");
                TestParse("2(x^3)", "(2*(x^3))");
                TestParse("x⁴-4x³+6x²-4x+1", "(((((x^4)-(4*(x^3)))+(6*(x^2)))-(4*x))+1)");
                TestParse("1/2√(1-x²)", "(1/(2*Sqrt((1-(x^2)))))");
                TestParse("eˣ", "(2.71828182845905^x)");
                TestParse("eᶜᵒˢ⁽ˣ⁾", "(2.71828182845905^Cos(x))");
            }