diff --git a/Tone/core/type/Frequency.test.ts b/Tone/core/type/Frequency.test.ts new file mode 100644 index 00000000..b3593170 --- /dev/null +++ b/Tone/core/type/Frequency.test.ts @@ -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); + }); + }); + +}); diff --git a/Tone/core/type/Frequency.ts b/Tone/core/type/Frequency.ts index 9a420320..ed6a97cd 100644 --- a/Tone/core/type/Frequency.ts +++ b/Tone/core/type/Frequency.ts @@ -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 { +export class FrequencyClass extends TimeClass { 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 { // AUGMENT BASE EXPRESSIONS /////////////////////////////////////////////////////////////////////////// - protected _getExpressions(defaultUnit): TypeBaseExpression { - return Object.assign({}, super._getExpressions(defaultUnit), { + protected _getExpressions(): TimeExpression { + 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 { 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 { * Frequency("C4").toMidi(); //60 */ toMidi(): MidiNote { - return FrequencyClass.ftom(this.valueOf()); + return ftom(this.valueOf()); } /** @@ -158,36 +160,36 @@ export class FrequencyClass extends TimeClass { /** * 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 { * 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); } diff --git a/Tone/core/type/Midi.test.ts b/Tone/core/type/Midi.test.ts new file mode 100644 index 00000000..15e0073e --- /dev/null +++ b/Tone/core/type/Midi.test.ts @@ -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"); + }); + }); +}); diff --git a/Tone/core/type/Midi.ts b/Tone/core/type/Midi.ts new file mode 100644 index 00000000..8bae478b --- /dev/null +++ b/Tone/core/type/Midi.ts @@ -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 { + + 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); +} diff --git a/Tone/core/type/Ticks.test.ts b/Tone/core/type/Ticks.test.ts new file mode 100644 index 00000000..8d974a89 --- /dev/null +++ b/Tone/core/type/Ticks.test.ts @@ -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"); + }); + }); + + }); + +}); diff --git a/Tone/core/type/Ticks.ts b/Tone/core/type/Ticks.ts index 84061587..e52ec013 100644 --- a/Tone/core/type/Ticks.ts +++ b/Tone/core/type/Ticks.ts @@ -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 { 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 { } } -export function Ticks(value: Time, units?: TypeBaseUnits): TicksClass { +export function Ticks(value?: TimeValue, units?: TimeBaseUnit): TicksClass { return new TicksClass(getContext(), value, units); } diff --git a/Tone/core/type/Time.test.ts b/Tone/core/type/Time.test.ts index 6f66f40b..e5439588 100644 --- a/Tone/core/type/Time.test.ts +++ b/Tone/core/type/Time.test.ts @@ -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"); + }); + }); }); diff --git a/Tone/core/type/Time.ts b/Tone/core/type/Time.ts index e7759881..6adb7fc8 100644 --- a/Tone/core/type/Time.ts +++ b/Tone/core/type/Time.ts @@ -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 extends TypeBaseClass { +export class TimeClass +extends TimeBaseClass { name = "Time"; - protected _getExpressions(defaultUnit): TypeBaseExpression { - return Object.assign(super._getExpressions(defaultUnit), { + protected _getExpressions(): TimeExpression { + 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 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 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 { return new TimeClass(getContext(), value, units); } diff --git a/Tone/core/type/TypeBase.ts b/Tone/core/type/TimeBase.ts similarity index 80% rename from Tone/core/type/TypeBase.ts rename to Tone/core/type/TimeBase.ts index 2527d04c..6a149a3f 100644 --- a/Tone/core/type/TypeBase.ts +++ b/Tone/core/type/TimeBase.ts @@ -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; +export type TimeValue = Time | TimeBaseClass; /** - * 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 { + [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 extends Tone { +export abstract class TimeBaseClass 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; + protected _expressions: TimeExpression; /** * 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 { + protected _getExpressions(): TimeExpression { return { hz: { method: (value) => { @@ -103,7 +102,7 @@ export abstract class TypeBaseClass 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 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 extend * Coerce a time type into this units type. * @param type Any time type units */ - fromType(type: TypeBaseClass): void { + fromType(type: TimeBaseClass): this { this._units = undefined; switch (this.defaultUnits) { case "s": @@ -279,7 +281,11 @@ export abstract class TypeBaseClass 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 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 = { - [key in TypeBaseUnits]: { - regexp: RegExp; - method: (value: string, ...args: string[]) => T; - }; -}; diff --git a/Tone/core/type/TransportTime.test.ts b/Tone/core/type/TransportTime.test.ts new file mode 100644 index 00000000..f1ba0f5a --- /dev/null +++ b/Tone/core/type/TransportTime.test.ts @@ -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"); + }); + }); + + }); +}); diff --git a/Tone/core/type/TransportTime.ts b/Tone/core/type/TransportTime.ts index 444a7109..83f29f0d 100644 --- a/Tone/core/type/TransportTime.ts +++ b/Tone/core/type/TransportTime.ts @@ -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 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); } diff --git a/Tone/signal/Abs.test.ts b/Tone/signal/Abs.test.ts index 46b37199..30f82698 100644 --- a/Tone/signal/Abs.test.ts +++ b/Tone/signal/Abs.test.ts @@ -47,4 +47,3 @@ describe("Abs", () => { }); }); -