/*
Package scale is for calculating music scales.
*/
package scale
import (
"errors"
"fmt"
"os"
"strings"
)
var (
sharpScale = []string{"A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"}
flatScale = []string{"A", "Bb", "B", "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab"}
sharps = []string{"a", "C", "G", "D", "A", "E", "B", "F#", "e", "b", "f#", "c#", "g#", "d#"} // a minor and C major is here just for the tests
flats = []string{"F", "Bb", "Eb", "Ab", "Db", "Gb", "d", "g", "c", "f", "bb", "eb"}
)
// setScale sets the scale to sharp or flat based on tonic
func setScale(tonic string) (scale []string, err error) {
for _, v := range sharps {
if v == tonic {
return sharpScale, nil
}
}
for _, v := range flats {
if v == tonic {
return flatScale, nil
}
}
return nil, errors.New("tonic has not found: neither in sharps, nor in flats")
}
// genChromatic generates the choromatic scale starting with the given tonic
func genChromatic(scale []string, tonic string) (chr []string) {
for k, v := range scale {
if strings.ToLower(v) == strings.ToLower(tonic) {
chr = scale[k:]
chr = append(chr, scale[:k]...)
break
}
}
return
}
// genDiatonic generates the diatonic scale based on intervals
func genDiatonic(scale []string, interval string) (diat []string) {
act := 0
diat = append(diat, scale[0])
for k := 0; k < len(interval)-1; k++ {
switch {
case interval[k] == 'm':
act += 1
case interval[k] == 'M':
act += 2
case interval[k] == 'A':
act += 3
}
if act >= len(scale) {
act = act - len(scale)
}
diat = append(diat, scale[act])
}
return
}
// Scale gives back the proper scale for the given tonic and intervals.
func Scale(tonic, interval string) (s []string) {
scale, err := setScale(tonic)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
s = genChromatic(scale, tonic)
if len(interval) == 0 {
return
}
s = genDiatonic(s, interval)
return
}