1

I would like to write an algorythm that builds regular 3key piano chords progression in a given octave shifting to the next one if not all possible notes were covered. For example:

Cmaj key will give all the notes/chords in it's progression, since the starting note is a start of the octave it will terminate at the next C. But if I start with the B note of the same octave, it will end with the B in the next one as well.

I would like to build it both for major and minor scales with ability to extend it for 7 and 9 type chords in the future.

This is not a homework, I would like to use c# and then re-write it in f# to learn the language a little bit more.

Edit:

My question is this then: What data structure should I use for the octave (C to C): LinkedList List or may be this will require completely different structure?

Edit2: So if we index notes like this, which I am not sure if it's a correct approach: 0 1 2 3 4 5 6 7 8 9 10 11 12

Input: Note = C (0), Scale = Maj Output: 0 4 7, 2 5 9, 4 7 12, etc.

dexter
  • 7,063
  • 9
  • 54
  • 71
  • How is your data being represented? What does an Octave look like? What does a Chord look like? How are those associated with Keys on the Piano, and what do THOSE look like? – jdmichal Dec 15 '10 at 20:53
  • 2
    ...and your question is? – robert Dec 15 '10 at 20:54
  • Is this only for visual purposes (ie information / manipulation on the screen), or do these notes need to also be associated with their frequencies for playback purposes? Also are you assuming twelve-tone equal temperament, or could that change? – user470379 Dec 15 '10 at 20:56
  • @user - this is only for visual purposes, everything is a whole note and 4/4 if it matters, but I am only looking for the output of notes per chord in a given progression. – dexter Dec 15 '10 at 21:00
  • 1
    Your formulation isn't clear enough, octave is an interval, chord is a combination of notes, progression is a sequence of chords. I can't combine them to make sense of your question. Can you give an example of input and desired output? – Yakov Galka Dec 15 '10 at 21:09
  • @ybungalobill: specified the wanted output. – dexter Dec 15 '10 at 21:16

8 Answers8

3

The easiest way to model this, perhaps, is to use the notion of midi note mapping, as the keys are enumerated and a first inversion triad from a given root will be

root, root + 4, root + 7

next inversion would be

root + 4, root + 7, root + 12

next inversion would be

root + 7, root + 12, root + 16

where root is the midi note number for your root.

In fact, given a chord in first inversion, it's trivial to generate all the other inversions by removing the first entry, putting it on the end and adding 12. So your chords would really start to look like this:

