strudel/js/strudel.mjs
2022-01-23 16:41:37 +00:00

129 lines
4.0 KiB
JavaScript

import Fraction from 'fraction.js'
Fraction.prototype.sam = function() {
return Fraction(Math.floor(this))
}
Fraction.prototype.lt = function(other) {
return this.compare(other) < 0
}
Fraction.prototype.lte = function(other) {
return this.compare(other) <= 0
}
Fraction.prototype.gte = function(other) {
return this.compare(other) >= 0
}
class TimeSpan {
constructor(begin, end) {
this.begin = Fraction(begin)
this.end = Fraction(end)
}
get spanCycles() {
var spans = []
var begin = this.begin
var end = this.end
var end_sam = end.sam()
while (end.gt(begin)) {
// If begin and end are in the same cycle, we're done.
if (begin.sam().equals(end_sam)) {
spans.push(new TimeSpan(begin, this.end))
break
}
// add a timespan up to the next sam
var next_begin = begin.next_sam()
spans.push(TimeSpan(begin, next_begin))
// continue with the next cycle
begin = next_begin
}
return(spans)
}
withTime(func_time) {
// Applies given function to both the begin and end time value of the timespan"""
return(new TimeSpan(func_time(this.begin), func_time(this.end)))
}
intersection(other) {
// Intersection of two timespans, returns None if they don't intersect.
intersect_begin = Math.max(this.begin, other.begin)
intersect_end = Math.min(this.end, other.end)
if (intersect_begin.gt(intersect_end)) {
return(undefined)
}
if (intersect_begin.equals(intersect_end)) {
// Zero-width (point) intersection - doesn't intersect if it's at the end of a
// non-zero-width timespan.
if (intersect_begin.equals(this.end) && this.begin.lt(this.end)) {
return(undefined)
}
if (intersect_begin.equals(other.end) && other.begin.lt(other.end)) {
return(undefined)
}
}
return(new TimeSpan(intersect_begin, intersect_end))
}
intersection_e(other) {
// Like 'sect', but raises an exception if the timespans don't intersect.
result = this.intersection(other)
if (result.equals(None)) {
// TODO - raise exception
// raise ValueError(f'TimeSpan {self} and TimeSpan {other} do not intersect')
}
return(result)
}
get midpoint() {
return(this.begin.add((this.end.sub(this.begin)).div(Fraction(2))))
}
equals(other) {
return this.begin.equals(other.begin) && this.end.equals(other.end)
}
}
class Hap {
/*
Event class, representing a value active during the timespan
'part'. This might be a fragment of an event, in which case the
timespan will be smaller than the 'whole' timespan, otherwise the
two timespans will be the same. The 'part' must never extend outside of the
'whole'. If the event represents a continuously changing value
then the whole will be returned as None, in which case the given
value will have been sampled from the point halfway between the
start and end of the 'part' timespan.
*/
constructor(whole, part, value) {
this.whole = whole
this.part = part
this.value = value
}
with_span(func) {
// Returns a new event with the function f applies to the event timespan.
var whole = this.whole ? func(self.whole) : undefined
return new Hap(whole, func(this.part), this.value)
}
with_value(func) {
// Returns a new event with the function f applies to the event value.
return new Hap(this.whole, this.part, func(this.value))
}
has_onset() {
// Test whether the event contains the onset, i.e that
// the beginning of the part is the same as that of the whole timespan."""
return (this.whole != undefined) && (this.whole.begin.equals(this.part.begin))
}
}
export {TimeSpan, Hap}