Skip to content

Instantly share code, notes, and snippets.

@WizardOfArc
Created October 26, 2018 15:33
Show Gist options
  • Select an option

  • Save WizardOfArc/c1c03a96b48161db90286087ec3c8671 to your computer and use it in GitHub Desktop.

Select an option

Save WizardOfArc/c1c03a96b48161db90286087ec3c8671 to your computer and use it in GitHub Desktop.

Revisions

  1. WizardOfArc created this gist Oct 26, 2018.
    209 changes: 209 additions & 0 deletions ChordFinderChartBuilder.scala
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,209 @@
    import java.io.FileWriter

    object NumberHelper {

    def safeMod(num: Int): Int = {
    val modded = num % 12
    if (modded < 0){
    modded + 12
    } else {
    modded
    }
    }
    }

    case class Note(value: Int){
    def -(other: Note) = {
    Notes.forInt(value - other.value)
    }
    def +(other: Note) = {
    Notes.forInt(value + other.value)
    }
    def show = s"$value"
    }

    object Notes {
    val One = Note(0)
    val FlatTwo = Note(1)
    val Two = Note(2)
    val FlatThree = Note(3)
    val Three = Note(4)
    val Four = Note(5)
    val Tritone = Note(6)
    val Five = Note(7)
    val FlatSix = Note(8)
    val Six = Note(9)
    val FlatSeven = Note(10)
    val Seven = Note(11)

    val allNotes = List(
    One, FlatTwo, Two, FlatThree,
    Three, Four, Tritone, Five,
    FlatSix, Six, FlatSeven, Seven
    )
    val diatonicNotes = List(One, Two, Three, Four, Five, Six, Seven)
    val simpleChordTones = List(One, FlatThree, Three, Five)
    def forInt(num: Int) = {
    allNotes(NumberHelper.safeMod(num))
    }
    }

    case class Root (
    val name: String,
    val value: Note
    )

    object Roots {
    val One = Root("I", Notes.forInt(0))
    val FlatTwo = Root("bII", Notes.forInt(1))
    val Two = Root("II", Notes.forInt(2))
    val FlatThree = Root("bIII", Notes.forInt(3))
    val Three = Root("III", Notes.forInt(4))
    val Four = Root("IV", Notes.forInt(5))
    val Tritone = Root("bV", Notes.forInt(6))
    val Five = Root("V", Notes.forInt(7))
    val FlatSix = Root("bVI", Notes.forInt(8))
    val Six = Root("VI", Notes.forInt(9))
    val FlatSeven = Root("bVII", Notes.forInt(10))
    val Seven = Root("VII", Notes.forInt(11))
    val allRoots = List(
    One, FlatTwo, Two, FlatThree,
    Three, Four, Tritone, Five,
    FlatSix, Six, FlatSeven, Seven
    )

    def forInt(num: Int): Root = allRoots(NumberHelper.safeMod(num))
    def forNote(note: Note): Root = forInt(note.value)
    }

    case class Quality (
    val name: String,
    val value: Note
    )

    object Qualities {
    val All = Quality("all", Notes.forInt(0))
    val FlatNine = Quality("b9", Notes.forInt(1))
    val Sus2 = Quality("sus2", Notes.forInt(2))
    val Minor = Quality("minor", Notes.forInt(3))
    val Major = Quality("major", Notes.forInt(4))
    val Sus4 = Quality("sus4", Notes.forInt(5))
    val Dim = Quality("dim or #4", Notes.forInt(6))
    val MajorOrMinor = Quality("major or minor", Notes.forInt(7))
    val Flat6 = Quality("b6", Notes.forInt(8))
    val Six = Quality("6", Notes.forInt(9))
    val Dom7 = Quality("7", Notes.forInt(10))
    val Major7 = Quality("major7", Notes.forInt(11))

    val allQualities = List(
    All, FlatNine, Sus2, Minor,
    Major, Sus4, Dim, MajorOrMinor,
    Flat6, Six, Dom7, Major7
    )

    def forInt(num: Int) = allQualities(NumberHelper.safeMod(num))
    def forNote(note: Note) = forInt(note.value)
    }


    case class Chord(root: Root, quality: Quality){
    def isDiatonic: Boolean = {
    Notes.diatonicNotes.contains(root.value) &&
    Notes.diatonicNotes.contains(root.value + quality.value )
    }

    def isSimple: Boolean = {
    Notes.simpleChordTones.contains(quality.value)
    }

    def display: String = s"${root.name} ${quality.name}"
    }


    object HTMLHelper {
    def style = """
    | <style>
    | th {
    | background-color: #afafaf;
    | }
    | td {
    | border: solid black 1px;
    | padding: 5px;
    | }
    | .diatonic {
    | font-weight: bold;
    | border: solid black 4px;
    | }
    | .simple {
    | background-color: #7faf7f;
    | }
    | </style>
    """.stripMargin

    def makePage(content: String): String = {
    s"""
    |<!DOCTYPE HTML>
    |<html>
    | <head>
    | $style
    | </head>
    | <body>
    | ${content}
    | </body>
    |</html>
    """.stripMargin
    }

    def makeTable(rows: Seq[String]): String = {
    s"""<table cellspacing="0" cellpadding="0">
    | ${rows.mkString("\n")}
    </table>
    """.stripMargin
    }

    }

    object ChordFinderChartBuilder {

    def toRow(entry: (Note, Seq[(Note, Chord)])): String = {
    val (note,cells) = entry
    s"""<tr>
    | <th>${note.show}</th>
    | ${cells.map{toCell }.mkString("\n")}
    |</tr>""".stripMargin
    }

    def toCell(value: (Note, Chord)): String = {
    val (_, chord) = value
    val simpleOption = if(chord.isSimple){
    Some("simple")
    } else { None }
    val diatonicOption = if(chord.isDiatonic){
    Some("diatonic")
    } else { None }

    val classlist = List(simpleOption, diatonicOption).flatten.mkString(" ")
    val simple = if(classlist.nonEmpty){
    s""" class="$classlist" """
    } else { "" }
    s"""<td$simple>${chord.display}</td>"""
    }

    def baseChart = {
    for {
    a <- Notes.allNotes
    b <- Qualities.allQualities
    } yield (a, Chord(Roots.forNote(a - b.value), b))
    }

    def main(args: Array[String]): Unit = {
    val grouped = baseChart.groupBy(_._1).toList.sortBy(_._1.value)
    val rows = grouped map toRow
    val contents = HTMLHelper.makePage(HTMLHelper.makeTable(rows ) )

    val fw = new FileWriter("chart.html")
    fw.write(contents)
    fw.close()
    }

    }