public int GetChord(ChordName chord)
{
    switch (chord) {
    case ChordName.Major: return new int[] { 0, 4, 7 };
    case ChordName.Minor: return new int[] { 0, 3, 7 };
    case ChordName.Augmented: return new int[] { 0, 4, 8 };
    case ChordName.Dominant7: return new int[] { 0, 4, 7, 10 };
    case ChordName.Maj7: return new int[] { 0, 4, 7, 11 };
    // etc
 }

Then whatever is returned from here (and probably using List would be better), you can write an IEnumerable that returns each of the inversions. Then you add the value of the root to the output and ta-da! you have your chord, which is now tremendously easy to output as, well, midi.

public int[] InvertChord(int[] chord)
{
    int[] inversion = new int[chord.Length];
    for (int i = 1; i < chord.Length; i++) {
        inversion[i-1] = chord[i];
    }
    inversion[inversion.Length-1] = chord[0] + 12;
    return inversion;
}

public int[][] ChordAndAllInversions(int[] chord)
{
    int[][] inversions = new int[chord.Length][];
    inversions[0] = chord;
    for (int i=1; i < chord.Length; i++) {
        inversions[i] = InvertChord(inversions[i - 1]);
    }
    return inversions;
}
plinth
  • 48,267
  • 11
  • 78
  • 120
  • This doesn't solve the problem of *assigning names* to the notes. – Alexandre C. Dec 15 '10 at 21:05
  • This wont work very well going forward to add support of 7 and 9 chords...I dont want to continue to extend this factory method forever for each type of chord. – dexter Dec 15 '10 at 21:06
  • 2
    Assigning names to notes is trivial. octave = (note-21)/12; noteNameIndex = (note-12) % 12; string name = NameFromNote(notNameIndex) + octave; – plinth Dec 15 '10 at 21:09
  • Max - if you noticed, I put in Dominant7. If you don't like the factory method, then define an lookup table in XML or whatever that associates chord name -> list of note offsets, read in the xml, search for the name, extract the offsets. This is truly a data driven problem. – plinth Dec 15 '10 at 21:11
  • 1
    where the problem gets interesting and to which F# offers more in terms of language support is, given a set of intervals, to provide the name of the chord. – plinth Dec 15 '10 at 21:22
3

When I did this in Java several years ago I made the following classes:

  1. Note -- represents each of the 12 distinct notes in any given octave ([C, C#/Db, D, D#/Eb, E, F, F#/Gb, G, G#/Ab, A, A#/Bb, B])
  2. Octave -- stores an integer to distinguish between other octaves
  3. Pitch -- stores a Note and Octave
  4. ScalePattern -- encodes the number of half steps from one pitch to the next (e.g. Major Scale is [0,2,4,5,7,9,11,12])
  5. Scale -- stores an initial Note and a ScalePattern

This design makes it easy to define and use ChordPatterns as well.

Sage Mitchell
  • 1,563
  • 8
  • 31
  • From above. I like this idea as well. Add to it an array of note names for sharp keys: [|'c';'c#';'d';'d#'...|] for flat keys: [|'c';'db';'d';'eb'...|] and a last array for which keys are sharp: [|'c'; 'g'; 'd'...|]. Based onthe pitch, lookup the sharp array and if it is there use the sharp keys note names, else the flat eys. Then you can apply your scale/chord pattern over the correct array of note names, wrapping around aftr the octave.. – akaphenom Dec 16 '10 at 22:59
2

Incidentally, I love Music Theory, Math, and F#, so I couldn't resist exploring this problem.

At first, I attempted a purely functional solution, using only modules, F# functions, and basic data-structures, but this quickly spun out of control (since I sought some rather ambitious goals including supporting arbitrary scales, not just "major" and "minor"). What follows is my first "serious" effort at "programming in the medium" in F# using object orientation. As I said before, I thought I could avoid this, but it turned out that using object orientation in F# actually works out quite nice, and doesn't undermine the beauty and succinctness too much (especially when we disregard consumability by other .NET languages).

Utils.fs

For starters, I have a couple utility functions I will be using:

module MusicTheory.Utils
open System

let rotate (arr:_[]) start =
    [|start..arr.Length + start - 1|] |> Array.map (fun i -> arr.[i% arr.Length])

//http://stackoverflow.com/questions/833180/handy-f-snippets/851449#851449
let memoize f = 
    let cache = Collections.Generic.Dictionary<_,_>(HashIdentity.Structural)
    fun x ->
        match cache.TryGetValue(x) with
        | true, res -> res
        | _ -> let res = f x
               cache.[x] <- res
               res

Note.fs

The Note type encapsulates a music note including it's name, sign (NoteSign), and it's position relative to other notes. But it does little other than that. The Aux module holds some underlying data-structures used for constructing and validating Notes (note that I am not too found of this module, I would have rather used private static fields on the Note type, but F# doesn't support private static fields. And since I am using a namespace instead of module to hold my types (so that I can use top of file declarations), I can't use free floating let bindings). I think the pattern match for extracting the NoteSign is especially neat.

namespace MusicTheory
open Utils
open System

///want to use public static field on Note, but don't exist
module Aux = 
    let indexedNoteNames = 
        let arr = [|
            ["B#"; "C"] //flip this order?
            ["C#";"Db"]
            ["D"]
            ["D#";"Eb"]
            ["E";"Fb"]
            ["E#";"F" ] //flip this order?
            ["F#";"Gb"]
            ["G"]
            ["G#";"Ab"]
            ["A"]
            ["A#";"Bb"]
            ["B";"Cb"] 
        |]
        Array.AsReadOnly(arr)

    let noteNames = indexedNoteNames |> Seq.concat |> Seq.toList
    let indexedSignlessNoteNames = [|'A';'B';'C';'D';'E';'F';'G'|]

open Aux

type NoteSign =
| Flat
| Sharp
| Natural

//Represents a note name and it's relative position (index)
type Note(name:string) =
    let name = 
        match noteNames |> List.exists ((=) name) with
        | true -> name
        | false -> failwith "invalid note name: %s" name

    let sign = 
        match name |> Seq.toArray with
        | [|_|]     -> NoteSign.Natural
        | [|_;'#'|] -> NoteSign.Sharp
        | [|_;'b'|] -> NoteSign.Flat
        | _         -> failwith "invalid note name sign" //not possible

    let index = 
        indexedNoteNames 
        |> Seq.findIndex (fun names -> names |> List.exists ((=) name))

    with 
    member self.Name = name
    member self.SignlessName = name.[0]
    member self.Sign = sign
    member self.Index = index
    override self.ToString() = name
    override self.GetHashCode() = name.GetHashCode()
    override self.Equals(other:obj) =
        match other with
        | :? Note as otherNote -> otherNote.Name = self.Name
        | _ -> false

    ///memoized instances of Note
    static member get = memoize (fun name -> Note(name))

Pitch.fs

Next is Pitch which encapsulates a specific frequency in the chromatic scale relative to some starting point, 0 (C). It exposes calculations for which octave it lays in as well as the set of Notes which may describe it (noting that outside of the context of a scale starting at a specific Note, are equally valid).

namespace MusicTheory
open Utils
open Aux
open System

///A note is a value 0-11 corresponding to positions in the chromatic scale.
///A pitch is any value relative to a starting point of the chromatic scale
type Pitch (pitchIndex:int) =
    let pitchIndex = pitchIndex
    let noteIndex = Math.Abs(pitchIndex % 12)
    let octave = 
        if pitchIndex >= 0 then (pitchIndex / 12) + 1
        else (pitchIndex / 12) - 1

    let notes = indexedNoteNames.[noteIndex] |> List.map Note.get

    with 
    member self.Notes = notes
    member self.PitchIndex = pitchIndex
    member self.NoteIndex = noteIndex
    ///e.g. pitchIndex = 5 -> 1, pitchIndex = -5 -> -1, pitchIndex = 13 -> 2
    member self.Octave = octave
    override self.ToString() = sprintf "Notes = %A, PitchIndex = %i, NoteIndex = %i,  Octave = %i" notes noteIndex pitchIndex octave
    override self.GetHashCode() = pitchIndex
    override self.Equals(other:obj) =
        match other with
        | :? Pitch as otherPitch -> otherPitch.PitchIndex = self.PitchIndex
        | _ -> false

    ///memoized instances of Pitch
    static member get = memoize (fun index -> Pitch(index))
    ///get the first octave pitch for the given note
    static member getByNote (note:Note) = note.Index |> Pitch.get
    ///get the first octave pitch for the given note name
    static member getByNoteName name = name |> Note.get |> Pitch.getByNote

ScaleIntervals.fs

In anticipation of our upcoming Scale type, we have a module ScaleIntervals filled with sub-modules filled with lists of intervals between pitches which describe scales (note that this differs from the index based representation others have been using). For your interest, note that Mode.ionian and Mode.aeolian correspond to the "major" and "minor" scales respectively. In practice, you'd probably want to use some external means for loading scale intervals at runtime.

//could encapsulate as a type, instead of checking in Scale constructors
///define modes by chromatic interval sequence
module MusicTheory.ScaleIntervals
open Utils

module Mode =
    let ionian = [|2;2;1;2;2;2;1|] //i.e. "Major"
    let dorian = Utils.rotate ionian 1
    let phrygian = Utils.rotate ionian 2 
    let lydian = Utils.rotate ionian 3
    let mixolydian = Utils.rotate ionian 4
    let aeolian = Utils.rotate ionian 5 //i.e. "Minor
    let locrian = Utils.rotate ionian 6

module EqualTone =
    let half = [|1;1;1;1;1;1;1;1;1;1;1;1|]
    let whole = [|2;2;2;2;2;2|]

module Pentatonic =
    let major = [|2;2;3;2;3|]
    let minor = Utils.rotate major 4 //not sure

Scale.fs

Here lays the heart of our solution. Itself, a Scale is quite simple, merely wrapping a sequence of scale intervals. But when viewed in the context of a Pitch or a Note, yields all of our results. I will point out that in isolation of a Pitch or a Note, Scale does have the interesting feature that it yields an infinite sequence of RelativeIndices derived from the scale intervals. Using this, we can yield an infinite sequence of Pitches built from this Scale starting at a given Pitch (GetPitches). But now for the most interesting method: GetNotePitchTuples, which yields on infinite sequence of Note, Pitch tuples, where the Notes are heuristically selected (see comments on that method for more info). Scale also provides several overloads for getting at Note sequences more easily, including a ToString(string) overload which accepts a string Note name and returns a string listing the first octave of Note names.

namespace MusicTheory
open Utils
open System

///A Scale is a set of intervals within an octave together with a root pitch
type Scale(intervals:seq<int>) =
    let intervals = 
        if intervals |> Seq.sum <> 12 then
            failwith "intervals invalid, do not sum to 12"
        else 
            intervals

    let relativeIndices = 
        let infiniteIntervals = Seq.initInfinite (fun _ -> intervals) |> Seq.concat
        infiniteIntervals |> Seq.scan (fun pos cur -> pos+cur) 0
    with
    member self.Intervals = intervals
    member self.RelativeIndices = relativeIndices
    override self.ToString() = sprintf "%A" intervals
    override self.GetHashCode() = intervals.GetHashCode()
    override self.Equals(other:obj) =
        match other with
        | :? Scale as otherScale -> otherScale.Intervals = self.Intervals
        | _ -> false

    ///Infinite sequence of pitches for this scale starting at rootPitch
    member self.GetPitches(rootPitch:Pitch) =
        relativeIndices
        |> Seq.map (fun i -> Pitch.get (rootPitch.PitchIndex + i))

    ///Infinite sequence of Note, Pitch tuples for this scale starting at rootPitch.
    ///Notes are selected heuristically: works perfectly for Modes, but needs some work
    ///for Pentatonic and EqualTone (perhaps introduce some kind of Sign bias or explicit classification).
    member self.GetNotePitchTuples(rootNote:Note, rootPitch:Pitch) =
        let selectNextNote (prevNote:Note) (curPitch:Pitch) =
            //make sure octave note same as root note
            if curPitch.Notes |> List.exists ((=) rootNote) then 
                rootNote
            else 
                //take the note with the least distance (signless name wise) from the root note
                //but not if the distance is 0.  assumes curPitch.Notes ordered asc in this way.
                //also assumes that curPitch.Notes of length 1 or 2.
                match curPitch.Notes with
                | [single] -> single
                | [first;second] when first.SignlessName = prevNote.SignlessName -> second
                | [first;_] -> first

        self.GetPitches(rootPitch)
        |> Seq.scan 
            (fun prev curPitch ->
                match prev with
                | None -> Some(rootNote, rootPitch) //first
                | Some(prevNote,_) -> Some(selectNextNote prevNote curPitch, curPitch)) //subsequent
            None
        |> Seq.choose id

    member self.GetNotePitchTuples(rootNote:Note) =
        self.GetNotePitchTuples(rootNote, Pitch.getByNote rootNote)

    member self.GetNotePitchTuples(rootNoteName:string) =
        self.GetNotePitchTuples(Note.get rootNoteName)

    ///return a string representation of the notes of this scale in an octave for the given note
    member self.ToString(note:Note) = 
        let notes = 
            (Scale(intervals).GetNotePitchTuples(note)) 
            |> Seq.take (Seq.length intervals + 1)
            |> Seq.toList 
            |> List.map (fst)
        sprintf "%A"  notes

    ///return a string representation of the notes of this scale in an octave for the given noteName
    member self.ToString(noteName:string) = 
        self.ToString(Note.get noteName)

Here is a demonstration:

open MusicTheory
open Aux
open ScaleIntervals

let testScaleNoteHeuristics intervals =
    let printNotes (noteName:string) =
        printfn "%A" (Scale(intervals).ToString(noteName))

    noteNames
    |> Seq.iter printNotes

//> testScaleNoteHeuristics Mode.ionian;;
//"[B#; D; E; F; G; A; B; B#]"
//"[C; D; E; F; G; A; B; C]"
//"[C#; D#; E#; F#; G#; A#; B#; C#]"
//"[Db; Eb; F; Gb; Ab; Bb; C; Db]"
//"[D; E; F#; G; A; B; C#; D]"
//"[D#; E#; G; Ab; Bb; C; D; D#]"
//"[Eb; F; G; Ab; Bb; C; D; Eb]"
//"[E; F#; G#; A; B; C#; D#; E]"
//"[Fb; Gb; Ab; A; B; C#; D#; Fb]"
//"[E#; G; A; Bb; C; D; E; E#]"
//"[F; G; A; Bb; C; D; E; F]"
//"[F#; G#; A#; B; C#; D#; E#; F#]"
//"[Gb; Ab; Bb; Cb; Db; Eb; F; Gb]"
//"[G; A; B; C; D; E; F#; G]"
//"[G#; A#; B#; C#; D#; E#; G; G#]"
//"[Ab; Bb; C; Db; Eb; F; G; Ab]"
//"[A; B; C#; D; E; F#; G#; A]"
//"[A#; B#; D; Eb; F; G; A; A#]"
//"[Bb; C; D; Eb; F; G; A; Bb]"
//"[B; C#; D#; E; F#; G#; A#; B]"
//"[Cb; Db; Eb; Fb; Gb; Ab; Bb; Cb]"
//val it : unit = ()

Chords

The next step is supporting the concept of a chord, both in isolation from a Scale (a set of Pitches) and in context of a Scale with a given root Note. I haven't given too much thought into whether any encapsulation is warranted here, but it would be very straight-forward to enhance Scale to, say, return a progression of chords (e.g. Note list for each Note in scale) given a starting Note and a chord pattern (like a triad, for example).

Stephen Swensen
  • 22,107
  • 9
  • 81
  • 136
1

The exact output you're trying to generate isn't clear. However, let's remember what a scale looks like:

 T T S T T T S
C D E F G A B C

(where the T's indicate two semitones between the notes, and the S's indicate one).

Knowing that, it's straightforward to generate every note in a scale.

Once you have a scale, you can pull out 1-3-5, then 2-4-6 etc. to get all the chords.

EDIT: There are a fixed number of notes in a scale, and you want to be able to grab notes out by index. Just use an array.

Anon.
  • 58,739
  • 8
  • 81
  • 86
  • Yes, this would be fine for Cmaj and A minor. But it gets tricky with any other scale as we have to add half tones. It gets even more tricky with minor progressions. – dexter Dec 15 '10 at 21:01
  • @Max: No, it's still easy. You generate your scale based on **Tones** and **Semitones** (The T's and S's I posted) and turn that into notes, rather than trying to generate the scales out of whole cloth. – Anon. Dec 15 '10 at 21:03
0

I would just use an integer, where 0 is the lowest key on the keyboard. Each increment represents a semitone higher. Then break down chords into intervals, e.g.:

type  1  3  5  7
----------------
maj = 0  4  3  4
min = 0  3  4  3
dom = 0  4  3  3
dim = 0  3  3  3
...

Then you can do simple addition to get all the notes of the chord. So, starting on note 43, a dominant chord would be notes:

43  47  50  53
D'Arcy Rittich
  • 167,292
  • 40
  • 290
  • 283
  • But how would you convert back to actual notes? (ie: Amaj = A C# E vs. 37 41 44) – user470379 Dec 15 '10 at 21:03
  • 1
    -1: You can't get a correct result this way, since e.g. C# and Db or Cb and B are represented by the same numbers but you need to output one or the other depending on the chord. – Yakov Galka Dec 15 '10 at 21:07
  • @ybungalobill: Noting that the notes are 1-3-5, deciding that it's "D-F#-A" instead of "D-Gb-A" isn't tricky. – Anon. Dec 15 '10 at 21:13
  • @Anan: That's not that simple in general, consider e.g. Abmin or if you're moving to more complex chords. However (@RedFilter) the OP apparently doesn't want the output be converted back. – Yakov Galka Dec 15 '10 at 21:22
  • @ybungalobill: why are you downvoting for not fulfilling an un-needed requirement? – D'Arcy Rittich Dec 16 '10 at 15:15
  • @RedFilter: I'm sorry, I downvoted before the OP clarified the question (per my request). Apparently I'm not the only one who understood it this way (see first comment). – Yakov Galka Dec 16 '10 at 15:21
0

Depends on what you want to do with the octave info, I guess. However, if you're looking to pull specific notes from the set of notes in the octave, and you're not looking to add notes after setting up your octave, I think it would be better to use a class that would give you good random access, like an Array.

Tola Odejayi
  • 3,019
  • 9
  • 31
  • 46
0

You are asking for chords in pitch class set notation. This means we don't care about the function or name of the notes, only the pitch classes in relation to the tonic of each chord.

Algorithm

static IEnumerable<IEnumerable<int>> GetChords(int[] scale, int extension)
{
    foreach (var degree in Enumerable.Range(0, scale.Length))
    {
        yield return GetChord(scale, extension, degree);
    }
}

static IEnumerable<int> GetChord(int[] scale, int extension, int degree)
{
    var d = degree;
    var m = extension - 1;
    var k = 2;

    do
    {
        yield return scale[d];
        d += k;
        d %= scale.Length;
        m -= k;

    } while (m >= 0);
}

Verification

static void Main(string[] args)
{
    var major = new[] { 0, 2, 4, 5, 7, 8, 11 };
    var text = new StringBuilder();

    text.AppendLine(Print(GetChords(major, 5)));   // triads
    text.AppendLine(Print(GetChords(major, 7)));   // 7ths
    text.AppendLine(Print(GetChords(major, 9)));   // 9ths
    text.AppendLine(Print(GetChords(major, 11)));  // 11ths
    text.AppendLine(Print(GetChords(major, 13)));  // 13ths

    var rendered = text.ToString();
    Console.WriteLine(rendered);

    Console.ReadKey();
}

static string Print(IEnumerable<IEnumerable<int>> chords)
{
    return string.Join(",", chords.Select(chord => string.Join(" ", chord.Select(x => x))));
}

yielding

- 0 4 7,2 5 8,4 7 11,5 8 0,7 11 2,8 0 4,11 2 5

- 0 4 7 11,2 5 8 0,4 7 11 2,5 8 0 4,7 11 2 5,8 0 4 7,11 2 5 8

- 0 4 7 11 2,2 5 8 0 4,4 7 11 2 5,5 8 0 4 7,7 11 2 5 8,8 0 4 7 11,11 2 5 8 0

- 0 4 7 11 2 5,2 5 8 0 4 7,4 7 11 2 5 8,5 8 0 4 7 11,7 11 2 5 8 0,8 0 4 7 11 2,11 2 5 8 0 4

- 0 4 7 11 2 5 8,2 5 8 0 4 7 11,4 7 11 2 5 8 0,5 8 0 4 7 11 2,7 11 2 5 8 0 4,8 0 4 7 11 2 5,11 2 5 8 0 4 7
-1

At some point you'll probably need the formula for MIDI note to frequency conversion. IIRC middle C is MIDI note 60, and each integral step represents a semitone. Here's some code:

http://www.musicdsp.org/showone.php?id=125

spender
  • 117,338
  • 33
  • 229
  • 351