1
\$\begingroup\$

I'd like to write a script that receives text input from the clipboard that will be from a song lyric sheet with chords. The goal is for the function to return the text to the clipboard after transposing the chord names up or down a half-step. If the solution is with shell commands, I might still wrap it in an AppleScript. I'm having a hard time conceptualizing a good, reliable approach to the transpose function itself.

Transposing down, any "C" chord would become a "B" chord. An "F#" would become and "F". An "Ab" would become a "G", etc. It really is fine if the function only transposes down or up, cause one can cycle through, but it'd be nice if it could go either way. Here's a sample of a text, with about as many variations I can think to include:

C F G
La la is a line of lyrics with simple chords
C B7 Ab F#
La la some chords have flats and sharps
C Abm Fm
La la other lines have minor chords
F#sus Fmj7 B5 Gsus2 Gm7
La la but chords can can't kinda funky
Cdim Daug F+ G2
Doo wop with many short suffixed annotations
C/F Am/G B7/G
and any can have a slash followed by a bass note.

Notes about a chord sheet formatting:

Chord names are given on lines above the line of lyric. * Verses etc. are separated by an extra line break.

  • All chord names are in capitals, and no other letters on a chord line are.

  • Only the chord letter and the flat b or # need to change when transposed. All other "sus", "m7", "+", "dim", etc. remain unchanged.

  • Chord half-step progression is: A A#/Bb B C C#/Db D D#/Eb E F F#/Gb G G#/Ab

  • Technically, a song should only use flats b or sharps uniformly.

  • Any bass notes after a slash / also need to be transposed.

One of the problems of course has to do with sequential changes and not changing a chord name that has already been changed.

