Skip to main content
Glama
emicklei

melrōse musical expression player

by emicklei
loop.go3.49 kB
package core import ( "bytes" "fmt" "sync" "time" "github.com/emicklei/melrose/notify" ) // todo: protect with mutex var runningLoop *Loop type Loop struct { ctx Context target []Sequenceable isRunning bool mutex sync.RWMutex condition Condition startedAt time.Time nextPlayAt time.Time } func NewLoop(ctx Context, target []Sequenceable) *Loop { return &Loop{ ctx: ctx, target: target, condition: TrueCondition, } } func (l *Loop) Target() []Sequenceable { return l.target } func (l *Loop) SetTarget(newTarget []Sequenceable) { l.mutex.Lock() defer l.mutex.Unlock() l.target = newTarget } func (l *Loop) IsRunning() bool { l.mutex.RLock() defer l.mutex.RUnlock() return l.isRunning } func (l *Loop) Storex() string { var b bytes.Buffer fmt.Fprintf(&b, "loop(") AppendStorexList(&b, true, l.target) fmt.Fprintf(&b, ")") return b.String() } func (l *Loop) Evaluate(ctx Context) error { // create and start a clone clone := NewLoop(l.ctx, l.target) cond := NoCondition if with, ok := ctx.(Conditional); ok { cond = with.Condition() } clone.condition = cond if notify.IsDebug() { notify.Debugf("loop.eval") } clone.Play(l.ctx, time.Now()) return nil } // Inspect is part of Inspectable func (l *Loop) Inspect(i Inspection) { i.Properties["running"] = l.isRunning if runningLoop == l { i.Properties["leader"] = true } } // in mutex func (l *Loop) reschedule(d AudioDevice, when time.Time) { if !l.isRunning { return } if l.condition != nil && !l.condition() { l.isRunning = false return } moment := when for _, each := range l.target { // after each other moment = d.Play(l.condition, each, l.ctx.Control().BPM(), moment) } if notify.IsDebug() { notify.Debugf("core.loop: next=%s", moment.Format("15:04:05.00")) } // schedule the loop itself so it can play again when Handle is called l.nextPlayAt = moment d.Schedule(l, moment) } func (l *Loop) NextPlayAt() time.Time { l.mutex.RLock() defer l.mutex.RUnlock() if !l.isRunning { return time.Time{} } return l.nextPlayAt } // Handle is part of TimelineEvent func (l *Loop) Handle(tim *Timeline, when time.Time) { l.mutex.Lock() defer l.mutex.Unlock() if !l.isRunning { return } l.reschedule(l.ctx.Device(), when) } func (l *Loop) NoteChangesDo(block func(NoteChange)) {} // Play is part of Playable func (l *Loop) Play(ctx Context, at time.Time) time.Time { l.mutex.Lock() defer l.mutex.Unlock() forever := time.Now().AddDate(100, 0, 0) if l.isRunning { return forever } when := at if runningLoop != nil { // only if loops do want to start at the same time // we delay this loop until the last loop finished if at.Sub(runningLoop.startedAt).Milliseconds() > 100 { when = runningLoop.nextPlayAt } } else { runningLoop = l } l.isRunning = true l.startedAt = when l.reschedule(l.ctx.Device(), when) return forever } // Stop is part of Playable func (l *Loop) Stop(ctx Context) error { l.mutex.Lock() defer l.mutex.Unlock() if !l.isRunning { return nil } l.isRunning = false if l == runningLoop { runningLoop = nil } return nil } // IsPlaying is part of Playable func (l *Loop) IsPlaying() bool { return l.isRunning } func (l *Loop) S() Sequence { return l.ToSequence(1) } func (l *Loop) ToSequence(loopcount int) Sequence { all := Sequence{} for i := 0; i < loopcount; i++ { for _, each := range l.target { all = all.SequenceJoin(each.S()) } } return all }

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