Created
September 28, 2020 20:46
-
-
Save arschles/4e0dcabdb5429a99d47fd8244e81f99e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Package scale provides functionality to calculate chromatic scales based on | |
// tonics, major/minor, sharp/flat, and intervals | |
// | |
// This requires extensive music understanding | |
// | |
// This is not an easy exercise and is not production ready. | |
// Hic sunt dracones 🤡 | |
package scale | |
import "strings" | |
// this is the entire chromatic scale represented in sharps format | |
var everythingSharps = []string{"A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"} | |
// this is the entire chromatic scale represented in flats format | |
var everythingFlats = []string{"A", "Bb", "B", "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab"} | |
// Scale will give you the scale back for a given tonic and interval. | |
// I have no idea how to explain what this thing does in music terms, so | |
// don't even ask. It follows some rules. The end. | |
// | |
// By the way, if you are looking to do this for an exercism problem, | |
// just run. Hic sunt dracones 👺 | |
func Scale(tonic, interval string) []string { | |
allSharps := map[string]struct{}{ | |
"G": {}, | |
"D": {}, | |
"A": {}, | |
"B": {}, | |
"E": {}, | |
"F#": {}, | |
"f#": {}, | |
"C": {}, | |
"a": {}, | |
"b": {}, | |
"c#": {}, | |
"g#": {}, | |
"d#": {}, | |
} | |
normalized := strings.Title(tonic) | |
entire := startingAt(normalized, everythingFlats) | |
if _, ok := allSharps[tonic]; ok { | |
entire = startingAt(normalized, everythingSharps) | |
} | |
if interval != "" { | |
return applySteps(entire, interval) | |
} | |
return entire | |
} | |
// applySteps parses the given interval and returns a slice based on the given one, | |
// according to it, from left to right. | |
// | |
// Once again, the rules are based on music scales. | |
// | |
// A = skip 2 elements in the slice, M = skip one, m = don't skip | |
func applySteps(slice []string, interval string) []string { | |
ret := []string{} | |
intervalIdx := 0 | |
for i := 0; i < len(slice); { | |
elt := slice[i] | |
ret = append(ret, elt) | |
curInterval := interval[intervalIdx] | |
if curInterval == 'M' { | |
i += 2 | |
} else if curInterval == 'A' { | |
i += 3 | |
} else { | |
i++ | |
} | |
intervalIdx++ | |
} | |
return ret | |
} | |
// startingAt returns a slice based on the given one, starting at the given | |
// string, and looping around until it reaches the original starting point | |
func startingAt(start string, slice []string) []string { | |
startIdx := 0 | |
for i, note := range slice { | |
if note == start { | |
startIdx = i | |
break | |
} | |
} | |
ret := make([]string, len(slice)) | |
retIdx := 0 | |
for curIdx := startIdx; curIdx < len(slice); curIdx++ { | |
ret[retIdx] = slice[curIdx] | |
retIdx++ | |
} | |
for i := 0; i < startIdx; i++ { | |
ret[retIdx] = slice[i] | |
retIdx++ | |
} | |
return ret | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment