func ChordToNotes(c string) (notes []Note, err error) {
	homeDir, err := os.UserHomeDir()
	if err != nil {
		return
	}
	chordDir := path.Join(homeDir, ".cache", "miti")
	chordFile := path.Join(chordDir, "chords.json")
	log.Tracef("chordFile: %s", chordFile)
	ks, err := jsonstore.Open(chordFile)
	if err != nil {
		err = os.MkdirAll(chordDir, os.ModePerm)
		if err != nil {
			return
		}
		ks = new(jsonstore.JSONStore)
	}
	err = ks.Get(c, &notes)
	if err == nil {
		return
	}
	if _, ok := chordToNotesCache[c]; ok {
		notes = chordToNotesCache[c]
		return
	}
	defer func() {
		if err == nil {
			chordToNotesCache[c] = notes
		}
	}()
	tmpfile, err := ioutil.TempFile(".", "lilypond")
	if err != nil {
		return
	}
	defer os.Remove(tmpfile.Name())  
	lilypondscript := `
\score {
\chordmode { ` + ChordToLilypond(c) + ` }
  \midi { }
}`
	_, err = tmpfile.WriteString(lilypondscript)
	if err != nil {
		return
	}
	err = tmpfile.Close()
	if err != nil {
		return
	}
	log.Tracef("lilypond:\n%s", lilypondscript)
	midiFileName := tmpfile.Name()
	if runtime.GOOS == "windows" {
		midiFileName += ".mid"
	} else {
		midiFileName += ".midi"
	}
	log.Tracef("writing midi file: '%s'", midiFileName)
	cmd := exec.Command("lilypond", "-o", tmpfile.Name(), tmpfile.Name())
	output, err := cmd.CombinedOutput()
	if err != nil {
		err = fmt.Errorf("lilypond error: %s\n\ndata: '%s'", err.Error(), output)
		return
	}
	defer os.Remove(midiFileName)
	f, err := os.Open(midiFileName)
	if err != nil {
		return
	}
	defer f.Close()
	file, err := midi.Read(f)
	if err != nil {
		return
	}
	for i, track := range file.Tracks {
		if i != 1 {
			continue
		}
		for _, n := range track.Events {
			if len(n.Messages) < 3 {
				continue
			}
			if n.Tick == 0 && n.Messages[2] == 90 {
				notes = append(notes, MidiToNote(int(n.Messages[1])))
			}
		}
	}
	if len(notes) == 0 {
		err = fmt.Errorf("no notes")
	} else {
		ks.Set(c, &notes)
		err = jsonstore.Save(ks, chordFile)
	}
	return
}
ChordToNotes converts chords to notes using lilypond
package music
import (
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"os/exec"
	"path"
	"runtime"
	"strconv"
	"strings"
	"github.com/kbinani/midi"
	"github.com/schollz/jsonstore"
	log "github.com/schollz/logger"
)
type Chord struct {
	Notes []Note
	On    bool
}
func (c Chord) String() string {
	s := ""
	for i, n := range c.Notes {
		if i == 0 {
			s += fmt.Sprintf("%s%d", n.Name, n.Octave)
		} else {
			s += n.Name
		}
	}
	return s
}
type Note struct {
	Name   string
	Octave int
	MIDI   int
}
func NewNote(name string, octave int) Note {
	return Note{
		Name:   name,
		Octave: octave,
		MIDI:   NoteToMidi(name, octave),
	}
}
func NoteToMidi(note string, octave int) int {
	if _, ok := allowedNotes[note]; !ok {
		return -1
	}
	return allowedNotes[note] + 12*(octave-1)
}
func ParseCluster(cluster string, lastNote0 ...Note) (ns []Note, err error) {
	lastNote := NewNote("C", 4)
	if len(lastNote0) > 0 {
		lastNote = lastNote0[0]
	}
	cc, nn, err := popFirstNote(cluster, lastNote)
	if err != nil {
		return
	}
	ns = append(ns, nn)
	lastNote = nn
	it := 0
	for {
		it++
		if it > 10 {
			err = fmt.Errorf("too many notes in cluster: %s", cluster)
			return
		}
		if cc == "" {
			break
		}
		cc, nn, err = popFirstNote(cc, lastNote)
		if err != nil {
			return
		}
		ns = append(ns, nn)
		lastNote = nn
	}
	return
}
func popFirstNote(s string, lastNote Note) (s2 string, n Note, err error) {
	defer func() {
		n.MIDI = NoteToMidi(n.Name, n.Octave)
	}()
	if isValidNote(s) {
		n = ClosestNote(s, lastNote)
		return
	}
	for i := 1; i <= len(s); i++ {
		nn := string(s[:i])
		nn1 := nn[len(nn)-1:]
		if isNumber(nn1) {
			s2 = strings.TrimPrefix(s, nn)
			octave, _ := strconv.Atoi(nn1)
			n = Note{Name: strings.TrimSuffix(nn, nn1), Octave: octave}
			return
		}
		if !isValidNote(nn) {
			nn = string(s[:i-1])
			s2 = strings.TrimPrefix(s, nn)
			n = ClosestNote(nn, lastNote)
			return
		}
	}
	err = fmt.Errorf("could not parse: '%s'", s)
	return
}
func isNumber(s string) bool {
	if _, err := strconv.Atoi(s); err == nil {
		return true
	}
	return false
}
func isValidNote(s string) bool {
	if _, ok := allowedNotes[s]; ok {
		return true
	}
	return false
}
func ClosestNote(name string, n Note) (n2 Note) {
	possibleNotes := []Note{NewNote(name, n.Octave), NewNote(name, n.Octave-1), NewNote(name, n.Octave+1)}
	midiDiff := 10000.0
	for i := 0; i < 3; i++ {
		d := math.Abs(float64(possibleNotes[i].MIDI - n.MIDI))
		if d < midiDiff {
			midiDiff = d
			n2 = possibleNotes[i]
		}
	}
	return
}
var allowedNotes = map[string]int{
	"C":  24,
	"C#": 25,
	"Cb": 23,
	"D":  26,
	"D#": 27,
	"Db": 25,
	"E":  28,
	"E#": 29,
	"Eb": 27,
	"F":  29,
	"F#": 30,
	"Fb": 28,
	"G":  31,
	"G#": 32,
	"Gb": 30,
	"A":  33,
	"A#": 34,
	"Ab": 32,
	"B":  35,
	"B#": 26,
	"Bb": 34,
}
var c0notes = map[string]int{
	"C":  24,
	"Db": 25,
	"D":  26,
	"Eb": 27,
	"E":  28,
	"F":  29,
	"Gb": 30,
	"G":  31,
	"Ab": 32,
	"A":  33,
	"Bb": 34,
	"B":  35,
}
var midiToNote map[int]Note
var chordToNotesCache map[string][]Note
func init() {
	chordToNotesCache = make(map[string][]Note)
	midiToNote = make(map[int]Note)
	for octave := 1; octave < 8; octave++ {
		for note := range c0notes {
			midiToNote[c0notes[note]+12*(octave-1)] = Note{Name: note, Octave: octave, MIDI: c0notes[note] + 12*(octave-1)}
		}
	}
}
func MidiToNote(midi int) Note {
	if _, ok := midiToNote[midi]; !ok {
		return Note{}
	}
	return midiToNote[midi]
}