Message Boards Message Boards

6
|
10325 Views
|
4 Replies
|
6 Total Likes
View groups...
Share
Share this post:

Musical chords in Mathematica

Mathematica function SoundNote in conjunction with Sound is capable of playing a wide range of notes in different musical instruments. But unfortunately, you can't specify chords in SoundNote.

In this post, I'll share a simple code of mine to show the possibilities of implementing this.

Before jumping into chords, first, we'll create functions that operate on notes. I find it useful to delimit the operational behavior of the code before implementing it. Bellow is shown what the function Note will accomplish.

NoteQ[note:_Integer | _String] (* test if "note" is valid *)
Note[note:_String | {__String}, "Number"] (* return the "number"-code of the note. C4 = 0, D5=14, etc. *)
Note[numb:_Integer  | {__Integer}, "Name"] (* return the name of the note with number code "numb" *)

And now for the real code:

Note$RegExp := "^([A-G])(#|b)?(-1|[0-9])?$" (* Regular Expression to match notes *)
NoteQ[note_String] := StringMatchQ[note, RegularExpression@Note$RegExp]
NoteQ[note_Integer] := -60 <= note <= 67 (* This range is taken from SoundNote *)

Note[note_String?NoteQ, "Number"] := Block[{r, sf, n},
   (* Divide the note into its "components" given by the RegExp *)
  {r, sf, n} = First@StringCases[note, RegularExpression@Note$RegExp :> {"$1", "$2", "$3"}];
  <|"C"->0,"D"->2,"E"->4,"F"->5,"G"->7,"A"->9,"B"->11|>[r] + (* map the base not A-G *)
     Which[sf=="#",1, sf=="b", -1, True, 0] + (* Map sharp and flats *)
     If[n!="", (ToExpression@n - 4)*12, 0]] (* Map octave *)

(* The opposite of what was previously defined *)
Note[number_Integer?NoteQ, "Name"] := <|0->"C",1->"C#",2->"D",3->"D#",4->"E",5->"F",
   6->"F#",7->"G",8->"G#",9->"A",10->"A#",11->"B"|>[Mod[number, 12]] <> ToString[4 + Floor[number/12]]

