mirror of
https://github.com/Tonejs/Tone.js
synced 2024-11-16 00:27:58 +00:00
fully converting all Time classes to ts
This commit is contained in:
parent
4e11f13336
commit
4c4db4b614
12 changed files with 965 additions and 138 deletions
220
Tone/core/type/Frequency.test.ts
Normal file
220
Tone/core/type/Frequency.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -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
137
Tone/core/type/Midi.test.ts
Normal 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
87
Tone/core/type/Midi.ts
Normal 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);
|
||||
}
|
190
Tone/core/type/Ticks.test.ts
Normal file
190
Tone/core/type/Ticks.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
184
Tone/core/type/TransportTime.test.ts
Normal file
184
Tone/core/type/TransportTime.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -47,4 +47,3 @@ describe("Abs", () => {
|
|||
});
|
||||
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue