From 696e84cafb76b404155c30a8c494d8c94770385e Mon Sep 17 00:00:00 2001 From: Yotam Mann Date: Mon, 18 Apr 2016 00:34:16 -0400 Subject: [PATCH] new core timing primitives --- Tone/type/Frequency.js | 270 ++++++++++++++++++++ Tone/type/Time.js | 227 +++++++++++++++++ Tone/type/TimeBase.js | 502 +++++++++++++++++++++++++++++++++++++ Tone/type/TransportTime.js | 121 +++++++++ Tone/type/Type.js | 224 +++++++++++++++++ 5 files changed, 1344 insertions(+) create mode 100644 Tone/type/Frequency.js create mode 100644 Tone/type/Time.js create mode 100644 Tone/type/TimeBase.js create mode 100644 Tone/type/TransportTime.js create mode 100644 Tone/type/Type.js diff --git a/Tone/type/Frequency.js b/Tone/type/Frequency.js new file mode 100644 index 00000000..c2e958eb --- /dev/null +++ b/Tone/type/Frequency.js @@ -0,0 +1,270 @@ +define(["Tone/core/Tone", "Tone/type/TimeBase"], function (Tone) { + + /** + * @param {[type]} val [description] + * @param {[type]} units [description] + * @example + * Tone.Frequency("C3").eval() // 261 + * Tone.Frequency(38, "midi").eval() // + * Tone.Frequency("C3").transpose(4).eval(); + * Tone.Frequency("440hz").transpose([0, 3, 7]).eval() // ["A4", "C5", "E5"]; + */ + Tone.Frequency = function(val, units){ + if (this instanceof Tone.Frequency){ + + Tone.TimeBase.call(this, val, units); + + } else { + return new Tone.Frequency(val, units); + } + }; + + Tone.extend(Tone.Frequency, Tone.TimeBase); + + /////////////////////////////////////////////////////////////////////////// + // AUGMENT BASE EXPRESSIONS + /////////////////////////////////////////////////////////////////////////// + + //clone the expressions so that + //we can add more without modifying the original + Tone.Frequency.prototype._primaryExpressions = Object.create(Tone.TimeBase.prototype._primaryExpressions); + + /* + * midi type primary expression + * @type {Object} + * @private + */ + Tone.Frequency.prototype._primaryExpressions.midi = { + regexp : /^(\d+(?:\.\d+)?midi)/, + method : function(value){ + return this.midiToFrequency(value); + } + }; + + /* + * note type primary expression + * @type {Object} + * @private + */ + Tone.Frequency.prototype._primaryExpressions.note = { + regexp : /^([a-g]{1}(?:b|#|x|bb)?)(-?[0-9]+)/i, + method : function(pitch, octave){ + var index = noteToScaleIndex[pitch.toLowerCase()]; + var noteNumber = index + (parseInt(octave) + 1) * 12; + return this.midiToFrequency(noteNumber); + } + }; + + /* + * BeatsBarsSixteenths type primary expression + * @type {Object} + * @private + */ + Tone.Frequency.prototype._primaryExpressions.tr = { + regexp : /^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?):?(\d+(?:\.\d+)?)?/, + method : function(m, q, s){ + var total = 1; + if (m && m !== "0"){ + total *= this._beatsToUnits(this._timeSignature() * parseFloat(m)); + } + if (q && q !== "0"){ + total *= this._beatsToUnits(parseFloat(q)); + } + if (s && s !== "0"){ + total *= this._beatsToUnits(parseFloat(s) / 4); + } + return total; + } + }; + + /////////////////////////////////////////////////////////////////////////// + // EXPRESSIONS + /////////////////////////////////////////////////////////////////////////// + + /** + * Transposes the frequency by the given number of semitones. + * @param {Interval} interval + * @return {Tone.Frequency} this + * @example + * Tone.Frequency("A4").transpose(3); //"C5" + */ + Tone.Frequency.prototype.transpose = function(interval){ + this._expr = function(expr, interval){ + var val = expr(); + return val * this.intervalToFrequencyRatio(interval); + }.bind(this, this._expr, interval); + return this; + }; + + /** + * Takes an array of semitone intervals and returns + * an array of frequencies transposed by those intervals. + * @param {Array} intervals + * @return {Tone.Frequency} this + * @example + * Tone.Frequency("A4").harmonize([0, 3, 7]); //["A4", "C5", "E5"] + */ + Tone.Frequency.prototype.harmonize = function(intervals){ + this._expr = function(expr, intervals){ + var val = expr(); + var ret = []; + for (var i = 0; i < intervals.length; i++){ + ret[i] = val * this.intervalToFrequencyRatio(intervals[i]); + } + return ret; + }.bind(this, this._expr, intervals); + return this; + }; + + /////////////////////////////////////////////////////////////////////////// + // UNIT CONVERSIONS + /////////////////////////////////////////////////////////////////////////// + + /** + * Return the value of the frequency as a MIDI note + * @return {MIDI} + */ + Tone.Frequency.prototype.toMidi = function(){ + return this.frequencyToMidi(this.eval()); + }; + + /** + * Return the value of the frequency in Scientific Pitch Notation + * @return {Note} + */ + Tone.Frequency.prototype.toNote = function(){ + var freq = this.eval(); + var log = Math.log(freq / Tone.Frequency.A4) / Math.LN2; + var noteNumber = Math.round(12 * log) + 57; + var octave = Math.floor(noteNumber/12); + if(octave < 0){ + noteNumber += -12 * octave; + } + var noteName = scaleIndexToNote[noteNumber % 12]; + return noteName + octave.toString(); + }; + + /** + * Return the duration of one cycle in seconds. + * @return {Seconds} + */ + Tone.Frequency.prototype.toSeconds = function(){ + return 1 / this.eval(); + }; + + /** + * Return the duration of one cycle in ticks + * @return {Ticks} + */ + Tone.Frequency.prototype.toTicks = function(){ + var quarterTime = this._beatsToUnits(1); + var quarters = this.eval() / quarterTime; + return Math.floor(quarters * Tone.Transport.PPQ); + }; + + /////////////////////////////////////////////////////////////////////////// + // UNIT CONVERSIONS HELPERS + /////////////////////////////////////////////////////////////////////////// + + /** + * Returns the value of a frequency in the current units + * @param {Frequency} freq + * @return {Number} + * @private + */ + Tone.Frequency.prototype._frequencyToUnits = function(freq){ + return freq; + }; + + /** + * Returns the value of a tick in the current time units + * @param {Ticks} ticks + * @return {Number} + * @private + */ + Tone.Frequency.prototype._ticksToUnits = function(ticks){ + return 1 / ((ticks * 60) / (Tone.Transport.bpm.value * Tone.Transport.PPQ)); + }; + + /** + * Return the value of the beats in the current units + * @param {Number} beats + * @return {Number} + * @private + */ + Tone.Frequency.prototype._beatsToUnits = function(beats){ + return 1 / Tone.TimeBase.prototype._beatsToUnits.call(this, beats); + }; + + /** + * Returns the value of a second in the current units + * @param {Seconds} seconds + * @return {Number} + * @private + */ + Tone.Frequency.prototype._secondsToUnits = function(seconds){ + return 1 / seconds; + }; + + /** + * The default units if none are given. + * @private + */ + Tone.Frequency.prototype._defaultUnits = "hz"; + + /////////////////////////////////////////////////////////////////////////// + // FREQUENCY CONVERSIONS + /////////////////////////////////////////////////////////////////////////// + + /** + * Note to scale index + * @type {Object} + */ + var noteToScaleIndex = { + "cbb" : -2, "cb" : -1, "c" : 0, "c#" : 1, "cx" : 2, + "dbb" : 0, "db" : 1, "d" : 2, "d#" : 3, "dx" : 4, + "ebb" : 2, "eb" : 3, "e" : 4, "e#" : 5, "ex" : 6, + "fbb" : 3, "fb" : 4, "f" : 5, "f#" : 6, "fx" : 7, + "gbb" : 5, "gb" : 6, "g" : 7, "g#" : 8, "gx" : 9, + "abb" : 7, "ab" : 8, "a" : 9, "a#" : 10, "ax" : 11, + "bbb" : 9, "bb" : 10, "b" : 11, "b#" : 12, "bx" : 13, + }; + + /** + * scale index to note (sharps) + * @type {Array} + */ + var scaleIndexToNote = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; + + /** + * The [concert pitch](https://en.wikipedia.org/wiki/Concert_pitch) + * A4's values in Hertz. + * @type {Frequency} + * @static + */ + Tone.Frequency.A4 = 440; + + /** + * Convert a MIDI note to frequency value. + * @param {MIDI} midi The midi number to convert. + * @return {Frequency} the corresponding frequency value + * @example + * tone.midiToFrequency(69); // returns 440 + */ + Tone.Frequency.prototype.midiToFrequency = function(midi){ + return Tone.Frequency.A4 * Math.pow(2, (midi - 69) / 12); + }; + + /** + * Convert a frequency value to a MIDI note. + * @param {Frequency} frequency The value to frequency value to convert. + * @returns {MIDI} + * @example + * tone.midiToFrequency(440); // returns 69 + */ + Tone.Frequency.prototype.frequencyToMidi = function(frequency){ + return 69 + 12 * Math.log2(frequency / Tone.Frequency.A4); + }; + + return Tone.Frequency; +}); \ No newline at end of file diff --git a/Tone/type/Time.js b/Tone/type/Time.js new file mode 100644 index 00000000..b8ee60e9 --- /dev/null +++ b/Tone/type/Time.js @@ -0,0 +1,227 @@ +define(["Tone/core/Tone", "Tone/type/TimeBase"], function (Tone) { + + /** + * @param {[type]} val [description] + * @param {[type]} units [description] + */ + Tone.Time = function(val, units){ + if (this instanceof Tone.Time){ + + Tone.TimeBase.call(this, val, units); + + } else { + return new Tone.Time(val, units); + } + }; + + Tone.extend(Tone.Time, Tone.TimeBase); + + //clone the expressions so that + //we can add more without modifying the original + Tone.Time.prototype._unaryExpressions = Object.create(Tone.TimeBase.prototype._unaryExpressions); + + /* + * Adds an additional unary expression + * which quantizes values to the next subdivision + * @type {Object} + * @private + */ + Tone.Time.prototype._unaryExpressions.quantize = { + regexp : /^@/, + method : function(rh){ + return Tone.Transport.nextSubdivision(rh()); + } + }; + + /* + * Adds an additional unary expression + * which adds the current clock time. + * @type {Object} + * @private + */ + Tone.Time.prototype._unaryExpressions.now = { + regexp : /^\+/, + method : function(lh){ + return this.now() + lh(); + } + }; + + /** + * Quantize the time by the given subdivision. Optionally add a + * percentage which will move the time value towards the ideal + * quantized value by that percentage. + * @param {Number|Time} val The subdivision to quantize to + * @param {NormalRange} [perc=1] Move the time value + * towards the quantized value by + * a percentage. + * @return {Tone.Time} this + * @example + * Tone.Time(21).quantize(2).eval() //returns 22 + * Tone.Time(0.6).quantize("4n", 0.5).eval() //returns 0.55 + */ + Tone.Time.prototype.quantize = function(subdiv, perc){ + perc = this.defaultArg(perc, 1); + this._expr = function(expr, subdivision, percent){ + expr = expr(); + subdivision = subdivision.eval(); + var multiple = Math.round(expr / subdivision); + var ideal = multiple * subdivision; + var diff = ideal - expr; + return expr + diff * percent; + }.bind(this, this._expr, new this.constructor(subdiv), perc); + return this; + }; + + /** + * Adds the current clock time to the time expression + * @return {Tone.Time} this + */ + Tone.Time.prototype.addNow = function(){ + this._expr = function(expr){ + return expr() + this.now(); + }.bind(this, this._expr); + return this; + }; + + /** + * @override + * Override the default value return when no arguments are passed in. + * The default value is 'now' + * @private + */ + Tone.Time.prototype._defaultExpr = function(){ + return function(expr){ + return expr() + this.now(); + }.bind(this, this._expr); + }; + + + //CONVERSIONS////////////////////////////////////////////////////////////// + + /** + * Convert a Time to Notation. Values will be thresholded to the nearest 128th note. + * @return {Notation} + */ + Tone.Time.prototype.toNotation = function(){ + var time = this.eval(); + var testNotations = ["1m", "2n", "4n", "8n", "16n", "32n", "64n", "128n"]; + var retNotation = this._toNotationHelper(time, testNotations); + //try the same thing but with tripelets + var testTripletNotations = ["1m", "2n", "2t", "4n", "4t", "8n", "8t", "16n", "16t", "32n", "32t", "64n", "64t", "128n"]; + var retTripletNotation = this._toNotationHelper(time, testTripletNotations); + //choose the simpler expression of the two + if (retTripletNotation.split("+").length < retNotation.split("+").length){ + return retTripletNotation; + } else { + return retNotation; + } + }; + + /** + * Helper method for Tone.toNotation + * @param {Number} units + * @param {Array} testNotations + * @return {String} + * @private + */ + Tone.Time.prototype._toNotationHelper = function(units, testNotations){ + //the threshold is the last value in the array + var threshold = this._notationToUnits(testNotations[testNotations.length - 1]); + var retNotation = ""; + for (var i = 0; i < testNotations.length; i++){ + var notationTime = this._notationToUnits(testNotations[i]); + //account for floating point errors (i.e. round up if the value is 0.999999) + var multiple = units / notationTime; + var floatingPointError = 0.000001; + if (1 - multiple % 1 < floatingPointError){ + multiple += floatingPointError; + } + multiple = Math.floor(multiple); + if (multiple > 0){ + if (multiple === 1){ + retNotation += testNotations[i]; + } else { + retNotation += multiple.toString() + "*" + testNotations[i]; + } + units -= multiple * notationTime; + if (units < threshold){ + break; + } else { + retNotation += " + "; + } + } + } + if (retNotation === ""){ + retNotation = "0"; + } + return retNotation; + }; + + /** + * Convert a notation value to the current units + * @param {Notation} notation + * @return {Number} + * @private + */ + Tone.Time.prototype._notationToUnits = function(notation){ + var primaryExprs = this._primaryExpressions; + var notationExprs = [primaryExprs.n, primaryExprs.t, primaryExprs.m]; + for (var i = 0; i < notationExprs.length; i++){ + var expr = notationExprs[i]; + var match = notation.match(expr.regexp); + if (match){ + return expr.method.call(this, match[1]); + } + } + }; + + /** + * Return the time encoded as Bars:Beats:Sixteenths. + * @return {BarsBeatsSixteenths} + */ + Tone.Time.prototype.toBarsBeatsSixteenths = function(){ + var quarterTime = this._beatsToUnits(1); + var quarters = this.eval() / quarterTime; + var measures = Math.floor(quarters / this._timeSignature()); + var sixteenths = (quarters % 1) * 4; + quarters = Math.floor(quarters) % this._timeSignature(); + var progress = [measures, quarters, sixteenths]; + return progress.join(":"); + }; + + /** + * Return the time in ticks. + * @return {Ticks} + */ + Tone.Time.prototype.toTicks = function(){ + var quarterTime = this._beatsToUnits(1); + var quarters = this.eval() / quarterTime; + return Math.floor(quarters * Tone.Transport.PPQ); + }; + + /** + * Return the time in samples + * @return {Samples} + */ + Tone.Time.prototype.toSamples = function(){ + return this.eval() * this.context.sampleRate; + }; + + /** + * Return the time as a frequency value + * @return {Frequency} + */ + Tone.Time.prototype.toFrequency = function(){ + return 1/this.eval(); + }; + + /** + * Return the time in seconds. + * @return {Seconds} + */ + Tone.Time.prototype.toSeconds = function(){ + return this.eval(); + }; + + return Tone.Time; +}); \ No newline at end of file diff --git a/Tone/type/TimeBase.js b/Tone/type/TimeBase.js new file mode 100644 index 00000000..319d76f0 --- /dev/null +++ b/Tone/type/TimeBase.js @@ -0,0 +1,502 @@ +define(["Tone/core/Tone"], function (Tone) { + + /** + * @class Tone.TimeBase is a flexible encoding of time + * which can be evaluated to and from a string. + * Parsing code modified from https://code.google.com/p/tapdigit/ + * Copyright 2011 2012 Ariya Hidayat, New BSD License + * @extends {Tone} + * @param {Time} val The time value as a number or string + * @param {String=} units Unit values + * @example + * Tone.TimeBase(4, "n") + * Tone.TimeBase(2, "t") + * Tone.TimeBase("2t").add("1m") + * Tone.TimeBase("2t + 1m"); + */ + Tone.TimeBase = function(val, units){ + + //allows it to be constructed with or without 'new' + if (this instanceof Tone.TimeBase) { + + /** + * Any expressions parsed from the Time + * @type {Array} + * @private + */ + this._expr = this._noOp; + + //default units + units = this.defaultArg(units, this._defaultUnits); + + //get the value from the given time + if (this.isString(val)){ + this._expr = this._parseExprString(val); + } else if (this.isNumber(val)){ + var method = this._primaryExpressions[units].method; + this._expr = method.bind(this, val); + } else if (this.isUndef(val)){ + //default expression + this._expr = this._defaultExpr(); + } + } else { + + return new Tone.TimeBase(val, units); + } + }; + + Tone.extend(Tone.TimeBase); + + /** + * Repalce the current time value with the value + * given by the expression string. + * @param {String} exprString + * @return {Tone.TimeBase} this + */ + Tone.TimeBase.prototype.set = function(exprString){ + this._expr = this._parseExprString(exprString); + return this; + }; + + /////////////////////////////////////////////////////////////////////////// + // ABSTRACT SYNTAX TREE PARSER + /////////////////////////////////////////////////////////////////////////// + + Tone.TimeBase.prototype._primaryExpressions = { + "n" : { + regexp : /^(\d+)n/i, + method : function(value){ + value = parseInt(value); + if (value === 1){ + return this._beatsToUnits(this._timeSignature()); + } else { + return this._beatsToUnits(4 / value); + } + } + }, + "t" : { + regexp : /^(\d+)t/i, + method : function(value){ + value = parseInt(value); + return this._beatsToUnits(8 / (parseInt(value) * 3)); + } + }, + "m" : { + regexp : /^(\d+)m/i, + method : function(value){ + return this._beatsToUnits(parseInt(value) * this._timeSignature()); + } + }, + "i" : { + regexp : /^(\d+)i/i, + method : function(value){ + return this._ticksToUnits(parseInt(value)); + } + }, + "hz" : { + regexp : /^(\d+(?:\.\d+)?)hz/i, + method : function(value){ + return this._frequencyToUnits(parseFloat(value)); + } + }, + "tr" : { + regexp : /^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?):?(\d+(?:\.\d+)?)?/, + method : function(m, q, s){ + var total = 0; + if (m && m !== "0"){ + total += this._beatsToUnits(this._timeSignature() * parseFloat(m)); + } + if (q && q !== "0"){ + total += this._beatsToUnits(parseFloat(q)); + } + if (s && s !== "0"){ + total += this._beatsToUnits(parseFloat(s) / 4); + } + return total; + } + }, + "s" : { + regexp : /^(\d+(?:\.\d+)?s)/, + method : function(value){ + return this._secondsToUnits(parseFloat(value)); + } + }, + "samples" : { + regexp : /^(\d+)samples/, + method : function(value){ + return parseInt(value) / this.context.sampleRate; + } + }, + "default" : { + regexp : /^(\d+(?:\.\d+)?)/, + method : function(value){ + return this._primaryExpressions[this._defaultUnits].method.call(this, value); + } + } + }; + + Tone.TimeBase.prototype._binaryExpressions = { + "+" : { + regexp : /^\+/, + precedence : 2, + method : function(lh, rh){ + return lh() + rh(); + } + }, + "-" : { + regexp : /^\-/, + precedence : 2, + method : function(lh, rh){ + return lh() - rh(); + } + }, + "*" : { + regexp : /^\*/, + precedence : 1, + method : function(lh, rh){ + return lh() * rh(); + } + }, + "/" : { + regexp : /^\//, + precedence : 1, + method : function(lh, rh){ + return lh() / rh(); + } + } + }; + + Tone.TimeBase.prototype._unaryExpressions = { + "neg" : { + regexp : /^\-/, + method : function(lh){ + return -lh(); + } + } + }; + + Tone.TimeBase.prototype._syntaxGlue = { + "(" : { + regexp : /^\(/ + }, + ")" : { + regexp : /^\)/ + } + }; + + /** + * tokenize the expression based on the Expressions object + * @param {string} expr + * @return {Object} returns two methods on the tokenized list, next and peek + * @private + */ + Tone.TimeBase.prototype._tokenize = function(expr){ + var position = -1; + var tokens = []; + + while(expr.length > 0){ + expr = expr.trim(); + var token = getNextToken(expr, this); + tokens.push(token); + expr = expr.substr(token.value.length); + } + + function getNextToken(expr, context){ + var expressions = ["_binaryExpressions", "_unaryExpressions", "_primaryExpressions", "_syntaxGlue"]; + for (var i = 0; i < expressions.length; i++){ + var group = context[expressions[i]]; + for (var opName in group){ + var op = group[opName]; + var reg = op.regexp; + var match = expr.match(reg); + if (match !== null){ + return { + method : op.method, + precedence : op.precedence, + regexp : op.regexp, + value : match[0], + }; + } + } + } + throw new SyntaxError("Unexpected token "+expr); + } + + return { + next : function(){ + return tokens[++position]; + }, + peek : function(){ + return tokens[position + 1]; + } + }; + }; + + /** + * Given a token, find the value within the groupName + * @param {Object} token + * @param {String} groupName + * @param {Number} precedence + * @private + */ + Tone.TimeBase.prototype._matchGroup = function(token, group, prec) { + var ret = false; + if (!this.isUndef(token)){ + for (var opName in group){ + var op = group[opName]; + if (op.regexp.test(token.value)){ + if (!this.isUndef(prec)){ + if(op.precedence === prec){ + return op; + } + } else { + return op; + } + } + } + } + return ret; + }; + + /** + * Match a binary expression given the token and the precedence + * @param {Lexer} lexer + * @param {Number} precedence + * @private + */ + Tone.TimeBase.prototype._parseBinary = function(lexer, precedence){ + if (this.isUndef(precedence)){ + precedence = 2; + } + var expr; + if (precedence < 0){ + expr = this._parseUnary(lexer); + } else { + expr = this._parseBinary(lexer, precedence - 1); + } + var token = lexer.peek(); + while (token && this._matchGroup(token, this._binaryExpressions, precedence)){ + token = lexer.next(); + expr = token.method.bind(this, expr, this._parseBinary(lexer, precedence - 1)); + token = lexer.peek(); + } + return expr; + }; + + /** + * Match a unary expression. + * @param {Lexer} lexer + * @private + */ + Tone.TimeBase.prototype._parseUnary = function(lexer){ + var token, expr; + token = lexer.peek(); + var op = this._matchGroup(token, this._unaryExpressions); + if (op) { + token = lexer.next(); + expr = this._parseUnary(lexer); + return op.method.bind(this, expr); + } + return this._parsePrimary(lexer); + }; + + /** + * Match a primary expression (a value). + * @param {Lexer} lexer + * @private + */ + Tone.TimeBase.prototype._parsePrimary = function(lexer){ + var token, expr; + token = lexer.peek(); + if (this.isUndef(token)) { + throw new SyntaxError("Unexpected end of expression"); + } + if (this._matchGroup(token, this._primaryExpressions)) { + token = lexer.next(); + var matching = token.value.match(token.regexp); + return token.method.bind(this, matching[1], matching[2], matching[3]); + } + if (token && token.value === "("){ + lexer.next(); + expr = this._parseBinary(lexer); + token = lexer.next(); + if (!(token && token.value === ")")) { + throw new SyntaxError("Expected )"); + } + return expr; + } + throw new SyntaxError("Cannot process token " + token.value); + }; + + /** + * Recursively parse the string expression into a syntax tree. + * @param {string} expr + * @return {Function} the bound method to be evaluated later + * @private + */ + Tone.TimeBase.prototype._parseExprString = function(exprString){ + var lexer = this._tokenize(exprString); + var tree = this._parseBinary(lexer); + return tree; + }; + + /////////////////////////////////////////////////////////////////////////// + // DEFAULTS + /////////////////////////////////////////////////////////////////////////// + + /** + * The initial expression value + * @return {Number} The initial value 0 + * @private + */ + Tone.TimeBase.prototype._noOp = function(){ + return 0; + }; + + /** + * The default expression value if no arguments are given + * @private + */ + Tone.TimeBase.prototype._defaultExpr = function(){ + return this._noOp; + }; + + /** + * The default units if none are given. + * @private + */ + Tone.TimeBase.prototype._defaultUnits = "s"; + + /////////////////////////////////////////////////////////////////////////// + // UNIT CONVERSIONS + /////////////////////////////////////////////////////////////////////////// + + /** + * Returns the value of a frequency in the current units + * @param {Frequency} freq + * @return {Number} + * @private + */ + Tone.TimeBase.prototype._frequencyToUnits = function(freq){ + return 1/freq; + }; + + /** + * Return the value of the beats in the current units + * @param {Number} beats + * @return {Number} + * @private + */ + Tone.TimeBase.prototype._beatsToUnits = function(beats){ + return (60 / Tone.Transport.bpm.value) * beats; + }; + + /** + * Returns the value of a second in the current units + * @param {Seconds} seconds + * @return {Number} + * @private + */ + Tone.TimeBase.prototype._secondsToUnits = function(seconds){ + return seconds; + }; + + /** + * Returns the value of a tick in the current time units + * @param {Ticks} ticks + * @return {Number} + * @private + */ + Tone.TimeBase.prototype._ticksToUnits = function(ticks){ + return ticks * (this._beatsToUnits(1) / Tone.Transport.PPQ); + }; + + /** + * Return the time signature. + * @return {Number} + * @private + */ + Tone.TimeBase.prototype._timeSignature = function(){ + return Tone.Transport.timeSignature; + }; + + /////////////////////////////////////////////////////////////////////////// + // EXPRESSIONS + /////////////////////////////////////////////////////////////////////////// + + /** + * Push an expression onto the expression list + * @param {Time} val + * @param {String} type + * @param {String} units + * @return {Tone.TimeBase} + * @private + */ + Tone.TimeBase.prototype._pushExpr = function(val, name, units){ + //create the expression + if (!(val instanceof Tone.TimeBase)){ + val = new Tone.TimeBase(val, units); + } + this._expr = this._binaryExpressions[name].method.bind(this, this._expr, val._expr); + return this; + }; + + /** + * Subtract the current value by the given time. + * @param {Time} val The value to divide by + * @param {String=} units Optional units to use with the value. + * @return {Tone.TimeBase} this + */ + Tone.TimeBase.prototype.add = function(val, units){ + return this._pushExpr(val, "+", units); + }; + + /** + * Subtract the current value by the given time. + * @param {Time} val The value to divide by + * @param {String=} units Optional units to use with the value. + * @return {Tone.TimeBase} this + */ + Tone.TimeBase.prototype.sub = function(val, units){ + return this._pushExpr(val, "-", units); + }; + + /** + * Multiply the current value by the given time. + * @param {Time} val The value to divide by + * @param {String=} units Optional units to use with the value. + * @return {Tone.TimeBase} this + */ + Tone.TimeBase.prototype.mult = function(val, units){ + return this._pushExpr(val, "*", units); + }; + + /** + * Divide the current value by the given time. + * @param {Time} val The value to divide by + * @param {String=} units Optional units to use with the value. + * @return {Tone.TimeBase} this + */ + Tone.TimeBase.prototype.div = function(val, units){ + return this._pushExpr(val, "/", units); + }; + + /** + * Evaluate the time value. Returns the time + * in seconds. + * @return {Seconds} + */ + Tone.TimeBase.prototype.eval = function(){ + return this._expr(); + }; + + /** + * Clean up + * @return {Tone.TimeBase} this + */ + Tone.TimeBase.prototype.dispose = function(){ + this._expr = null; + }; + + return Tone.TimeBase; +}); \ No newline at end of file diff --git a/Tone/type/TransportTime.js b/Tone/type/TransportTime.js new file mode 100644 index 00000000..f8e13d9b --- /dev/null +++ b/Tone/type/TransportTime.js @@ -0,0 +1,121 @@ +define(["Tone/core/Tone", "Tone/type/Time"], function (Tone) { + + /** + * @extends {Tone.Time} + */ + Tone.TransportTime = function(val, units){ + if (this instanceof Tone.TransportTime){ + + Tone.Time.call(this, val, units); + + } else { + return new Tone.TransportTime(val, units); + } + }; + + Tone.extend(Tone.TransportTime, Tone.Time); + + //clone the expressions so that + //we can add more without modifying the original + Tone.TransportTime.prototype._unaryExpressions = Object.create(Tone.Time.prototype._unaryExpressions); + + /** + * Adds an additional unary expression + * which quantizes values to the next subdivision + * @type {Object} + * @private + */ + Tone.TransportTime.prototype._unaryExpressions.quantize = { + regexp : /^@/, + method : function(rh){ + var subdivision = rh(); + var multiple = Math.ceil(Tone.Transport.ticks / subdivision); + return multiple * subdivision; + } + }; + + /** + * @override + * The value of a beat in ticks. + * @param {Number} beats + * @return {Number} + * @private + */ + Tone.TransportTime.prototype._beatsToUnits = function(beats){ + return Tone.Transport.PPQ * beats; + }; + + /** + * @override + * @param {Ticks} ticks + * @return {Number} + * @private + */ + Tone.TransportTime.prototype._ticksToUnits = function(ticks){ + return ticks; + }; + + /** + * Returns the value of a second in the current units + * @param {Seconds} seconds + * @return {Number} + * @private + */ + Tone.TransportTime.prototype._secondsToUnits = function(seconds){ + var quarterTime = (60 / Tone.Transport.bpm.value); + var quarters = seconds / quarterTime; + return Math.floor(quarters * Tone.Transport.PPQ); + }; + + /** + * Evaluate the time expression. Returns values in ticks + * @return {Ticks} + */ + Tone.TransportTime.prototype.eval = function(){ + return Math.floor(this._expr()); + }; + + /** + * The current time along the Transport + * @return {Ticks} The Transport's position in ticks. + */ + Tone.TransportTime.prototype.now = function(){ + return Tone.Transport.ticks; + }; + + /** + * Return the time in ticks. + * @return {Ticks} + */ + Tone.TransportTime.prototype.toTicks = function(){ + return this.eval(); + }; + + /** + * Return the time in samples + * @return {Samples} + */ + Tone.TransportTime.prototype.toSamples = function(){ + return this.toSeconds() * this.context.sampleRate; + }; + + /** + * Return the time as a frequency value + * @return {Frequency} + */ + Tone.TransportTime.prototype.toFrequency = function(){ + return 1/this.toSeconds(); + }; + + /** + * Return the time in seconds. + * @return {Seconds} + */ + Tone.TransportTime.prototype.toSeconds = function(){ + var beatTime = 60/Tone.Transport.bpm.value; + var tickTime = beatTime / Tone.Transport.PPQ; + return this.eval() * tickTime; + }; + + return Tone.TransportTime; +}); \ No newline at end of file diff --git a/Tone/type/Type.js b/Tone/type/Type.js new file mode 100644 index 00000000..76e8f6fb --- /dev/null +++ b/Tone/type/Type.js @@ -0,0 +1,224 @@ +define(["Tone/core/Tone", "Tone/type/Time", "Tone/type/Frequency", "Tone/type/TransportTime"], +function (Tone) { + + /////////////////////////////////////////////////////////////////////////// + // TYPES + /////////////////////////////////////////////////////////////////////////// + + /** + * Units which a value can take on. + * @enum {String} + */ + Tone.Type = { + /** + * Default units + * @typedef {Default} + */ + Default : "number", + /** + * Time can be described in a number of ways. Read more [Time](https://github.com/Tonejs/Tone.js/wiki/Time). + * + * + * + * @typedef {Time} + */ + Time : "time", + /** + * Frequency can be described similar to time, except ultimately the + * values are converted to frequency instead of seconds. A number + * is taken literally as the value in hertz. Additionally any of the + * Time encodings can be used. Note names in the form + * of NOTE OCTAVE (i.e. C4) are also accepted and converted to their + * frequency value. + * @typedef {Frequency} + */ + Frequency : "frequency", + /** + * TransportTime describes a position along the Transport's timeline. It is + * similar to Time in that it uses all the same encodings, but TransportTime specifically + * pertains to the Transport's timeline, which is startable, stoppable, loopable, and seekable. + * [Read more](https://github.com/Tonejs/Tone.js/wiki/TransportTime) + * @typedef {TransportTime} + */ + TransportTime : "transportTime", + /** + * Ticks are the basic subunit of the Transport. They are + * the smallest unit of time that the Transport supports. + * @typedef {Ticks} + */ + Ticks : "ticks", + /** + * Normal values are within the range [0, 1]. + * @typedef {NormalRange} + */ + NormalRange : "normalRange", + /** + * AudioRange values are between [-1, 1]. + * @typedef {AudioRange} + */ + AudioRange : "audioRange", + /** + * Decibels are a logarithmic unit of measurement which is useful for volume + * because of the logarithmic way that we perceive loudness. 0 decibels + * means no change in volume. -10db is approximately half as loud and 10db + * is twice is loud. + * @typedef {Decibels} + */ + Decibels : "db", + /** + * Half-step note increments, i.e. 12 is an octave above the root. and 1 is a half-step up. + * @typedef {Interval} + */ + Interval : "interval", + /** + * Beats per minute. + * @typedef {BPM} + */ + BPM : "bpm", + /** + * The value must be greater than or equal to 0. + * @typedef {Positive} + */ + Positive : "positive", + /** + * A cent is a hundredth of a semitone. + * @typedef {Cents} + */ + Cents : "cents", + /** + * Angle between 0 and 360. + * @typedef {Degrees} + */ + Degrees : "degrees", + /** + * A number representing a midi note. + * @typedef {MIDI} + */ + MIDI : "midi", + /** + * A colon-separated representation of time in the form of + * Bars:Beats:Sixteenths. + * @typedef {BarsBeatsSixteenths} + */ + BarsBeatsSixteenths : "barsBeatsSixteenths", + /** + * Sampling is the reduction of a continuous signal to a discrete signal. + * Audio is typically sampled 44100 times per second. + * @typedef {Samples} + */ + Samples : "samples", + /** + * Hertz are a frequency representation defined as one cycle per second. + * @typedef {Hertz} + */ + Hertz : "hertz", + /** + * A frequency represented by a letter name, + * accidental and octave. This system is known as + * [Scientific Pitch Notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation). + * @typedef {Note} + */ + Note : "note", + /** + * One millisecond is a thousandth of a second. + * @typedef {Milliseconds} + */ + Milliseconds : "milliseconds", + /** + * Seconds are the time unit of the AudioContext. In the end, + * all values need to be evaluated to seconds. + * @typedef {Seconds} + */ + Seconds : "seconds", + /** + * A string representing a duration relative to a measure. + * + * @typedef {Notation} + */ + Notation : "notation", + }; + + /////////////////////////////////////////////////////////////////////////// + // AUGMENT TONE's PROTOTYPE + /////////////////////////////////////////////////////////////////////////// + + /** + * Convert Time into seconds. + * + * Unlike the method which it overrides, this takes into account + * transporttime and musical notation. + * + * Time : 1.40 + * Notation: 4n|1m|2t + * Now Relative: +3n + * Math: 3n+16n or even complicated expressions ((3n*2)/6 + 1) + * + * @param {Time} time + * @return {Seconds} + */ + Tone.prototype.toSeconds = function(time){ + if (this.isNumber(time)){ + return time; + } else if (this.isString(time) || this.isUndef(time)){ + return (new Tone.Time(time)).eval(); + } else if (time instanceof Tone.TransportTime){ + return time.toSeconds(); + } else if (time instanceof Tone.Time){ + return time.eval(); + } else if (time instanceof Tone.Frequency){ + return time.toSeconds(); + } + }; + + /** + * Convert a frequency representation into a number. + * @param {Frequency} freq + * @return {Hertz} the frequency in hertz + */ + Tone.prototype.toFrequency = function(freq){ + if (this.isNumber(freq)){ + return freq; + } else if (this.isString(freq) || this.isUndef(freq)){ + return (new Tone.Frequency(freq)).eval(); + } else if (freq instanceof Tone.Frequency){ + return freq.eval(); + } else if (freq instanceof Tone.Time){ + return freq.toFrequency(); + } + }; + + /** + * Convert a time representation into ticks. + * @param {Time} time + * @return {Ticks} the time in ticks + */ + Tone.prototype.toTicks = function(time){ + if (this.isNumber(time) || this.isString(time) || this.isUndef(time)){ + return (new Tone.TransportTime(time)).eval(); + } else if (time instanceof Tone.Frequency){ + return time.toTicks(); + } else if (time instanceof Tone.TransportTime){ + return time.eval(); + } else if (time instanceof Tone.Time){ + return time.toTicks(); + } + }; + + return Tone; +}); \ No newline at end of file