One could isolate the lines with chords by looking only at lines with three spaces in a row. And only the capital letters (and b & #) need to be looked at on those rows.

I found this where someone was working on chord transposing, (PHP chord transposer), but it was using inline chord notation, and also I don't speak PHP.

Here is code that I now have working. It has a lot of repeats, so is not very efficient. There a better way than this?

property chordList : {"A#", "Bb", "C#", "Db", "D#", "Eb", "F#", "Gb", "G#", "Ab", "A", "B", "C", "D", "E", "F", "G"}
property codeList : {"©12", "©13", "©16", "©17", "©19", "©20", "©23", "©24", "©26", "©27", "©11", "©14", "©15", "©18", "©21", "©22", "©25"}
property loweredList : {"A", "A", "C", "C", "D", "D", "F", "F", "G", "G", "Ab", "Bb", "B", "Db", "Eb", "E", "Gb"}
property raisedList : {"B", "B", "D", "D", "E", "E", "G", "G", "A", "A", "A#", "C", "C#", "D#", "F", "F#", "G#"}
set theString to the clipboard
set transposeUp to false -- true transposes up, false shifts down
set newString to transposeChords(theString, transposeUp)
set the clipboard to newString
on transposeChords(musicString, shiftUp)
 if shiftUp then
 set changeList to raisedList
 else
 set changeList to loweredList
 end if
 set otid to AppleScript's text item delimiters
 considering case
 set transposedString to ""
 repeat with p from 1 to (count paragraphs in musicString)
 set thisLine to paragraph p of musicString
 if (thisLine contains " ") and (thisLine does not contain "t") then
 -- change all chord names to a ©11, ©12, etc. code
 repeat with c from 1 to (count chordList)
 set thisLine to replaceString((item c of chordList), (item c of codeList), thisLine)
 end repeat
 -- change all codes to the shifted counterpart
 repeat with c from 1 to (count codeList)
 set thisLine to replaceString((item c of codeList), (item c of changeList), thisLine)
 end repeat
 set newLine to thisLine
 else
 set newLine to thisLine
 end if
 set transposedString to transposedString & newLine & return
 end repeat
 return transposedString
 end considering
 set AppleScript's text item delimiters to otid
end transposeChords
on replaceString(toFind, replaceWith, aString)
 set AppleScript's text item delimiters to toFind
 set aString to text items of aString
 set AppleScript's text item delimiters to replaceWith
 set aString to aString as string
end replaceString
dfhwze
14.1k3 gold badges40 silver badges101 bronze badges
asked Aug 19, 2019 at 2:42
\$\endgroup\$
8
  • 2
    \$\begingroup\$ If you just had the chords, this could be doable with a specialized lookup table (dealing with the sus and aug and slash is only a little harder). The formatting is another matter. Ideally, though, you want a real program which understands the notation, not something hacked together that does a blind translation of your requirements. My gut says this might be too large of a scope of a program for SO (ie answers would be far too long), but Im willing to be proven wrong. At the very least it’s an interesting problem \$\endgroup\$ Commented Aug 19, 2019 at 2:47
  • 2
    \$\begingroup\$ Hey @jweaks, I disagree somewhat with D Ben Knoble. I think this is very doable to a good degree of accuracy and speed. However, just to be extra sure what we're dealing with, can you paste a link to a formatted copy of the lyric/chord sheet on a site that preserves tabs and spaces as they are (SO has converted any tabs that might have been there into spaces, and I doubt the horizontal alignment of the chords is that pronounced at the source). Once you've done that, I'll see whether I'm still as confident. If you want to include more sample song sheets, that's fine and good too. \$\endgroup\$ Commented Aug 19, 2019 at 7:14
  • \$\begingroup\$ PS. I notice that, while you use a the letter b and the sign # in place of genuine flat/sharp symbols. Is that intentional, or something that some piece fo software limits you to, or a product of not previously knowing how to access those symbols for use in text: sharp: ♯ flat: ♭ There's a bunch of others too: ♩ ♪ ♫ ♬ 𝄫 ♮ 𝄞 𝄢 〽︎ 𝄀 𝄁 𝄂 𝄃 𝄆 𝄇 𝄈 𝄐 𝄑 𝄡 ⏔ ⏕ ⏖ ℟ ℣ and a few more (they may not print well in SO's choice of font, but generally look good in most monospace situations. \$\endgroup\$ Commented Aug 19, 2019 at 7:23
  • \$\begingroup\$ Notation does not use tabs. It uses spaces to align the chord names, so no problem there. And yes, they use a lower case b and a number sign hash # for sharp. Plain text designed for monospace font. \$\endgroup\$ Commented Aug 19, 2019 at 15:07
  • \$\begingroup\$ @jweaks - is there any particular way of identifying chord lines? All I can think are (1) to count through every line (skipping empty lines) and work on odd-numbered lines, or (b) look for lines with strings of multiple spaces. But neither approach feels particularly robust. There's no label or tag or common feature we can pick out that says (effectively) "this is a chord line"? \$\endgroup\$ Commented Aug 19, 2019 at 17:22

1 Answer 1

2
\$\begingroup\$

After working for a couple days, I came up with code that works. It loops through paragraphs that match three spaces, and then finds/replaces each chord name, one a time, first changing them to a code, and then to the shifted chord. It's not super robust, in that it uses arrays and repeat loops through them to alter the chords. It works fine thought since song sheets are not huge. Still, a regex change if possible would be more robust.

property chordList : {"A#", "Bb", "C#", "Db", "D#", "Eb", "F#", "Gb", "G#", "Ab", "A", "B", "C", "D", "E", "F", "G"}
property codeList : {"©12", "©13", "©16", "©17", "©19", "©20", "©23", "©24", "©26", "©27", "©11", "©14", "©15", "©18", "©21", "©22", "©25"}
property loweredList : {"A", "A", "C", "C", "D", "D", "F", "F", "G", "G", "Ab", "Bb", "B", "Db", "Eb", "E", "Gb"}
property raisedList : {"B", "B", "D", "D", "E", "E", "G", "G", "A", "A", "A#", "C", "C#", "D#", "F", "F#", "G#"}
set theString to the clipboard
set transposeUp to false -- true transposes up, false shifts down
set newString to transposeChords(theString, transposeUp)
set the clipboard to newString
on transposeChords(musicString, shiftUp)
 if shiftUp then
 set changeList to raisedList
 else
 set changeList to loweredList
 end if
 set otid to AppleScript's text item delimiters
 considering case
 set transposedString to ""
 repeat with p from 1 to (count paragraphs in musicString)
 set thisLine to paragraph p of musicString
 if (thisLine contains " ") and (thisLine does not contain "t") then
 -- change all chord names to a ©11, ©12, etc. code
 repeat with c from 1 to (count chordList)
 set thisLine to replaceString((item c of chordList), (item c of codeList), thisLine)
 end repeat
 -- change all codes to the shifted counterpart
 repeat with c from 1 to (count codeList)
 set thisLine to replaceString((item c of codeList), (item c of changeList), thisLine)
 end repeat
 set newLine to thisLine
 else
 set newLine to thisLine
 end if
 set transposedString to transposedString & newLine & return
 end repeat
 return transposedString
 end considering
 set AppleScript's text item delimiters to otid
end transposeChords
on replaceString(toFind, replaceWith, aString)
 set AppleScript's text item delimiters to toFind
 set aString to text items of aString
 set AppleScript's text item delimiters to replaceWith
 set aString to aString as string
end replaceString
answered Aug 20, 2019 at 15:19
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.