Skip to main content
Glama
emicklei

melrōse musical expression player

by emicklei
impl.go5.29 kB
package api import ( "bytes" "fmt" "path/filepath" "reflect" "strings" "time" "github.com/emicklei/melrose/core" "github.com/emicklei/melrose/dsl" midifile "github.com/emicklei/melrose/midi/file" "github.com/emicklei/melrose/notify" "github.com/expr-lang/expr/file" ) type ServiceImpl struct { context core.Context evaluator *dsl.Evaluator } func NewService(ctx core.Context) Service { return &ServiceImpl{context: ctx, evaluator: dsl.NewEvaluator(ctx)} } func (s *ServiceImpl) Context() core.Context { return s.context } func (s *ServiceImpl) ChangeDefaultDeviceAndChannel(isInput bool, deviceID int, channel int) error { // TODO handle channel var err error if isInput { err = s.context.Device().HandleSetting("midi.in", []any{deviceID}) } else { err = s.context.Device().HandleSetting("midi.out", []any{deviceID}) } if err != nil { notify.Errorf("change device/channel failed:%s", err.Error()) } return nil } func (s *ServiceImpl) updateMetadata(file string, lineEnd int, source string) error { s.context.Environment().Store(core.WorkingDirectory, filepath.Dir(file)) // get and store lineEnd end breaks := strings.Count(source, "\n") if breaks > 0 { s.context.Environment().Store(core.EditorLineStart, lineEnd-breaks) } else { s.context.Environment().Store(core.EditorLineStart, lineEnd) } s.context.Environment().Store(core.EditorLineEnd, lineEnd) return nil } func (s *ServiceImpl) CommandInspect(file string, lineEnd int, source string) (any, error) { s.updateMetadata(file, lineEnd, source) lastValue, err := s.evaluator.EvaluateProgram(source) if err != nil { return nil, patchFilelocation(err, lineEnd) } if lastValue == nil { core.PrintValue(s.context, nil) return lastValue, nil } // check for function if reflect.TypeOf(lastValue).Kind() == reflect.Func { if fn, ok := s.evaluator.LookupFunction(source); ok { fmt.Fprintf(notify.Console.StandardOut, "%s: %s\n", fn.Title, fn.Description) } } else { core.PrintValue(s.context, lastValue) } return lastValue, nil } type CommandPlayResponse struct { EndTime time.Time ExpressionResult any } func (s *ServiceImpl) CommandPlay(file string, lineEnd int, source string) (CommandPlayResponse, error) { s.updateMetadata(file, lineEnd, source) programResult, err := s.evaluator.EvaluateProgram(source) if err != nil { return CommandPlayResponse{}, patchFilelocation(err, lineEnd) } var endTime time.Time if pl, ok := programResult.(core.Playable); ok { notify.Infof("play(%s)", displayString(s.context, pl)) endTime = pl.Play(s.context, time.Now()) } else { // unvalue if needed if u, ok := programResult.(core.HasValue); ok { programResult = u.Value() } // any sequenceable is playable if seq, ok := programResult.(core.Sequenceable); ok { notify.Infof("play(%s)", displayString(s.context, seq)) endTime = s.context.Device().Play( core.NoCondition, seq, s.context.Control().BPM(), time.Now()) } else { notify.Debugf("not sequenceable:%v [%T]", programResult, programResult) } } return CommandPlayResponse{EndTime: endTime, ExpressionResult: programResult}, nil } func (s *ServiceImpl) CommandStop(file string, lineEnd int, source string) (any, error) { s.updateMetadata(file, lineEnd, source) returnValue, err := s.evaluator.EvaluateProgram(source) if err != nil { return nil, patchFilelocation(err, lineEnd) } if p, ok := returnValue.(core.Stoppable); ok { notify.Infof("stopping(%s)", displayString(s.context, p)) return returnValue, p.Stop(s.context) } return returnValue, nil } func (s *ServiceImpl) CommandEvaluate(file string, lineEnd int, source string) (any, error) { s.updateMetadata(file, lineEnd, source) returnValue, err := s.evaluator.EvaluateProgram(source) if err != nil { return nil, patchFilelocation(err, lineEnd) } return returnValue, nil } func (s *ServiceImpl) CommandKill() error { // kill the play and any loop dsl.StopAllPlayables(s.context) s.context.Device().Reset() return nil } func (s *ServiceImpl) CommandHover(source string) string { // inspect as variable value, ok := s.context.Variables().Get(source) if ok { return core.NewInspect(s.context, source, value).Markdown() } // inspect as function fun, ok := s.evaluator.LookupFunction(source) if ok { return fun.Markdown() } return "" } func (s *ServiceImpl) CommandMIDISample(filename string, lineEnd int, source string) ([]byte, error) { s.updateMetadata(filename, lineEnd, source) returnValue, err := s.evaluator.EvaluateProgram(source) if err != nil { return []byte{}, patchFilelocation(err, lineEnd) } buffer := new(bytes.Buffer) err = midifile.ExportOn(buffer, returnValue, s.context.Control().BPM(), s.context.Control().BIAB()) if err != nil { return []byte{}, err } return buffer.Bytes(), nil } func (s *ServiceImpl) ListDevices() []core.DeviceDescriptor { return s.context.Device().ListDevices() } func displayString(ctx core.Context, v any) string { name := ctx.Variables().NameFor(v) if len(name) == 0 { name = core.Storex(v) } return name } func patchFilelocation(err error, lineEnd int) error { // patch Location of error if fe, ok := err.(*file.Error); ok { fe.Line = fe.Line - 1 + lineEnd return fe } return err }

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