Regex for standard guitar lyric/chord bracketing
Asked Answered
M

5

7

I am trying to add square brackets around chords in standard text documents formatted for guitar/lyrics to make them more compatible with the OnSong app. I have the rules but don't understand how to match for all the possible combinations. The rules are:

  • Chords will begin with a single capital A-G
  • if the capital A-G is followed by a space, line break, #, b, m, sus, aug, dim, maj, min, or / I'd like to read until the next whitespace or line break (because of standard guitar formatting, a chord like F#min/E is possible, and rather than bother splitting it all out, I just want to keep reading until space)
  • The regex should NOT match if the capital A-G is followed by another letter not in the list above (for example the name "Ed" should not match)
  • bonus points if you can figure out how to make "A small world" NOT match due to the word FOLLOWING the "A" not being a valid chord.
  • super bonus points if the replacement can remove a space before (when not starting a line) and after the new brackets (to keep alignment in place) - it was pointed out to me that this will fail on close chords... this is completely acceptable.

A couple notes: This is for a helper script... perfection is not needed. I do this by hand right now, so the occasional miss is okay. I'm not trying to parse out the details of the chords, just to wrap them in []. While the standard layout is 1 row of chords, 1 row of lyrics, this can't be counted on, so I'm aware some scenarios will fail occasionally.

Test source (chords are random for testing purposes, in case any musicians were going to chime in on the terrible music):

Db    Dsus4/F#           A            Cbmin/C
A man can't be asked for that much to do
D/F#        G         A           D#/E
And I can't sweep you off of your feet

Should turn into:

