fully converting all Time classes to ts

This commit is contained in:
Yotam Mann 2019-07-16 15:29:34 -04:00
parent 4e11f13336
commit 4c4db4b614
12 changed files with 965 additions and 138 deletions

View file

@ -0,0 +1,220 @@
import { expect } from "chai";
import teoria from "teoria";
import { BasicTests } from "test/helper/Basic";
import { Offline } from "test/helper/Offline";
import { getContext } from "../Global";
import { Frequency, FrequencyClass } from "./Frequency";
import { Midi } from "./Midi";
import { Ticks } from "./Ticks";
import { Time } from "./Time";
import { TransportTime } from "./TransportTime";
describe("FrequencyClass", () => {
BasicTests(Frequency);
context("Constructor", () => {
it("can be made with or without 'new'", () => {
const f0 = Frequency();
expect(f0).to.be.instanceOf(FrequencyClass);
f0.dispose();
const f1 = new FrequencyClass(getContext());
expect(f1).to.be.instanceOf(FrequencyClass);
f1.dispose();
});
it("can pass in a number in the constructor", () => {
const frequency = Frequency(1);
expect(frequency).to.be.instanceOf(FrequencyClass);
frequency.dispose();
});
it("can pass in a string in the constructor", () => {
const frequency = Frequency("1");
expect(frequency).to.be.instanceOf(FrequencyClass);
frequency.dispose();
});
it("can pass in a value and a type", () => {
expect(Frequency(4, "n").valueOf()).to.equal(2);
});
it("with no arguments evaluates to 0", () => {
expect(Frequency().valueOf()).to.equal(0);
});
it("is evaluated in equations and comparisons using valueOf", () => {
// @ts-ignore
expect(Frequency(1) + 1).to.equal(2);
// @ts-ignore
expect(Frequency(1) + Frequency(1)).to.equal(2);
// @ts-ignore
expect(Frequency(1) > Frequency(0)).to.be.true;
// @ts-ignore
expect(+Frequency(1)).to.equal(1);
});
it("can convert from Time", () => {
expect(Frequency(Time(2)).valueOf()).to.equal(0.5);
expect(Frequency(Time("4n")).valueOf()).to.equal(2);
expect(Frequency(Time(4, "n")).valueOf()).to.equal(2);
});
it("can convert from Frequency", () => {
expect(Frequency(Frequency(2)).valueOf()).to.equal(2);
expect(Frequency(Frequency("4n")).valueOf()).to.equal(2);
expect(Frequency(Frequency(4, "n")).valueOf()).to.equal(2);
});
it("can convert from TransportTime", () => {
expect(Frequency(TransportTime(2)).valueOf()).to.equal(0.5);
expect(Frequency(TransportTime("4n")).valueOf()).to.equal(2);
});
it("can convert from Midi", () => {
expect(Frequency(Midi("C4")).valueOf()).to.equal(Frequency("C4").valueOf());
expect(Frequency(Midi(60)).valueOf()).to.equal(Frequency("C4").valueOf());
expect(Frequency(Midi(61)).valueOf()).to.equal(Frequency("C#4").valueOf());
});
it("can convert from Ticks", () => {
return Offline(({transport}) => {
expect(Frequency(Ticks(transport.PPQ)).valueOf()).to.equal(2);
expect(Frequency(Ticks("4n")).valueOf()).to.equal(2);
});
});
});
context("Eval Types", () => {
it("evaluates numbers as frequency", () => {
expect(Frequency("1").valueOf()).to.equal(1);
expect(Frequency("123").valueOf()).to.equal(123);
expect(Frequency(3.2).valueOf()).to.equal(3.2);
});
it("evaluates notation", () => {
return Offline(({transport}) => {
transport.bpm.value = 120;
transport.timeSignature = 4;
expect(Frequency("4n").valueOf()).to.equal(2);
expect(Frequency("8n").valueOf()).to.equal(4);
expect(Frequency(16, "n").valueOf()).to.equal(8);
transport.bpm.value = 60;
transport.timeSignature = [5, 4];
expect(Frequency("1m").valueOf()).to.equal(1 / 5);
transport.bpm.value = 120;
transport.timeSignature = 4;
});
});
it("evalutes hertz", () => {
expect(Frequency("1hz").valueOf()).to.equal(1);
expect(Frequency("2hz").valueOf()).to.equal(2);
expect(Frequency(4, "hz").valueOf()).to.equal(4);
expect(Frequency("0.25hz").valueOf()).to.equal(0.25);
});
it("evalutes ticks", () => {
return Offline(({transport}) => {
expect(Frequency(transport.PPQ, "i").valueOf()).to.equal(2);
expect(Frequency(1, "i").valueOf()).to.equal(transport.PPQ * 2);
});
});
it("evalutes transport time", () => {
expect(Frequency("1:0").valueOf()).to.equal(0.5);
expect(Frequency("1:4:0").valueOf()).to.equal(0.25);
// expect(Frequency("2:1:0").valueOf()).to.equal(0.25);
});
it("evalutes midi", () => {
expect(Frequency(48, "midi").valueOf()).to.be.closeTo(teoria.Note.fromMIDI(48).fq(), 0.0001);
expect(Frequency(69, "midi").valueOf()).to.be.closeTo(teoria.Note.fromMIDI(69).fq(), 0.0001);
});
it("evalutes hz", () => {
expect(Frequency(48, "hz").valueOf()).to.equal(48);
expect(Frequency(480, "hz").valueOf()).to.equal(480);
});
it("can convert notes into frequencies", () => {
expect(Frequency("C4").valueOf()).to.be.closeTo(teoria.note("C4").fq(), 0.0001);
expect(Frequency("D4").valueOf()).to.be.closeTo(teoria.note("D4").fq(), 0.0001);
expect(Frequency("Db4").valueOf()).to.be.closeTo(teoria.note("Db4").fq(), 0.0001);
expect(Frequency("E4").valueOf()).to.be.closeTo(teoria.note("E4").fq(), 0.0001);
expect(Frequency("F2").valueOf()).to.be.closeTo(teoria.note("F2").fq(), 0.0001);
expect(Frequency("Gb-1").valueOf()).to.be.closeTo(teoria.note("Gb-1").fq(), 0.0001);
expect(Frequency("A#10").valueOf()).to.be.closeTo(teoria.note("A#10").fq(), 0.0001);
expect(Frequency("Bb2").valueOf()).to.be.closeTo(teoria.note("Bb2").fq(), 0.0001);
});
it("handles double accidentals", () => {
expect(Frequency("Cbb4").valueOf()).to.be.closeTo(teoria.note("Cbb4").fq(), 0.0001);
expect(Frequency("Dx4").valueOf()).to.be.closeTo(teoria.note("Dx4").fq(), 0.0001);
expect(Frequency("Dbb4").valueOf()).to.be.closeTo(teoria.note("Dbb4").fq(), 0.0001);
expect(Frequency("Ex4").valueOf()).to.be.closeTo(teoria.note("Ex4").fq(), 0.0001);
expect(Frequency("Fx2").valueOf()).to.be.closeTo(teoria.note("Fx2").fq(), 0.0001);
expect(Frequency("Gbb-1").valueOf()).to.be.closeTo(teoria.note("Gbb-1").fq(), 0.0001);
expect(Frequency("Ax10").valueOf()).to.be.closeTo(teoria.note("Ax10").fq(), 0.0001);
expect(Frequency("Bbb2").valueOf()).to.be.closeTo(teoria.note("Bbb2").fq(), 0.0001);
});
it("can accomidate different concert tuning", () => {
FrequencyClass.A4 = 444;
expect(Frequency("C4").valueOf()).to.be.closeTo(teoria.note("C4").fq(FrequencyClass.A4), 0.0001);
expect(Frequency("D1").valueOf()).to.be.closeTo(teoria.note("D1").fq(FrequencyClass.A4), 0.0001);
FrequencyClass.A4 = 100;
expect(Frequency("C4").valueOf()).to.be.closeTo(teoria.note("C4").fq(FrequencyClass.A4), 0.0001);
// return it to normal
FrequencyClass.A4 = 440;
});
});
context("transpose/harmonize", () => {
it("can transpose a value", () => {
expect(Frequency("A4").transpose(3).toMidi()).to.equal(72);
expect(Frequency("A4").transpose(-3).toMidi()).to.equal(66);
expect(Frequency(440).transpose(-12).valueOf()).to.equal(220);
});
it("can harmonize a value", () => {
expect(Frequency("A4").harmonize([0, 3])).to.be.an("array");
expect(Frequency("A4").harmonize([0, 3]).length).to.equal(2);
expect(Frequency("A4").harmonize([0, 3])[0].toNote()).to.equal("A4");
expect(Frequency("A4").harmonize([0, 3])[1].toNote()).to.equal("C5");
expect(Frequency("A4").harmonize([-12, 0, 12])).to.be.an("array");
expect(Frequency("A4").harmonize([-12, 0, 12]).length).to.equal(3);
expect(Frequency("A4").harmonize([-12, 0, 12])[0].toNote()).to.equal("A3");
expect(Frequency("A4").harmonize([-12, 0, 12])[1].toNote()).to.equal("A4");
expect(Frequency("A4").harmonize([-12, 0, 12])[2].toNote()).to.equal("A5");
});
});
context("Conversions", () => {
it("can convert frequencies into notes", () => {
expect(Frequency(261.625).toNote()).to.equal(teoria.Note.fromFrequency(261.625).note.scientific());
expect(Frequency(440).toNote()).to.equal(teoria.Note.fromFrequency(440).note.scientific());
expect(Frequency(220).toNote()).to.equal(teoria.Note.fromFrequency(220).note.scientific());
expect(Frequency(13.75).toNote()).to.equal(teoria.Note.fromFrequency(13.75).note.scientific());
expect(Frequency(4979).toNote()).to.equal("D#8");
});
it("can convert note to midi values", () => {
expect(Frequency("C4").toMidi()).to.equal(teoria.note("C4").midi());
expect(Frequency("C#0").toMidi()).to.equal(teoria.note("C#0").midi());
expect(Frequency("A-4").toMidi()).to.equal(teoria.note("A-4").midi());
});
it("can convert hertz to seconds", () => {
expect(Frequency(4).toSeconds()).to.equal(0.25);
expect(Frequency("2hz").toSeconds()).to.equal(0.5);
});
});
});

