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 = """ | """.stripMargin def makePage(content: String): String = { s""" | | | | $style | | | ${content} | | """.stripMargin } def makeTable(rows: Seq[String]): String = { s""" | ${rows.mkString("\n")}
""".stripMargin } } object ChordFinderChartBuilder { def toRow(entry: (Note, Seq[(Note, Chord)])): String = { val (note,cells) = entry s""" | ${note.show} | ${cells.map{toCell }.mkString("\n")} |""".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"""${chord.display}""" } 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() } }