Chord Recognition with Cantabile Performer (for version 4169 & later)

Hi All,

I am posting this for anyone using the latest Cantabile version 4169 Performer & later. What it covers is how we now have a way to have a blob of comparative string expressions that identify the chord being played. Of course this is not for everyone so if it isn’t there is no need to use it. But for some novice or intermediate musicians it could help to share chords over the network through the UI and on displays that have Cantabile running on them. The development of it stemmed from a request by another user here named Sergio. He is from Rome and plays at home and in a community church group and likes using Cantabile to perform and learn songs. He was using a VST called SChord for this task and wanted to embed it in Cantabile to get rid of the separate GUI. By the way SChord was used to cross compare when figuring this out. So I asked Brad if there were any functions that could detect held notes or groups of notes and he kindly produced the first note_held functions:

is_note_held(noteNumber, [channel]) - returns 1 or 0 if note number is held
all_notes_held(notesArray, [channel]) - returns 1 or 0 if all the note numbers in the array are held
any_notes_held(notesArray, [channel]) - returns 1 or 0 if any of the note numbers in the array are held

With these 3 functions it was possible to start detecting groups of held notes and creating expressions that could produce a string label for the chord held like the middle C root C major chord.

$(all_notes_held([48,52,55],1) == 1 ? “CMajor”:“”)

using the any_notes_held function you could put arrays of all the same named notes on the keyboard and that allowed covering the whole keyboard for a given chord like C major.

$(any_notes_held([0,12,24,36,48,60,72,84,96,108,120] ,1) ==1 && any_notes_held([4,16,28,40,52,64,76,88,100,112,124] ,1) ==1 && any_notes_held([7,19,31,43,55,67,79,91,103,115,127] ,1) ==1 && any_notes_held([11,23,35,47,59,71,83,95,107,119] ,1) == 0 ? “Cmajor”:“”)

This was great but there were problems with overlapping chords and chords that shared the same notes. Some of this was addressed using the any_notes_held function shown above. Sergio built up the entire range for common chords using this numeric style and it was quite extensive. As the expression list grew the amount of text characters it took did too so I asked Brad if there was a way to address all the C notes of the MIDI range with one function and he came out with the function:

is_pitch_class_held(pitch_class, [channel])

This helped a lot to make the syntax smaller and introduced being able to enter the array elements in literal note letters without comma separators. It was based on testing for all instances of C through B on the whole range of the keyboard. This way you dealt with only one octave of possible notes when making expressions. So the C chord example became

$(is_pitch_class_held(“CEG”,1) == 1 && is_pitch_class_held(“C#”,1) == 0 && is_pitch_class_held(“D”,1) == 0 && is_pitch_class_held(“D#”,1) == 0 && is_pitch_class_held(“F”,1) == 0 && is_pitch_class_held(“F#”,1) == 0 && is_pitch_class_held(“G#”,1) == 0 && is_pitch_class_held(“A”,1) == 0 && is_pitch_class_held(“A#”,1) == 0 && is_pitch_class_held(“B”,1) == 0 ? “CMajor” :“”)

This was an advance but in order to be able to have other chords separated out it was necessary to filter by a test for zero all the other possible notes in the octave. This had to be done discreetly with the existing pitch class function so the expression got long again. These are all the other entries shown above following the first expression that recognizes the chord. So a request was made for a function that could sense for any pitch class held and Brad gave us :

any_pitch_class_held(pitch_class, [channel])

This changed the same expression for the C chord to

$(is_pitch_class_held(“CEG”,1) == 1 && any_pitch_class_held(“C#DD#FF#G#AA#B”,1) == 0 ? “CM” :“”)

This was the solution that was most compact and so Sergio (in Italian) & me (in English) started working on all the other chord possibilities. It went well and we built up from 15 or so common chord types but began to run into trouble with chords where there were the same pitch classes used by chords needing different chord names. This applied to sus2 & sus4, augmented, diminished & diminished7 , minor7 & Major6 and a few others. So after some thought & back and forth with Sergio I asked if Brad could create a function that could detect the lowest held pitch and he came out with one that would do it and also one to work with the previous notes_held functions. They were:


This was the final piece in the puzzle because it allowed us to separate the shared note type chords. C augmented for instance shares the same pitch classes as E augmented and G# augmented. So in this example for C Augmented you see how determining the lowest held note of a group of notes allows determining which chord value to display.

$(is_pitch_class_held(“CEG#”,1) ==1 && any_pitch_class_held(“C#DD#FF#GAA#B”,1) == 0 && lowest_held_pitch_class(1)==“C” ? “Caug”:“”)

A by product of all this adventure is that these functions Brad made can be used in the expression engine of the new bindings system to determine the triggering of the binding. So things like triggering state changes on particular chords or other targeted actions are possible using simple or complex expressions using these note held functions.

I have compiled my results and put then in a zip file below. One file is the separated chord groups with labels on the groups for if you want to copy the all or part of an expression for other uses in or build up your own smaller footprint version of the expression blob.

The second file is the finished full chord content in a compressed expression blob I built up. This blob can be pasted as is into the label field of a Controller bar button or into a note field in the Show Notes tab. There are copies of both files that are set to MIDI channel 2. This is for if you want to do a split keyboard
and have a chord seen on the upper zone and a separate one on the lower zone.


The chord types recognized in this compilation are:

Single Notes, Major, Minor, 5th, 7th, 9th, 13th, Major6th, 6th/sus4, 7th/sus4, add9, Major7th, Major9th, Major11th, Major13th, Minor2nd, Minor6th, Minor7th, Minor/Major7, Minor 9th, Minor 11th, Minor 13th, Sus2, Sus4, Diminished, Diminished7th, Augmented, Augmented7th.

Cantabile Chord Recognition Expressions - (16.5 KB)

Sergio will be making the Italian version available when it’s ready. My thanks to him for starting the wheels rolling on and for working on this project plus his ideas on separating the shared chords. And many Thanks to Brad for all his patience and help tackling this and to the amazing Cantabile Performer.




Just…WOW! :star_struck:

Great job, Dave and Sergio!

Hi all.
As mentioned by Dave, I am posting the Italian version of the chord recognizer.
I must give great credit to Dave for letting me participate, with so much patience, in this adventure. Certainly, alone, I would never have succeeded. And certainly thanks must be given to Brad, who provided targeted expressions, which made it possible to create this chord and note recognizer in the availability of Cantabile (particularly in the Controller Bar buttons and for other uses as well), which is certainly useful to me, but to anyone who is interested, for so many reasons, whether more or less didactic, in using it.
As Dave said, the script can be used totally or even partially: if so many things can be left out, one can copy what is of interest, even partially.
In the Italian version (pass me this vanity of mine: musical chords were born in Italy…), I used Dave’s scheme, that is, there is a compressed part and an uncompressed part. In the latter you can look closely at the chords by scale as set.
Also here, the settings for Midi channels 1 and 2 are present. I have added the single notes for Midi channel 3 in case you are using the pedalboard of an organ vst that has them.
I hope you like it.
Ita Chords - ch midi (22.0 KB)