Vinyl siding

I've been writing scripts to automate music file tagging since at least 2014: in Bash, then Scheme, and now in Haskell. There is one function that I've been damned to forget and reimplement during each of these iterations: turning a vinyl side number (A, B, ...) into a disc number (1, 2, ...). Let me write it down here.

At first glance it doesn't seem that annoying; just convert the character to an int, subtract 64, and divide that by two. But things get funky when you have more than 13 discs or tapes to deal with. It happens. After side Z, you loop around to having an AA side, followed by a AB side, and so on. The logic to mitigate this also isn't too bad, but at that point, we might as well try to account for any number of discs, and the ABZFUCK sides that represent them.

Here's some Haskell I wrote to deal with that. It's a basic recursive function that whittles a vinyl side string down to its proper number, character by character. It assumes the input has already been filtered of any digits. So "A1" should be "A", since we're only concerned with the disc number here.

letterDisc ∷ Char → Int
letterDisc = (subtract 64) . fromEnum . toUpper

vinylDisc ∷ String → Int
vinylDisc []       = 0
vinylDisc (α:[]  ) = ceiling ((fromIntegral $ letterDisc α) / 2)
vinylDisc (α:ω:[]) = (13 * (letterDisc α)) + vinylDisc [ω]
vinylDisc (α:ω   ) = (side α) * (degree (α:ω)) + vinylDisc ω
  where side   = (* 13) . letterDisc
        degree = (26 ^) . (subtract 2) . length

letterDisc converts an individual char to an integer. It should be straightforward enough. I broke it out into its own function to make the main one less busy.

vinylDisc does the heavy lifting. It's worth exploring case by case.

vinylDisc []       = 0

If the string is empty, you can't have a disc number—duh. This is not a base case in the recursion, since the other cases also explicitly pattern match for the end of the string.

vinylDisc (α:[]  ) = ceiling ((fromIntegral $ letterDisc α) / 2)

This pattern should cover 99.99% of vinyl and tape releases—all those with less than 14 pieces of media. Turn the letter into a number, divide it by two, round up.

vinylDisc (α:ω:[]) = (13 * (letterDisc α)) + vinylDisc [ω]

This logic kicks in when a side has exactly two letters. The first of those two should be multiplied by 13, with the previous case applied to the second letter.

vinylDisc (α:ω   ) = (side α) * (degree (α:ω)) + vinylDisc ω
  where side   = (* 13) . letterDisc
        degree = (26 ^) . (subtract 2) . length

This is the piece of logic that gave me this biggest headache, and I've yet to find a single actual album that requires it, but this case covers an arbitrary number of letters. Any nth letter must be multiplied by 13 as well as its degree, where a degree is 26 raised to the power of n-2. This product is summed up against a recursive call of vinylDisc to the string's tail. Maybe the length could be properly memozied, but honestly, how long should these strings be?

Side "ABZFUCK" is found on the 172,339,823rd disc by the way.