mirror of
https://github.com/alda-lang/alda.git
synced 2026-03-03 18:23:36 +01:00
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.
121 lines
3.3 KiB
Go
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
|
|
}
|