View file

@ -1,8 +1,10 @@
import { getContext } from "../Global";
import { intervalToFrequencyRatio } from "./Conversions";
import { intervalToFrequencyRatio, mtof } from "./Conversions";
import { ftom, getA4, setA4 } from "./Conversions";
import { TimeClass } from "./Time";
import { TypeBaseExpression } from "./TypeBase";
import { TimeBaseClass, TimeBaseUnit, TimeExpression, TimeValue } from "./TimeBase";
export type FrequencyUnit = TimeBaseUnit | "midi";
/**
* Frequency is a primitive type for encoding Frequency values.
@ -12,11 +14,11 @@ import { TypeBaseExpression } from "./TypeBase";
* Frequency(38, "midi") //
* Frequency("C3").transpose(4);
*/
export class FrequencyClass extends TimeClass<Hertz> {
export class FrequencyClass<Type extends number = Hertz> extends TimeClass<Type, FrequencyUnit> {
name = "Frequency";
readonly defaultUnits = "hz";
readonly defaultUnits: FrequencyUnit = "hz";
/**
* The [concert tuning pitch](https://en.wikipedia.org/wiki/Concert_pitch) which is used
@ -33,12 +35,12 @@ export class FrequencyClass extends TimeClass<Hertz> {
// AUGMENT BASE EXPRESSIONS
///////////////////////////////////////////////////////////////////////////
protected _getExpressions(defaultUnit): TypeBaseExpression<Hertz> {
return Object.assign({}, super._getExpressions(defaultUnit), {
protected _getExpressions(): TimeExpression<Type> {
return Object.assign({}, super._getExpressions(), {
midi : {
regexp : /^(\d+(?:\.\d+)?midi)/,
method(value): number {
if (this._defaultUnits === "midi") {
if (this.defaultUnits === "midi") {
return value;
} else {
return FrequencyClass.mtof(value);
@ -50,7 +52,7 @@ export class FrequencyClass extends TimeClass<Hertz> {
method(pitch, octave): number {
const index = noteToScaleIndex[pitch.toLowerCase()];
const noteNumber = index + (parseInt(octave, 10) + 1) * 12;
if (this._defaultUnits === "midi") {
if (this.defaultUnits === "midi") {
return noteNumber;
} else {
return FrequencyClass.mtof(noteNumber);
@ -114,7 +116,7 @@ export class FrequencyClass extends TimeClass<Hertz> {
* Frequency("C4").toMidi(); //60
*/
toMidi(): MidiNote {
return FrequencyClass.ftom(this.valueOf());
return ftom(this.valueOf());
}
/**
@ -158,36 +160,36 @@ export class FrequencyClass extends TimeClass<Hertz> {
/**
* With no arguments, return 0
*/
protected _noArg(): Hertz {
return 0;
protected _noArg(): Type {
return 0 as Type;
}
/**
* Returns the value of a frequency in the current units
*/
protected _frequencyToUnits(freq: Hertz): Hertz {
return freq;
protected _frequencyToUnits(freq: Hertz): Type {
return freq as Type;
}
/**
* Returns the value of a tick in the current time units
*/
protected _ticksToUnits(ticks: Ticks): Hertz {
return 1 / ((ticks * 60) / (this._getBpm() * this._getPPQ()));
protected _ticksToUnits(ticks: Ticks): Type {
return 1 / ((ticks * 60) / (this._getBpm() * this._getPPQ())) as Type;
}
/**
* Return the value of the beats in the current units
*/
protected _beatsToUnits(beats: number): Hertz {
return 1 / super._beatsToUnits(beats);
protected _beatsToUnits(beats: number): Type {
return 1 / super._beatsToUnits(beats) as Type;
}
/**
* Returns the value of a second in the current units
*/
protected _secondsToUnits(seconds: Seconds): Hertz {
return 1 / seconds;
protected _secondsToUnits(seconds: Seconds): Type {
return 1 / seconds as Type;
}
/**
@ -198,7 +200,7 @@ export class FrequencyClass extends TimeClass<Hertz> {
* FrequencyClass.mtof(69); // returns 440
*/
static mtof(midi: MidiNote): Hertz {
return FrequencyClass.A4 * Math.pow(2, (midi - 69) / 12);
return mtof(midi);
}
/**
@ -239,6 +241,9 @@ const noteToScaleIndex = {
*/
const scaleIndexToNote = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
export function Frequency(value, units?): FrequencyClass {
/**
* Convert a value into a FrequencyClass object.
*/
export function Frequency(value?: TimeValue | Frequency, units?: FrequencyUnit): FrequencyClass {
return new FrequencyClass(getContext(), value, units);
}

137
Tone/core/type/Midi.test.ts Normal file
View file

@ -0,0 +1,137 @@
import { expect } from "chai";
import teoria from "teoria";
import { BasicTests } from "test/helper/Basic";
import { Offline } from "test/helper/Offline";
import { Frequency } from "./Frequency";
import { Midi, MidiClass } from "./Midi";
import { Ticks } from "./Ticks";
import { Time } from "./Time";
import { TransportTime } from "./TransportTime";
describe("MidiClass", () => {
BasicTests(MidiClass);
context("Constructor", () => {
it("can pass in a number in the constructor", () => {
const midi = Midi(1);
expect(midi).to.be.instanceOf(MidiClass);
midi.dispose();
});
it("can pass in a string in the constructor", () => {
const midi = Midi("1");
expect(midi).to.be.instanceOf(MidiClass);
midi.dispose();
});
it("can pass in a value and a type", () => {
expect(Midi(128, "n").valueOf()).to.equal(36);
});
it("with no arguments evaluates to 0", () => {
expect(Midi().valueOf()).to.equal(0);
});
it("is evaluated in equations and comparisons using valueOf", () => {
// @ts-ignore
expect(Midi(1) + 1).to.equal(2);
// @ts-ignore
expect(Midi(1) + Midi(1)).to.equal(2);
// @ts-ignore
expect(Midi(1) > Midi(0)).to.be.true;
// @ts-ignore
expect(+Midi(1)).to.equal(1);
});
it("can convert from seconds", () => {
expect(Midi("0.1s").valueOf()).to.equal(3);
expect(Midi("0.05s").valueOf()).to.equal(15);
expect(Midi(0.05, "s").valueOf()).to.equal(15);
});
it("can convert from hertz", () => {
expect(Midi("440hz").valueOf()).to.equal(69);
expect(Midi(220, "hz").valueOf()).to.equal(57);
});
it("can convert from ticks", () => {
expect(Midi("1i").valueOf()).to.equal(67);
expect(Midi(2, "i").valueOf()).to.equal(55);
});
it("can convert from Time", () => {
expect(Midi(Time(0.01)).valueOf()).to.equal(43);
expect(Midi(Time("128n")).valueOf()).to.equal(36);
expect(Midi(Time(128, "n")).valueOf()).to.equal(36);
});
it("can convert from Midi", () => {
expect(Midi(Midi(2)).valueOf()).to.equal(2);
expect(Midi(Midi("64n")).valueOf()).to.equal(24);
expect(Midi(Midi(64, "n")).valueOf()).to.equal(24);
});
it("can convert from Frequency", () => {
expect(Midi(Frequency("C4")).valueOf()).to.equal(60);
expect(Midi(Frequency("64n")).valueOf()).to.equal(24);
expect(Midi(Frequency(64, "n")).valueOf()).to.equal(24);
});
it("can convert from TransportTime", () => {
expect(Midi(TransportTime(0.01)).valueOf()).to.equal(43);
expect(Midi(TransportTime("256n")).valueOf()).to.equal(48);
});
it("can convert from Ticks", () => {
return Offline(({transport}) => {
expect(Midi(Ticks(transport.PPQ)).valueOf()).to.equal(-24);
expect(Midi(Ticks("4n")).valueOf()).to.equal(-24);
});
});
});
context("Conversions", () => {
it("can convert frequencies into notes", () => {
expect(Midi(48).toNote()).to.equal(teoria.Note.fromMIDI(48).scientific());
expect(Midi(90).toNote()).to.equal(teoria.Note.fromMIDI(90).scientific());
expect(Midi("C#4").toNote()).to.equal("C#4");
});
it("can convert note to midi values", () => {
expect(Midi("C4").toMidi()).to.equal(teoria.note("C4").midi());
expect(Midi("C#0").toMidi()).to.equal(teoria.note("C#0").midi());
expect(Midi("A-4").toMidi()).to.equal(teoria.note("A-4").midi());
});
it("can convert midi to frequency", () => {
expect(Midi(60).toFrequency()).to.equal(teoria.Note.fromMIDI(60).fq());
expect(Midi(25).toFrequency()).to.equal(teoria.Note.fromMIDI(25).fq());
expect(Midi(108).toFrequency()).to.equal(teoria.Note.fromMIDI(108).fq());
});
});
context("transpose/harmonize", () => {
it("can transpose a value", () => {
expect(Midi("A4").transpose(3).toMidi()).to.equal(72);
expect(Midi("A4").transpose(-3).toMidi()).to.equal(66);
expect(Midi(69).transpose(-12).valueOf()).to.equal(57);
});
it("can harmonize a value", () => {
expect(Midi("A4").harmonize([0, 3])).to.be.an("array");
expect(Midi("A4").harmonize([0, 3]).length).to.equal(2);
expect(Midi("A4").harmonize([0, 3])[0].toNote()).to.equal("A4");
expect(Midi("A4").harmonize([0, 3])[1].toNote()).to.equal("C5");
expect(Midi("A4").harmonize([-12, 0, 12])).to.be.an("array");
expect(Midi("A4").harmonize([-12, 0, 12]).length).to.equal(3);
expect(Midi("A4").harmonize([-12, 0, 12])[0].toNote()).to.equal("A3");
expect(Midi("A4").harmonize([-12, 0, 12])[1].toNote()).to.equal("A4");
expect(Midi("A4").harmonize([-12, 0, 12])[2].toNote()).to.equal("A5");
});
});
});

87
Tone/core/type/Midi.ts Normal file
View file

@ -0,0 +1,87 @@
import { getContext } from "../Global";
import { ftom, mtof } from "./Conversions";
import { FrequencyClass, FrequencyUnit } from "./Frequency";
import { TimeValue } from "./TimeBase";
/**
* @class Midi is a primitive type for encoding Time values.
* Midi can be constructed with or without the `new` keyword. Midi can be passed
* into the parameter of any method which takes time as an argument.
* @constructor
* @extends {Tone.Frequency}
* @param {String|Number} val The time value.
* @param {String=} units The units of the value.
* @example
* var t = Midi("4n");//a quarter note
*/
export class MidiClass extends FrequencyClass<MidiNote> {
name = "Midi";
readonly defaultUnits = "midi";
/**
* Returns the value of a frequency in the current units
*/
protected _frequencyToUnits(freq: Hertz): MidiNote {
return ftom(super._frequencyToUnits(freq));
}
/**
* Returns the value of a tick in the current time units
*/
protected _ticksToUnits(ticks: Ticks): MidiNote {
return ftom(super._ticksToUnits(ticks));
}
/**
* Return the value of the beats in the current units
*/
protected _beatsToUnits(beats: number): MidiNote {
return ftom(super._beatsToUnits(beats));
}
/**
* Returns the value of a second in the current units
*/
protected _secondsToUnits(seconds: Seconds): MidiNote {
return ftom(super._secondsToUnits(seconds));
}
/**
* Return the value of the frequency as a MIDI note
* @return {MIDI}
* @example
* Midi(60).toMidi(); //60
*/
toMidi(): MidiNote {
return this.valueOf();
}
/**
* Return the value of the frequency as a MIDI note
* @return {MIDI}
* @example
* Midi(60).toFrequency(); //261.6255653005986
*/
toFrequency(): Hertz {
return mtof(this.toMidi());
}
/**
* Transposes the frequency by the given number of semitones.
* @return A new transposed MidiClass
* @example
* Midi("A4").transpose(3); //"C5"
*/
transpose(interval: Interval): MidiClass {
return new MidiClass(this.context, this.toMidi() + interval);
}
}
/**
* Convert a value into a FrequencyClass object.
*/
export function Midi(value?: TimeValue, units?: FrequencyUnit): MidiClass {
return new MidiClass(getContext(), value, units);
}

View file

@ -0,0 +1,190 @@
import { expect } from "chai";
import { BasicTests } from "test/helper/Basic";
import { atTime, Offline } from "test/helper/Offline";
import { getContext } from "../Global";
import { Frequency } from "./Frequency";
import { Ticks, TicksClass } from "./Ticks";
import { Time } from "./Time";
import { TransportTime } from "./TransportTime";
describe("TicksClass", () => {
BasicTests(Ticks);
context("Constructor", () => {
it("can be made with or without 'new'", () => {
const t0 = Ticks();
expect(t0).to.be.instanceOf(TicksClass);
t0.dispose();
const t1 = new TicksClass(getContext());
expect(t1).to.be.instanceOf(TicksClass);
t1.dispose();
});
it("can pass in a number in the constructor", () => {
return Offline(({transport}) => {
const time = Ticks(1);
expect(time).to.be.instanceOf(TicksClass);
expect(time.valueOf()).to.equal(1);
time.dispose();
});
});
it("can pass in a string in the constructor", () => {
return Offline(({transport}) => {
const time = Ticks("1");
expect(time).to.be.instanceOf(TicksClass);
expect(time.valueOf()).to.equal(1);
time.dispose();
});
});
it("can pass in a value and a type", () => {
return Offline(({transport}) => {
expect(Ticks(4, "m").valueOf()).to.equal(transport.PPQ * 16);
});
});
it("with no arguments evaluates to 0 when the transport is stopped", () => {
return Offline(() => {
expect(Ticks().valueOf()).to.equal(0);
});
});
it("with no arguments evaluates to the current ticks when the transport is started", () => {
return Offline((context) => {
context.transport.start();
return atTime(0.29, () => {
expect(new TicksClass(context).valueOf()).to.equal(context.transport.ticks);
context.transport.stop();
});
}, 0.3);
});
it("is evaluated in equations and comparisons using valueOf", () => {
// @ts-ignore
expect(Ticks("1i") + 1).to.equal(2);
// @ts-ignore
expect(Ticks("1i") + Ticks("1i")).to.equal(2);
expect(Ticks("1i") > Ticks(0)).to.be.true;
expect(+Ticks("1i")).to.equal(1);
});
it("can convert from Time", () => {
return Offline(({transport}) => {
expect(Ticks(Time(2)).valueOf()).to.equal(transport.PPQ * 4);
expect(Ticks(Time("4n")).valueOf()).to.equal(transport.PPQ);
expect(Ticks(Time(4, "n")).valueOf()).to.equal(transport.PPQ);
});
});
it("can convert from Frequency", () => {
return Offline(({transport}) => {
expect(Ticks(Frequency(2)).valueOf()).to.equal(transport.PPQ);
expect(Ticks(Frequency("4n")).valueOf()).to.equal(transport.PPQ);
expect(Ticks(Frequency(4, "n")).valueOf()).to.equal(transport.PPQ);
});
});
it("can convert from TransportTime", () => {
return Offline(({transport}) => {
expect(Ticks(TransportTime(2)).valueOf()).to.equal(transport.PPQ * 4);
expect(Ticks(TransportTime("4n")).valueOf()).to.equal(transport.PPQ);
});
});
it("can convert from Ticks", () => {
return Offline(({transport}) => {
expect(Ticks(Ticks(transport.PPQ)).valueOf()).to.equal(transport.PPQ);
expect(Ticks(Ticks("4n")).valueOf()).to.equal(transport.PPQ);
});
});
it("can convert from an Object", () => {
return Offline(({transport}) => {
expect(Ticks({ "4n" : 2 }).valueOf()).to.equal(transport.PPQ * 2);
expect(Ticks({ "1n" : 1, "8t" : 2 }).valueOf()).to.equal(transport.PPQ * 4 + transport.PPQ * (2 / 3));
});
});
});
context("Quantizes values", () => {
it("can quantize values", () => {
return Offline(({transport}) => {
expect(Ticks("4t").quantize("4n").valueOf()).to.be.closeTo(transport.PPQ, 0.01);
});
});
it("can get the next subdivison when the transport is started", () => {
return Offline((context) => {
const transport = context.transport;
transport.start();
return atTime(0.59, () => {
expect(new TicksClass(context, "@1m").valueOf()).to.be.closeTo(4 * transport.PPQ, 1);
expect(new TicksClass(context, "@4n").valueOf()).to.be.closeTo(transport.PPQ * 2, 1);
});
}, 0.6);
});
});
context("Operators", () => {
it("can add the current time", () => {
return Offline((context) => {
context.transport.start();
return atTime(0.59, () => {
const now = context.transport.ticks;
expect(new TicksClass(context, "+4i").valueOf()).to.be.closeTo(4 + now, 0.01);
expect(new TicksClass(context, "+2n").valueOf()).to.be.closeTo(context.transport.PPQ * 2 + now, 0.01);
expect(new TicksClass(context, "+2n").valueOf()).to.be.closeTo(context.transport.PPQ * 2 + now, 0.01);
context.transport.stop();
});
}, 0.6);
});
});
context("Conversions", () => {
it("converts time into notation", () => {
return Offline(({transport}) => {
transport.bpm.value = 120;
transport.timeSignature = 4;
expect(Ticks("4n").toNotation()).to.equal("4n");
// expect(Ticks(1.5 * transport.PPQ).toNotation()).to.equal("2n + 4n");
expect(Ticks(0).toNotation()).to.equal("0");
// expect(Ticks("1:2:3").toNotation()).to.equal("1m + 2n + 8n + 16n");
});
});
it("converts time into samples", () => {
return Offline(({transport}) => {
expect(Ticks(transport.PPQ).toSamples()).to.equal(0.5 * getContext().sampleRate);
});
});
it("converts time into frequency", () => {
return Offline(({transport}) => {
expect(Ticks(transport.PPQ * 4).toFrequency()).to.equal(0.5);
expect(Ticks("2n").toFrequency()).to.equal(1);
});
});
it("converts time into seconds", () => {
return Offline(() => {
expect(Ticks("2n").toSeconds()).to.equal(1);
});
});
it("converts time into BarsBeatsSixteenths", () => {
return Offline(({transport}) => {
expect(Ticks("3:1:3").toBarsBeatsSixteenths()).to.equal("3:1:3");
expect(Ticks(4 * transport.PPQ).toBarsBeatsSixteenths()).to.equal("1:0:0");
});
});
});
});

View file

@ -1,7 +1,6 @@
import { Context } from "../context/Context";
import { getContext } from "../Global";
import { TimeBaseUnit, TimeValue } from "./TimeBase";
import { TransportTimeClass } from "./TransportTime";
import { TypeBaseUnits } from "./TypeBase";
/**
* Ticks is a primitive type for encoding Time values.
@ -14,7 +13,7 @@ export class TicksClass extends TransportTimeClass<Ticks> {
name = "Ticks";
readonly defaultUnits: TypeBaseUnits = "i";
readonly defaultUnits: TimeBaseUnit = "i";
/**
* Get the current time in the given units
@ -59,6 +58,6 @@ export class TicksClass extends TransportTimeClass<Ticks> {
}
}
export function Ticks(value: Time, units?: TypeBaseUnits): TicksClass {
export function Ticks(value?: TimeValue, units?: TimeBaseUnit): TicksClass {
return new TicksClass(getContext(), value, units);
}

View file

@ -1,9 +1,11 @@
import { expect } from "chai";
import { BasicTests } from "test/helper/Basic";
import { Offline } from "test/helper/Offline";
import { atTime, Offline } from "test/helper/Offline";
import { getContext } from "../Global";
import { Tone } from "../Tone";
import { Frequency } from "./Frequency";
import { Ticks } from "./Ticks";
import { Time, TimeClass } from "./Time";
import { TransportTime } from "./TransportTime";
describe("TimeClass", () => {
@ -51,26 +53,26 @@ describe("TimeClass", () => {
});
it("can convert from Time", () => {
// expect(Time(Time(2)).valueOf()).to.equal(2);
expect(Time(Time(2)).valueOf()).to.equal(2);
expect(Time("4n").valueOf()).to.equal(0.5);
});
// it("can convert from Frequency", () => {
// expect(Time(Frequency(2)).valueOf()).to.equal(0.5);
// expect(Time(Frequency("4n")).valueOf()).to.equal(0.5);
// });
it("can convert from Frequency", () => {
expect(Time(Frequency(2)).valueOf()).to.equal(0.5);
expect(Time(Frequency("4n")).valueOf()).to.equal(0.5);
});
// it("can convert from TransportTime", () => {
// expect(Time(TransportTime(2)).valueOf()).to.equal(2);
// expect(Time(TransportTime("4n")).valueOf()).to.equal(0.5);
// });
it("can convert from TransportTime", () => {
expect(Time(TransportTime(2)).valueOf()).to.equal(2);
expect(Time(TransportTime("4n")).valueOf()).to.equal(0.5);
});
// it("can convert from Ticks", () => {
// return Offline(function(Transport) {
// expect(Time(Ticks(Transport.PPQ)).valueOf()).to.equal(0.5);
// expect(Time(Ticks("4n")).valueOf()).to.equal(0.5);
// });
// });
it("can convert from Ticks", () => {
return Offline(({ transport}) => {
expect(Time(Ticks(transport.PPQ)).valueOf()).to.equal(0.5);
expect(Time(Ticks("4n")).valueOf()).to.equal(0.5);
});
});
it("evalutes objects", () => {
return Offline(() => {
@ -96,16 +98,17 @@ describe("TimeClass", () => {
expect(Time(2).quantize(8, 0.75).valueOf()).to.equal(0.5);
});
// it("can get the next subdivison when the transport is started", () => {
// return Offline(function(Transport) {
// Transport.start(0.1);
// return Test.atTime(0.69, () => {
// expect(Time("@1m").valueOf()).to.be.closeTo(2.1, 0.01);
// expect(Time("@4n").valueOf()).to.be.closeTo(1.1, 0.01);
// expect(Time("@8n").valueOf()).to.be.closeTo(0.85, 0.01);
// });
// }, 0.7);
// });
it("can get the next subdivison when the transport is started", () => {
return Offline((context) => {
const transport = context.transport;
transport.start(0.1);
return atTime(0.69, () => {
expect(new TimeClass(context, "@1m").valueOf()).to.be.closeTo(2.1, 0.01);
expect(new TimeClass(context, "@4n").valueOf()).to.be.closeTo(1.1, 0.01);
expect(new TimeClass(context, "@8n").valueOf()).to.be.closeTo(0.85, 0.01);
});
}, 0.7);
});
});
context("Operators", () => {
@ -148,30 +151,30 @@ describe("TimeClass", () => {
expect(Time(2).toFrequency()).to.equal(0.5);
});
// it("converts time into ticks", () => {
// return Offline(function(Transport) {
// expect(Time("2n").toTicks()).to.equal(2 * Transport.PPQ);
// // floating point checks
// const bpmOrig = Tone.Transport.bpm.value;
// Tone.Transport.bpm.value = 100;
// expect(Time("0:1:3").toTicks()).to.equal(1.75 * Transport.PPQ);
// Tone.Transport.bpm.value = bpmOrig;
// });
// });
it("converts time into ticks", () => {
return Offline(({ transport }) => {
expect(Time("2n").toTicks()).to.equal(2 * transport.PPQ);
// floating point checks
const bpmOrig = transport.bpm.value;
transport.bpm.value = 100;
expect(Time("0:1:3").toTicks()).to.equal(1.75 * transport.PPQ);
transport.bpm.value = bpmOrig;
});
});
// it("converts time into BarsBeatsSixteenths", () => {
// return Offline(function(Transport) {
// expect(Time("3:1:3").toBarsBeatsSixteenths()).to.equal("3:1:3");
// expect(Time(2).toBarsBeatsSixteenths()).to.equal("1:0:0");
// // trailing zero removal test
// Transport.bpm.value = 100;
// expect(Time("0:1:3").toBarsBeatsSixteenths()).to.equal("0:1:3");
// expect(Time("14:0:0").toBarsBeatsSixteenths()).to.equal("14:0:0");
// expect(Time("15:0:0").toBarsBeatsSixteenths()).to.equal("15:0:0");
// Transport.bpm.value = 90;
// expect(Time("100:0:0").toBarsBeatsSixteenths()).to.equal("100:0:0");
// });
// });
it("converts time into BarsBeatsSixteenths", () => {
return Offline(({ transport }) => {
expect(Time("3:1:3").toBarsBeatsSixteenths()).to.equal("3:1:3");
expect(Time(2).toBarsBeatsSixteenths()).to.equal("1:0:0");
// trailing zero removal test
transport.bpm.value = 100;
expect(Time("0:1:3").toBarsBeatsSixteenths()).to.equal("0:1:3");
expect(Time("14:0:0").toBarsBeatsSixteenths()).to.equal("14:0:0");
expect(Time("15:0:0").toBarsBeatsSixteenths()).to.equal("15:0:0");
transport.bpm.value = 90;
expect(Time("100:0:0").toBarsBeatsSixteenths()).to.equal("100:0:0");
});
});
});

View file

@ -1,6 +1,6 @@
import { getContext } from "../Global";
import { ftom } from "./Conversions";
import { TypeBaseClass, TypeBaseExpression, TypeBaseUnits } from "./TypeBase";
import { TimeBaseClass, TimeBaseUnit, TimeExpression, TimeValue } from "./TimeBase";
/**
* TimeClass is a primitive type for encoding and decoding Time values.
@ -10,15 +10,16 @@ import { TypeBaseClass, TypeBaseExpression, TypeBaseUnits } from "./TypeBase";
* @example
* var t = Time("4n");//a quarter note
*/
export class TimeClass<Type extends Seconds | Ticks = Seconds> extends TypeBaseClass<Type> {
export class TimeClass<Type extends Seconds | Ticks = Seconds, Unit extends string = TimeBaseUnit>
extends TimeBaseClass<Type, Unit> {
name = "Time";
protected _getExpressions(defaultUnit): TypeBaseExpression<Type> {
return Object.assign(super._getExpressions(defaultUnit), {
protected _getExpressions(): TimeExpression<Type> {
return Object.assign(super._getExpressions(), {
now: {
method: (capture: string): Type => {
return this._now() + new TimeClass(this.context, capture).valueOf() as Type;
return this._now() + new (this.constructor as typeof TimeClass)(this.context, capture).valueOf() as Type;
},
regexp: /^\+(.+)/,
},
@ -42,8 +43,8 @@ export class TimeClass<Type extends Seconds | Ticks = Seconds> extends TypeBaseC
* Time(21).quantize(2) //returns 22
* Time(0.6).quantize("4n", 0.5) //returns 0.55
*/
quantize(subdiv: number | string | TimeObject, percent = 1): Type {
const subdivision = new TimeClass(this.context, subdiv).valueOf();
quantize(subdiv: number | Subdivision | TimeObject, percent = 1): Type {
const subdivision = new (this.constructor as typeof TimeClass)(this.context, subdiv).valueOf();
const value = this.valueOf();
const multiple = Math.round(value / subdivision);
const ideal = multiple * subdivision;
@ -132,6 +133,11 @@ export class TimeClass<Type extends Seconds | Ticks = Seconds> extends TypeBaseC
}
}
export function Time(value?: Time, units?: TypeBaseUnits): TimeClass {
/**
* Create a TimeClass from a time string or number.
* @param value A value which reprsents time
* @param units The value's units if they can't be inferred by the value.
*/
export function Time(value?: TimeValue, units?: TimeBaseUnit): TimeClass<Seconds> {
return new TimeClass(getContext(), value, units);
}

View file

@ -1,75 +1,74 @@
import { Tone } from "../../core/Tone";
import { Context } from "../context/Context";
import { getContext } from "../Global";
import { Tone } from "../Tone";
import { isDefined, isObject , isString, isUndef } from "../util/TypeCheck";
interface TypeBaseClassOptions {
value?: TypeBaseClassValue;
units?: TypeBaseUnits;
context: Context;
}
type TypeBaseClassValue = string | number | TimeObject | TypeBaseClass<any>;
export type TimeValue = Time | TimeBaseClass<any, any>;
/**
* TypeBase is a flexible encoding of time which can be evaluated to and from a string.
* The units that the TimeBase can accept. extended by other classes
*/
export type TimeBaseUnit = "s" | "n" | "t" | "m" | "i" | "hz" | "tr" | "samples" | "number";
export interface TypeFunction {
regexp: RegExp;
method: (value: string, ...args: string[]) => number;
}
export interface TimeExpression<Type extends number> {
[key: string]: {
regexp: RegExp;
method: (value: string, ...args: string[]) => Type;
};
}
/**
* TimeBase is a flexible encoding of time which can be evaluated to and from a string.
* @param val The time value as a number, string or object
* @param units Unit values
* @example
* new TypeBase(4, "n")
* new TypeBase(2, "t")
* new TypeBase("2t")
* new TypeBase({"2t" : 2})
* new TypeBase("2t") + new TypeBase("4n");
* new TimeBase(4, "n")
* new TimeBase(2, "t")
* new TimeBase("2t")
* new TimeBase({"2t" : 2})
* new TimeBase("2t") + new TimeBase("4n");
*/
export abstract class TypeBaseClass<Type extends Seconds | Hertz | Ticks> extends Tone {
export abstract class TimeBaseClass<Type extends number, Unit extends string> extends Tone {
readonly context: Context;
/**
* The value of the units
*/
protected _val?: TypeBaseClassValue;
protected _val?: TimeValue;
/**
* The units of time
*/
protected _units?: TypeBaseUnits;
protected _units?: Unit;
/**
* All of the conversion expressions
*/
protected _expressions: TypeBaseExpression<Type>;
protected _expressions: TimeExpression<Type>;
/**
* The default units
*/
readonly defaultUnits: TypeBaseUnits = "s";
readonly defaultUnits: Unit = "s" as Unit;
constructor(context: Context, value?: TypeBaseClassValue, units?: TypeBaseUnits) {
constructor(context: Context, value?: TimeValue, units?: Unit) {
super();
this._val = value;
this._units = units;
this.context = context;
this._expressions = this._getExpressions(this.defaultUnits);
if (value instanceof TypeBaseClass) {
this.fromType(value);
}
}
static getDefaults(): TypeBaseClassOptions {
return {
context : getContext(),
};
this._expressions = this._getExpressions();
}
/**
* All of the time encoding expressions
*/
protected _getExpressions(defaultUnit: TypeBaseUnits): TypeBaseExpression<Type> {
protected _getExpressions(): TimeExpression<Type> {
return {
hz: {
method: (value) => {
@ -103,7 +102,7 @@ export abstract class TypeBaseClass<Type extends Seconds | Hertz | Ticks> extend
},
number: {
method: (value) => {
return this._expressions[defaultUnit].method.call(this, value);
return this._expressions[this.defaultUnits].method.call(this, value);
},
regexp: /^(\d+(?:\.\d+)?)$/,
},
@ -153,12 +152,15 @@ export abstract class TypeBaseClass<Type extends Seconds | Hertz | Ticks> extend
* Evaluate the time value. Returns the time in seconds.
*/
valueOf(): Type {
if (this._val instanceof TimeBaseClass) {
this.fromType(this._val);
}
if (isUndef(this._val)) {
return this._noArg();
} else if (isString(this._val) && isUndef(this._units)) {
for (const units in this._expressions) {
if (this._expressions[units].regexp.test(this._val.trim())) {
this._units = units as TypeBaseUnits;
this._units = units as Unit;
break;
}
}
@ -267,7 +269,7 @@ export abstract class TypeBaseClass<Type extends Seconds | Hertz | Ticks> extend
* Coerce a time type into this units type.
* @param type Any time type units
*/
fromType(type: TypeBaseClass<any>): void {
fromType(type: TimeBaseClass<any, any>): this {
this._units = undefined;
switch (this.defaultUnits) {
case "s":
@ -279,7 +281,11 @@ export abstract class TypeBaseClass<Type extends Seconds | Hertz | Ticks> extend
case "hz":
this._val = type.toFrequency();
break;
case "midi":
this._val = type.toMidi();
break;
}
return this;
}
/**
@ -327,17 +333,3 @@ export abstract class TypeBaseClass<Type extends Seconds | Hertz | Ticks> extend
return this;
}
}
/**
* The units that the TypeBase can accept. extended by other classes
*/
export type TypeBaseUnits = "s" | "n" | "t" | "m" | "i" | "hz" | "tr" | "samples" | "number";
/**
* The format of the type conversion expressions
*/
export type TypeBaseExpression<T> = {
[key in TypeBaseUnits]: {
regexp: RegExp;
method: (value: string, ...args: string[]) => T;
};
};

View file

@ -0,0 +1,184 @@
import { expect } from "chai";
import { BasicTests } from "test/helper/Basic";
import { atTime, Offline } from "test/helper/Offline";
import { getContext } from "../Global";
import { Frequency } from "./Frequency";
import { Ticks } from "./Ticks";
import { Time } from "./Time";
import { TransportTime, TransportTimeClass } from "./TransportTime";
describe("TransportTimeClass", () => {
BasicTests(TransportTime);
context("Constructor", () => {
it("can be made with or without 'new'", () => {
const t0 = TransportTime();
expect(t0).to.be.instanceOf(TransportTimeClass);
t0.dispose();
const t1 = new TransportTimeClass(getContext());
expect(t1).to.be.instanceOf(TransportTimeClass);
t1.dispose();
});
it("can pass in a number in the constructor", () => {
return Offline(() => {
const time = TransportTime(1);
expect(time).to.be.instanceOf(TransportTimeClass);
expect(time.valueOf()).to.equal(1);
time.dispose();
});
});
it("can pass in a string in the constructor", () => {
return Offline((context) => {
const time = TransportTime("1");
expect(time).to.be.instanceOf(TransportTimeClass);
expect(time.valueOf()).to.equal(1);
time.dispose();
});
});
it("can pass in a value and a type", () => {
return Offline((context) => {
expect(TransportTime(4, "m").valueOf()).to.equal(8);
});
});
it("with no arguments evaluates to 0 when the transport is stopped", () => {
return Offline(() => {
expect(TransportTime().valueOf()).to.equal(0);
});
});
it("with no arguments evaluates to the current ticks when the transport is started", () => {
return Offline((context) => {
const transport = context.transport;
transport.start();
return atTime(0.29, () => {
expect(new TransportTimeClass(context).valueOf()).to.equal(transport.seconds);
transport.stop();
});
}, 0.3);
});
it("is evaluated in equations and comparisons using valueOf", () => {
// @ts-ignore
expect(TransportTime("1") + 1).to.equal(2);
// @ts-ignore
expect(TransportTime("1") + TransportTime("1")).to.equal(2);
expect(TransportTime("1") > TransportTime(0)).to.be.true;
expect(+TransportTime("1")).to.equal(1);
});
it("can convert from Time", () => {
expect(TransportTime(Time(2)).valueOf()).to.equal(2);
expect(TransportTime(Time("4n")).valueOf()).to.equal(0.5);
});
it("can convert from Frequency", () => {
expect(TransportTime(Frequency(2)).valueOf()).to.equal(0.5);
expect(TransportTime(Frequency("4n")).valueOf()).to.equal(0.5);
});
it("can convert from TransportTime", () => {
expect(TransportTime(TransportTime(2)).valueOf()).to.equal(2);
expect(TransportTime(TransportTime("4n")).valueOf()).to.equal(0.5);
});
it("can convert from Ticks", () => {
return Offline((context) => {
const transport = context.transport;
expect(TransportTime(Ticks(transport.PPQ)).valueOf()).to.equal(0.5);
expect(TransportTime(Ticks("4n")).valueOf()).to.equal(0.5);
});
});
it("can convert from an Object", () => {
return Offline(() => {
expect(TransportTime({ "4n" : 2 }).valueOf()).to.equal(1);
expect(TransportTime({ "1n" : 1, "8t" : 2 }).valueOf()).to.be.closeTo(2.333, 0.01);
});
});
});
context("Quantizes values", () => {
it("can quantize values", () => {
return Offline((context) => {
expect(TransportTime("4t").quantize("4n").valueOf()).to.be.closeTo(0.5, 0.01);
});
});
it("can get the next subdivison when the transport is started", () => {
return Offline((context) => {
const transport = context.transport;
transport.start();
return atTime(0.59, () => {
expect(new TransportTimeClass(context, "@1m").valueOf()).to.be.closeTo(2, 0.01);
expect(new TransportTimeClass(context, "@4n").valueOf()).to.be.closeTo(1, 0.01);
});
}, 0.6);
});
});
context("Operators", () => {
it("can add the current time", () => {
return Offline((context) => {
const transport = context.transport;
transport.start();
return atTime(0.59, () => {
const now = transport.seconds;
const quarterNote = 60 / transport.bpm.value;
expect(new TransportTimeClass(context, "+4i").valueOf()).to.be.closeTo(4 / transport.PPQ + now, 0.1);
expect(new TransportTimeClass(context, "+2n").valueOf()).to.be.closeTo(quarterNote * 2 + now, 0.1);
transport.stop();
});
}, 0.6);
});
});
context("Conversions", () => {
it("converts time into notation", () => {
return Offline((context) => {
const transport = context.transport;
transport.bpm.value = 120;
transport.timeSignature = 4;
expect(TransportTime("4n").toNotation()).to.equal("4n");
expect(TransportTime(1.5).toNotation()).to.equal("2n.");
expect(TransportTime(0).toNotation()).to.equal("0");
expect(TransportTime("1:0:0").toNotation()).to.equal("1m");
});
});
it("converts time into samples", () => {
return Offline((context) => {
expect(TransportTime(2).toSamples()).to.equal(2 * context.sampleRate);
});
});
it("converts time into frequency", () => {
return Offline(() => {
expect(TransportTime(2).toFrequency()).to.equal(0.5);
});
});
it("converts time into seconds", () => {
return Offline(() => {
expect(TransportTime("2n").toSeconds()).to.equal(1);
});
});
it("converts time into BarsBeatsSixteenths", () => {
return Offline(() => {
expect(TransportTime("3:1:3").toBarsBeatsSixteenths()).to.equal("3:1:3");
expect(TransportTime(2).toBarsBeatsSixteenths()).to.equal("1:0:0");
});
});
});
});

View file

@ -1,7 +1,6 @@
import { Context } from "../context/Context";
import { getContext } from "../Global";
import { TimeClass } from "./Time";
import { TypeBaseUnits } from "./TypeBase";
import { TimeBaseUnit, TimeValue } from "./TimeBase";
/**
* TransportTime is a the time along the Transport's
@ -21,6 +20,12 @@ export class TransportTimeClass<Type extends Seconds | Ticks = Seconds> extends
}
}
export function TransportTime(value: Time, units?: TypeBaseUnits): TransportTimeClass {
/**
* TransportTime is a the time along the Transport's
* timeline. It is similar to Tone.Time, but instead of evaluating
* against the AudioContext's clock, it is evaluated against
* the Transport's position. See [TransportTime wiki](https://github.com/Tonejs/Tone.js/wiki/TransportTime).
*/
export function TransportTime(value?: TimeValue, units?: TimeBaseUnit): TransportTimeClass {
return new TransportTimeClass(getContext(), value, units);
}

View file

@ -47,4 +47,3 @@ describe("Abs", () => {
});
});