(* To create chords we'll need to transpose the root note *)
Note[number:_Integer?NoteQ|{__Integer?NoteQ}, transp_Integer, "Transposition"] /; NoteQ@Max[transp+transp] \[And] NoteQ@Min[transp+transp] := number + transp
Note[note:_String?NoteQ|{__String?NoteQ}, transp_Integer, "Transposition"] := Note[Note[Note[note, "Number"], transp, "Transposition"], "Name"]

(* "Vectorize" the inputs *)
Note[notes:{__String?NoteQ}, "Number"]  := Note[#, "Number"] & /@ notes
Note[numbers:{__Integer?NoteQ}, "Name"] := Note[#, "Name"] & /@ numbers
Note[note:_Integer?NoteQ | _String?NoteQ, transp:{__Integer}, "Transposition"] := Note[note, #, "Transposition"] & /@ transp

Now we can define a Chord function.

Chord[chord_String, "Root"] (* Give the root note for the chord. Caug13 -> C *)
Chord[chord_String, "Type"] (* return the quality/type of chord, Major, Minor, etc. *)
Chord[chord_String, "Number"] (* Return a list with the number-form of the notes in the chord *)
Chord[chord_String, "Notes"] (* The actual notes of that chord *)

Now for the real code:

Chord[chord_String /; StringLength@chord >= 2, "Root"] := If[MemberQ[{"#","b"}, StringTake[chord, {2}]], StringTake[chord, 2], StringTake[chord, 1]]
Chord[chord_String, "Notes"] := Note[Chord[chord, "Root"], Chord[Chord[chord, "Type"], "Number"], "Transposition"]
Chord[notes : {__String}, "Notes"] := Chord[#, "Notes"] & /@ notes

Chord[chord_String /; StringLength@chord >= 2, "Type"] := 
 Block[{rest = StringReplace[chord, Chord[chord, "Root"] -> ""]},
    Which[
        StringMatchQ[rest, RegularExpression@"^(M|maj)?$"], "Major",
        StringMatchQ[rest, RegularExpression@"^(m|min)$"],  "Minor",     
        StringMatchQ[rest, RegularExpression@"^(aug|[+])$"], "Augmented",
        StringMatchQ[rest, RegularExpression@"^(dim|o)$"],   "Diminished",

        StringMatchQ[rest, RegularExpression@"^(M|maj)6$"], "Major Sixth",
        StringMatchQ[rest, RegularExpression@"^(m|min)6$"], "Minor Sixth",

        StringMatchQ[rest, RegularExpression@"^(7|dom7)$"], "Dominant Seventh",
        StringMatchQ[rest, RegularExpression@"^(M|maj)7$"], "Major Seventh",   
        StringMatchQ[rest, RegularExpression@"^(m|min)7$"], "Minor Seventh",   
        StringMatchQ[rest, RegularExpression@"^(aug|[+])7$"], "Augmented Seventh",
        StringMatchQ[rest, RegularExpression@"^(dim|o)7$"], "Diminished Seventh",
        StringMatchQ[rest, RegularExpression@"^(0)7$"], "Half Diminished Seventh",
        StringMatchQ[rest, RegularExpression@"^(m/M7)$"], "Minor/Major Seventh",     
        StringMatchQ[rest, RegularExpression@"^(M|maj)7[+]5$"], "Augmented Major Seventh",
        StringMatchQ[rest, RegularExpression@"^(maj7[-]5)$"], "Dominant Seventh Flat Five Chord",  

        StringMatchQ[rest, RegularExpression@"^(9|dom9)$"], "Dominant Ninth",
        StringMatchQ[rest, RegularExpression@"^(M|maj)9$"], "Major Ninth",
        StringMatchQ[rest, RegularExpression@"^(m|min)9$"], "Minor Dominant Ninth",
        StringMatchQ[rest, RegularExpression@"^[+](M|maj)9$"], "Augmented Major Ninth",
        StringMatchQ[rest, RegularExpression@"^(aug|[+])9$"], "Augmented Dominant Ninth",
        StringMatchQ[rest, RegularExpression@"^(m/M9)$"], "Minor/Major Ninth",
        StringMatchQ[rest, RegularExpression@"^(0)9$"], "Half Diminished Ninth",
        StringMatchQ[rest, RegularExpression@"^(0b)9$"], "Half Diminished Minor Ninth",
        StringMatchQ[rest, RegularExpression@"^(o|dim)9$"], "Diminished Ninth",
        StringMatchQ[rest, RegularExpression@"^(ob)9$"], "Diminished Minor Ninth",

        StringMatchQ[rest, RegularExpression@"^(11|dom11)$"],  "Dominant Eleventh",
        StringMatchQ[rest, RegularExpression@"^(M|maj)11$"], "Major Eleventh",
        StringMatchQ[rest, RegularExpression@"^(m/M11)$"],  "Minor/Major Eleventh",
        StringMatchQ[rest, RegularExpression@"^(m|min)11$"], "Minor Eleventh",
        StringMatchQ[rest, RegularExpression@"^[+](M|maj)11$"], "Augmented Major Eleventh",
        StringMatchQ[rest, RegularExpression@"^(aug|[+])11$"], "Augmented Eleventh",
        StringMatchQ[rest, RegularExpression@"^(0)11$"], "Half Diminished Eleventh",
        StringMatchQ[rest, RegularExpression@"^(dim|o)11$"], "Diminished Eleventh",

        StringMatchQ[rest, RegularExpression@"^(M|maj)13$"], "Major Thirteenth",
        StringMatchQ[rest, RegularExpression@"^(13|dom13)$"], "Dominant Thirteenth",
        StringMatchQ[rest, RegularExpression@"^(m/M13)$"], "Minor/Major Thirteenth",
        StringMatchQ[rest, RegularExpression@"^(m|min)13$"],  "Minor Dominant Thirteenth",
        StringMatchQ[rest, RegularExpression@"^[+](M|maj)13$"],  "Augmented Major Thirteenth",
        StringMatchQ[rest, RegularExpression@"^(aug|[+])13$"], "Augmented Dominant Thirteenth",
        StringMatchQ[rest, RegularExpression@"^(0)13$"], "Half Diminished Thirteenth",

        True, $Failed
    ]
  ]

(* Chords *)
Chord["Major", "Number"] := {0, 4, 7}
Chord["Minor", "Number"] := {0, 3, 7}
Chord["Augmented", "Number"]  := {0, 4, 8}
Chord["Diminished", "Number"] := {0, 3, 6}

Chord["Major Sixth", "Number"] := {0, 4, 7, 9}
Chord["Minor Sixth", "Number"] := {0, 3, 7, 9}

Chord["Dominant Seventh", "Number"]        := {0, 4, 7, 10}
Chord["Major Seventh", "Number"]           := {0, 4, 7, 11}
Chord["Minor Seventh", "Number"]           := {0, 3, 7, 10}
Chord["Augmented Seventh", "Number"]       := {0, 4, 8, 10}
Chord["Diminished Seventh", "Number"]      := {0, 3, 6, 9}
Chord["Half Diminished Seventh", "Number"] := {0, 3, 6, 10}
Chord["Minor/Major Seventh", "Number"]     := {0, 3, 7, 11}
Chord["Augmented Major Seventh", "Number"] := {0, 4, 8, 11}
Chord["Dominant Seventh Flat Five", "Number"] := {0, 4, 6, 10}

Chord["Major Ninth", "Number"]          := {0, 4, 7, 11, 14}
Chord["Dominant Ninth", "Number"]       := {0, 4, 7, 10, 14}
Chord["Minor/Major Ninth", "Number"]    := {0, 3, 7, 11, 14}
Chord["Minor Dominant Ninth", "Number"] := {0, 3, 7, 10, 14}
Chord["Augmented Major Ninth", "Number"] := {0, 4, 8, 11, 14}
Chord["Augmented Dominant Ninth", "Number"]    := {0, 4, 8, 10, 14}
Chord["Half Diminished Ninth", "Number"]    := {0, 3, 6, 10, 14}
Chord["Half Diminished Minor Ninth", "Number"] := {0, 3, 6, 10, 13}
Chord["Diminished Ninth", "Number"]    := {0, 3, 6, 10, 14}
Chord["Diminished Minor Ninth", "Number"] := {0, 3, 6, 9, 13}

Chord["Dominant Eleventh", "Number"] := {0, 4, 7, 10, 14, 17}
Chord["Major Eleventh", "Number"] := {0, 4, 7, 11, 14, 17}
Chord["Minor/Major Eleventh", "Number"] := {0, 3, 7, 11, 14, 17}
Chord["Minor Eleventh", "Number"] := {0, 3, 7, 10, 14, 17}
Chord["Augmented Major Eleventh", "Number"] := {0, 4, 8, 11, 14, 17}
Chord["Augmented Eleventh", "Number"] := {0, 4, 8, 10, 14, 17}
Chord["Half Diminished Eleventh", "Number"] :={0, 3, 6, 10, 13, 17}
Chord["Diminished Eleventh", "Number"] := {0, 3, 6, 2, 13, 16}

Chord["Major Thirteenth", "Number"] := {0, 4, 7, 11, 2, 17, 21}
Chord["Dominant Thirteenth", "Number"] := {0, 4, 7, 10, 14, 17, 21}
Chord["Minor/Major Thirteenth", "Number"] := {0, 3, 7, 11, 14, 17, 21}
Chord["Minor Dominant Thirteenth", "Number"] := {0, 3, 7, 10, 14, 17, 21}
Chord["Augmented Major Thirteenth", "Number"] := {0, 4, 8, 11, 14, 17, 21}
Chord["Augmented Dominant Thirteenth", "Number"] := {0, 4, 8, 10, 14, 17, 21}
Chord["Half Diminished Thirteenth", "Number"] := {0, 3, 6, 10, 14, 17, 21}

And real-life "application":

SoundNote[#, 0.5] & /@ Chord[StringSplit["C Dm7 Fmaj7 Dm F C Gm Dm Gm F C", " "],"Notes"] // Sound

You can listen it:

Note: Full-disclaimer, I'm not a musician, the only instrument I play is the flute and I'm self-taught, hence I know next to nothing about chords. All my knowledge came from wikipedia: https://en.wikipedia.org/wiki/Chord_names_and_symbols_(popular_music). There are possibily many wrong things or bugs, and the code could be more efficient and less cumbersome.. The main goal of this post is to share the idea.

POSTED BY: Thales Fernandes
4 Replies

Hi Thales. Your post got me thinking, and for that I thank you. I appreciate your 'pythonic' comment - sometimes approaching a problem with Mathematica requires a great deal of head-scratching! All the best, John

POSTED BY: John Loach

enter image description here -- you have earned Featured Contributor Badge enter image description here Your exceptional post has been selected for our editorial column Staff Picks http://wolfr.am/StaffPicks and Your Profile is now distinguished by a Featured Contributor Badge and is displayed on the Featured Contributor Board. Thank you!

POSTED BY: EDITORIAL BOARD

Hi Thales Wonderful work. Funny - I was just working on chord construction in Bitwig, and needed more math functions. So I found your Mathematica project. I've used your approach and added the capability for inversions to it. So - if you want a root chord, enter it normally. If you want first inversion, enter it like Cmaj7-1. Second inversion Cmaj7-2, and so on. Would you like to see the revised project? I'd be happy to send it to you and you could update your post. I've never posted to Mathematica. I'll work on adding the drop-2 chord inversions as well. Further on, I'd like to give the program a chord progression and have it calculate the smoothest path through the chords. It could be done by adding up the semitone movements between chords for each inversion and choosing the minimum voice movements. Also, the program could choose a different chord type to optimize the change as well. Then add a bit of randomness. Kind of fun! The chord progression may be exported to Bitwig, and ported through a program called Divisimate to automate orchestrations. Thank you for posting your work. It's really good. Cheers, John (jwloach@loacheng.on.ca)

POSTED BY: John Loach

Dear John, I encourage you to make your own separated post about your new ideas. If I could suggest, my original post lacked images to display, better examples, and such.

I frankly, don't remember much about this problem and over the years I switched to a more pythonic way (open-source) than Mathematica and I forgot a lot about it. Best, Thales

POSTED BY: Thales Fernandes
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract