Skip to main content
Glama
emicklei

melrōse musical expression player

by emicklei
notemap.go4.09 kB
package op import ( "bytes" "errors" "fmt" "strings" "github.com/emicklei/melrose/core" "github.com/emicklei/melrose/notify" ) const ( formatDotAndBangs = iota formatNumbers ) type NoteMap struct { Target core.HasValue Indices []int indicesFormat int maxIndex int } // NewNoteMap returns a NoteMap that creates a sequence from occurrences of a note. // The format of indices can be one of: // 1 2 4 ; each number is an index in the sequence where the note is present; rest notes are placed in the gaps. // ! . ! ; each dot is a rest, each exclamation mark is a presence of a note. func NewNoteMap(indices string, note core.HasValue) (NoteMap, error) { idx := []int{} // check for dots and bangs first var parsed [][]int format := formatNumbers var maxIndex int if strings.ContainsAny(indices, "!.") { parsed = parseIndices(convertDotsAndBangs(indices)) format = formatDotAndBangs maxIndex = len(indices) } else if strings.ContainsAny(indices, "1234567890 ") { // space is allowed parsed = parseIndices(indices) } else { return NoteMap{}, errors.New("bad syntax NoteMap; must have digits,spaces OR dots and exclamation marks") } for _, each := range parsed { idx = append(idx, each[0]) } max := sliceMax(idx) if max > maxIndex { maxIndex = max } return NoteMap{ Target: note, Indices: idx, // internal indicesFormat: format, maxIndex: maxIndex, }, nil } func convertDotsAndBangs(format string) string { var b bytes.Buffer for i, each := range []rune(format) { if each == '.' { fmt.Fprintf(&b, " ") } else { fmt.Fprintf(&b, "%d ", i+1) } } return b.String() } func (n NoteMap) formattedIndices(format int) string { var b bytes.Buffer if format == formatDotAndBangs { for i := 1; i <= n.maxIndex; i++ { found := false for _, each := range n.Indices { if each == i { found = true break } } if found { fmt.Fprintf(&b, "!") } else { fmt.Fprintf(&b, ".") } } } else { for i, each := range n.Indices { if i > 0 { fmt.Fprintf(&b, " ") } fmt.Fprintf(&b, "%d", each) } } return b.String() } func (n NoteMap) Storex() string { st, ok := n.Target.(core.Storable) if !ok { st, ok = n.Target.Value().(core.Storable) } if ok { return fmt.Sprintf("notemap('%s',%s)", n.formattedIndices(n.indicesFormat), st.Storex()) } return "" } // Inspect implements Inspectable func (n NoteMap) Inspect(i core.Inspection) { if n.indicesFormat == formatDotAndBangs { i.Properties["nrs"] = n.formattedIndices(formatNumbers) } else { i.Properties["dots"] = n.formattedIndices(formatDotAndBangs) } n.S().Inspect(i) } func sliceMax(indices []int) int { max := 0 for _, each := range indices { if each > max { max = each } } return max } func (n NoteMap) S() core.Sequence { var note core.Note notelike, ok := n.Target.Value().(core.NoteConvertable) if !ok { // try create sequence first seq, ok := n.Target.Value().(core.Sequenceable) if !ok { notify.Console.Errorf("cannot map %v (%T)", n.Target.Value(), n.Target.Value()) return core.EmptySequence } // then take the first note notes := seq.S() if len(notes.Notes) == 0 || len(notes.Notes[0]) == 0 { return core.EmptySequence } note = notes.Notes[0][0] } else { var err error note, err = notelike.ToNote() if err != nil { notify.Panic(err) return core.EmptySequence } } notes := make([]core.Note, n.maxIndex) for i := range notes { notes[i] = note.ToRest() } for _, each := range n.Indices { notes[each-1] = note } return core.BuildSequence(notes) } // Replaced is part of Replaceable func (n NoteMap) Replaced(from, to core.Sequenceable) core.Sequenceable { if core.IsIdenticalTo(n, from) { return to } notelike, ok := n.Target.Value().(core.NoteConvertable) if !ok { return n } note, err := notelike.ToNote() if err != nil { return n } return NoteMap{ Target: core.On(note.Replaced(from, to)), Indices: n.Indices, indicesFormat: n.indicesFormat, maxIndex: n.maxIndex} }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/emicklei/melrose'

If you have feedback or need assistance with the MCP directory API, please join our Discord server