[Db]  [Dsus4/F#]         [A]          [Cbmin/C]
A man can't be asked for that much to do
[D/F#]      [G]       [A]         [D#/E]
And I can't sweep you off of your feet

My first attempt got me close with:

([A-G]((?!\s).)*)

but that picked up words that began with those letters as well. I have gone around in circles now and only gotten as far as:

\b([CDEFGAB](#|##|b|bb|sus|maj|min|aug)?\b)

When I've tried to use [^\s+] I get mixed results that pick up more of what I want but also ditch things I need. I think I'm just over my head. Any help would be GREATLY appreciated and any explanation of how it works would be even better. While I'd like a solution, I'd also really love to explain why it works...

Murdocca answered 19/3, 2015 at 12:38 Comment(4)
So A# Eb // Oh man! will become [A#][Eb] // Oh man!? Due to the closeness of these chords, we loose the alignment (adding brackets).Korikorie
Good point on chords right up against each other... sometimes the alignment will just not work -- I've accepted that fate :)Murdocca
With what language or tool will you use the regex? And what is this animal?Goodness
No particular language in mind... was planning to just make a little shell script or tiny java app to process the text files. As an example, this is a really common format for guitarists/vocalists to get for a song. Lyrics with basic chords above the lyrics. I also use an app for live performance that can read text files, but if the chords are bracketed, it will convert them into actual chord diagrams, which is extremely useful. The problem is, with thousands of song files, adding brackets becomes cumbersome. (example link) tabs.ultimate-guitar.com/p/pearl_jam/alive_crd.htmMurdocca
S
7

This passes using your sample input and achieves all your "super bonus points" requirements:

String output = input.replaceAll("(?m)(^| )([A-G](##?|bb?)?((sus|maj|min|aug|dim)\\d?)?(/[A-G](##?|bb?)?)?)( (?!\\w)|$)", "[$2]");

This code turns this (as a single String with embedded line fees):

Db    Dsus4/F#           A            Cbmin/C
A man can't be asked for that much to do
D/F#        G         A           D#/E
And I can't sweep you off of your feet

Into this:

[Db]  [Dsus4/F#]         [A]          [Cbmin/C]
A man can't be asked for that much to do
[D/F#]      [G]       [A]         [D#/E]
And I can't sweep you off of your feet
Spalding answered 19/3, 2015 at 14:4 Comment(4)
very cool -- is it NOT retaining alignment at the start currently?Murdocca
No - as you can see from the output, the entire chord line is bumped one char to the right by the insertion of the [ at the beginning. To avoid this, one would have to consume an extra space (ie 2 spaces) following a chord at the start of the line. Although to be honest the alignment looks fine with the [ now marking the start of chord - maybe just use it as is. BTW you may find Apache commons-io FileUtils.readFileToString() and writeStringToFile() handySpalding
I'm trying to implement this in R.. but I cant get it working. My regex101 example throws an error / An unescaped delimiter must be escaped with a backslash (\) regex101.com/r/3NbIwi/1Peptonize
@Peptonize the forward slash character has no special meaning in regex, but many languages use the forward slash to delimit regexes, and I suspect R is one such language. Try putting a backslash before each forward slash in my regex. If that doesn’t work, try a double backslash before each forward slash.Spalding
D
4

I have improved a little bit the answer from previous answer to help in my case. Now It was ignoring some "chords likely" when it is in the beginning of the verse (like A, E). This what I came out:

(\(*[CDEFGAB](?:b|bb)*(?:#|##|sus|maj|min|aug|m|M|°|[0-9])*[\(]?[\d\/]*[\)]?(?:[CDEFGAB](?:b|bb)*(?:#|##|sus|maj|min|aug|m|M|°|[0-9])*[\d\/]*)*\)*)(?=[\s|$])(?! [a-z])
Daimon answered 6/3, 2017 at 11:55 Comment(0)
Z
3

I have some working regex for the case you provided, but not sure how it will work for others. The problem is that a line can start with A, or it can be in the song line. I tried to work around it using the negative lookahead checking if the chord is followed by a space and an alphanumeric. If there is a space and an alphanumeric, we do not match this chord. Since the chords can repeat after /, I am doubling the pattern.

\b([CDEFGAB](?:b|bb)*(?:#|##|sus|maj|min|aug)*[\d\/]*(?:[CDEFGAB](?:b|bb)*(?:#|##|sus|maj|min|aug)*[\d\/]*)*)(?=\s|$)(?! \w)

Have a look at the demo.

Zwick answered 19/3, 2015 at 12:54 Comment(3)
This is truly excellent. I'm adding in the 'm' option I forgot as well. (?<=\b)([CDEFGAB](?:b|bb|m)*(?:#|##|sus|maj|min|aug)*[\d\/]*(?:[CDEFGAB](?:b|bb|m)*(?:#|##|sus|maj|min|aug)*[\d\/]*)*)(?=\s|$)(?! \w)Murdocca
Mind that there might be a problem if there is just 1 space between the chords. Perhaps, we can make it safer with (?! [^ CDEFGAB]) lookahead.Leia
fyi (?<=\b) is identical is just \b, since neither consumes input. Also this would match "Aaugmin" or A####### - use ? instead of *Spalding
S
3

Check this out:

/([A-G](#|b)?)(\(?(M|maj|major|m|min|minor|dim|sus|dom|aug)?(\+|-|add)?\d*\)?)(\/([A-G](#|b)?))?/g

I've took it from chord-transposer:

var XRegExp = require("xregexp");

// Chromatic scale starting from C using flats only.
var FLAT_SCALE = ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "Cb"];

// Chromatic scale starting from C using sharps only.
var SHARP_SCALE = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];

// Regex for recognizing chords
var ROOT_PATTERN = '(?<root>[A-G](#|b)?)';

var SUFFIX_PATTERN = '(?<suffix>\\(?(M|maj|major|m|min|minor|dim|sus|dom|aug)?(\\+|-|add)?\\d*\\)?)';

var BASS_PATTERN = '(\\/(?<bass>[A-G](#|b)?))?';

var MINOR_PATTERN = '(m|min|minor)+';

var CHORD_REGEX = XRegExp("^" + ROOT_PATTERN + SUFFIX_PATTERN + BASS_PATTERN + "$");

and then

console.log(CHORD_REGEX); // will output regexp mentioned at the beginning of the answer

It works great for me.

Serpasil answered 21/2, 2019 at 20:28 Comment(0)
G
0

the code executes the agreement well in the sample I checked and Agreements simplesles 7th agreements with sharp or flat like C # 7

string strRegex = @"^[A-G]([5679bm#]([57])?|1[13]|6\/9|7[-#+b][59]|7?sus[24]|add[249]|aug|dim7?|m\/maj7|m1[13]|m[679]|m7?b5|maj1[13]|maj[79])?([\/][A-G]([5679bm#])?([57])?)?";

Regex myRegex = new Regex(strRegex, RegexOptions.None);
string strTargetString = @"A";
string strReplace = @"[$0]";

return myRegex.Replace(strTargetString, strReplace);
Galipot answered 19/3, 2015 at 14:47 Comment(2)
fyi this is a java questionSpalding
@Eric oddly, another user added Java to the mix, as my question was really a general Regex issue. I realize that the substitution at the end requires a language choice but any could really do it. That said, I think this overcomplicates the real need... since I'm not trying to prove invalid chords (dim35 or the like) there's a lot of extra checking going on in here. This would likely be really useful if someone was going to parse out the details though.Murdocca

© 2022 - 2024 — McMap. All rights reserved.