Files
alda-mirror/client/model/marker.go
Dave Yarwood 632be89a6c Bugfix: adjust when we do and don't apply global attributes
This improves things a bit with respect to global attributes being applied too
often. Usually you don't notice because we're just applying the same attribute
values multiple times, but it's particularly noticeable with things like
(octave! 'up).

I haven't quite 100% fixed the (octave! 'up) case yet. I think I also need to
make it so that global attribute changes don't immediately apply the attribute
change, because then it'll just get applied again right before the next
note/rest.
2021-06-06 20:23:50 -04:00

121 lines
3.3 KiB
Go

package model
import (
"fmt"
"alda.io/client/json"
)
// A Marker gives a name to a point in time in a score.
type Marker struct {
SourceContext AldaSourceContext
Name string
}
// GetSourceContext implements HasSourceContext.GetSourceContext.
func (marker Marker) GetSourceContext() AldaSourceContext {
return marker.SourceContext
}
// JSON implements RepresentableAsJSON.JSON.
func (marker Marker) JSON() *json.Container {
return json.Object(
"type", "marker",
"value", json.Object("name", marker.Name),
)
}
// UpdateScore implements ScoreUpdate.UpdateScore by storing the current point
// in time as a named marker.
//
// The assumption is that all current parts have the same current offset. In the
// rare event that this is not the case, an error is returned because we don't
// have a single current offset to store under the marker.
func (marker Marker) UpdateScore(score *Score) error {
if len(score.CurrentParts) == 0 {
return fmt.Errorf(
"can't define marker \"%s\" outside the context of a part", marker.Name,
)
}
offset := score.CurrentParts[0].CurrentOffset
if len(score.CurrentParts) > 1 {
for _, part := range score.CurrentParts[1:len(score.CurrentParts)] {
if part.CurrentOffset != offset {
return fmt.Errorf("offset of marker \"%s\" unclear", marker.Name)
}
}
}
score.Markers[marker.Name] = offset
return nil
}
// DurationMs implements ScoreUpdate.DurationMs by returning 0, since defining a
// marker is conceptually instantaneous.
func (Marker) DurationMs(part *Part) float64 {
return 0
}
// VariableValue implements ScoreUpdate.VariableValue.
func (marker Marker) VariableValue(score *Score) (ScoreUpdate, error) {
return marker, nil
}
// AtMarker is an action where a part's offset gets set to a point in time
// denoted previously by a Marker.
type AtMarker struct {
SourceContext AldaSourceContext
Name string
}
// GetSourceContext implements HasSourceContext.GetSourceContext.
func (atMarker AtMarker) GetSourceContext() AldaSourceContext {
return atMarker.SourceContext
}
// JSON implements RepresentableAsJSON.JSON.
func (atMarker AtMarker) JSON() *json.Container {
return json.Object(
"type", "at-marker",
"value", json.Object("name", atMarker.Name),
)
}
// UpdateScore implements ScoreUpdate.UpdateScore by setting the current offset
// of all active parts to the offset stored in the marker with the provided
// name.
//
// If no such marker was previously defined, an error is returned.
func (atMarker AtMarker) UpdateScore(score *Score) error {
score.ApplyGlobalAttributes()
offset, hit := score.Markers[atMarker.Name]
if !hit {
return fmt.Errorf("Marker undefined: %s", atMarker.Name)
}
for _, part := range score.CurrentParts {
part.LastOffset = part.CurrentOffset
part.CurrentOffset = offset
}
return nil
}
// DurationMs implements ScoreUpdate.DurationMs by returning 0 because jumping
// to a marker is conceptually instantaneous.
//
// NB: I don't think it really makes any sense for an AtMarker event to occur
// within a Cram expression. Arguably, this should result in a syntax error.
func (AtMarker) DurationMs(part *Part) float64 {
return 0
}
// VariableValue implements ScoreUpdate.VariableValue.
func (atMarker AtMarker) VariableValue(score *Score) (ScoreUpdate, error) {
return atMarker, nil
}