diff --git a/CHANGELOG.md b/CHANGELOG.md index 988fa42..e07217b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## 1.1.1 — 2016.04.10 + +### Changes +* Better interval names (more commonly used). [#37](https://github.com/sunyatasattva/overtones/issues/37) +* Facebook share button now shows share count. +* Linking latest release page in the footer instead of specific release. + +### Fixes +* Fixes compatibility with older versions of Safari. [#38](https://github.com/sunyatasattva/overtones/issues/38) + +### Behind the hood +* Added some missing code documentation. + +### [Full changelog](https://github.com/sunyatasattva/overtones/compare/1.1.0...1.1.1) + ## 1.1.0 — 2016.03.24 ### New features diff --git a/README.md b/README.md index 218b598..e5a93ab 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# overtones -A visualization of overtones for music theory and polyphonic overtone singing composers +# Overtones +A visualization of overtones for music theory and polyphonic overtone singing composers. http://www.suonoterapia.org/overtones diff --git a/assets/data/intervals.json b/assets/data/intervals.json index 561f0e0..1bcc8b4 100644 --- a/assets/data/intervals.json +++ b/assets/data/intervals.json @@ -1,240 +1,246 @@ { - "1/1": { - "name": "Perfect unison", - "short": "P1" - }, - "32805/32768": { - "name": "Schisma" - }, - "81/80": { - "name": "Syntonic comma", - "alternateNames": [ - "Major comma", - "Chromatic diesis", - "Comma of Didymus" - ] - }, - "531441/524288": { - "name": "Pythagorean comma", - "alternateNames": [ - "Ditonic comma" - ] - }, - "128/125": { - "name": "Enharmonic diesis", - "alternateNames": [ - "Enharmonic diesis", - "Minor diesis", - "Diminished second" - ] - }, - "25/24": { - "name": "Augmented unison", - "short": "A1", - "alternateNames": [ - "Minor chromatic semitone", - "Minor semitone", - "Small semitone" - ] - }, - "256/243": { - "name": "Limma", - "alternateNames": [ - "Pythagorean limma" - ] - }, - "135/128": { - "name": "Chromatic semitone", - "alternateNames": [ - "Major limma" - ] - }, - "18/17": { - "name": "Small septendecimal semitone" - }, - "17/16": { - "name": "Large septendecimal semitone" - }, - "16/15": { - "name": "Minor second", - "short": "m2", - "alternateNames": [ - "Diatonic semitone", - "Major semitone", - "Half tone" - ] - }, - "2187/2048": { - "name": "Apotome", - "alternateNames": [ - "Pythagorean major semitone" - ] - }, - "15/14": { - "name": "Septimal diatonic semitone" - }, - "14/13": { - "name": "Trienthird", - "alternateNames": [ - "Tridecimal supraminor second" - ] - }, - "27/25": { - "name": "Large limma" - }, - "13/12": { - "name": "Tridecimal subtone", - "alternateNames": [ - "Three-quarters tone" - ] - }, - "12/11": { - "name": "Undecimal neutral second", - "short": "n2" - }, - "11/10": { - "name": "Greater undecimal neutral second", - "short": "n2", - "alternateNames": [ - "Ptolemy's second" - ] - }, - "10/9": { - "name": "Minor tone" - }, - "9/8": { - "name": "Major second", - "short": "M2", - "alternateNames": [ - "Tone", - "Major tone", - "Whole tone", - ] - }, - "8/7": { - "name": "Septimal major second", - "short": "SM2", - "alternateNames": [ - "Supermajor second" - ] - }, - "7/6": { - "name": "Septimal minor third", - "short": "sm2", - "alternateNames": [ - "Subminor third" - ] - }, - "6/5": { - "name": "Minor third", - "short": "m3", - "alternateNames": [ - "Semiditonus" - ] - }, - "16/13": { - "name": "Tridecimal neutral third" - }, - "5/4": { - "name": "Major third", - "short": "M3", - "alternateNames": [ - "Ditonus" - ] - }, - "4/3": { - "name": "Perfect fourth", - "short": "P4", - "alternateNames": [ - "Diatessaron" - ] - }, - "11/8": { - "name": "Superfourth", - "alternateNames": [ - "Undecimal semi-augmented fourth" - ] - }, - "45/32": { - "name": "Augmented fourth", - "short": "A4", - "alternateNames": [ - "Tritone" - ] - }, - "64/45": { - "name": "Diminished fifth", - "short": "d5", - "alternateNames": [ - "Semitritone", - "Semi-diapente" - ] - }, - "16/11": { - "name": "Undecimal subfifth" - }, - "3/2": { - "name": "Perfect fifth", - "short": "P5", - "alternateNames": [ - "Diapente" - ] - }, - "8/5": { - "name": "Minor sixth", - "short": "m6", - "alternateNames": [ - "Tetratonus", - "Hexachordum minus", - "Semitonus maius cum diapente" - ] - }, - "13/8": { - "name": "Neutral sixth", - "short": "n6", - "alternateNames": [ - "Tridecimal neutral sixth" - ] - }, - "5/3": { - "name": "Major sixth", - "short": "M6", - "alternateNames": [ - "Hexachordum maius", - "Tonus cum diapente" - ] - }, - "7/4": { - "name": "Harmonic seventh", - "short": "m7", - "alternateNames": [ - "Septimal minor seventh", - "Subminor seventh", - "Augmented sixth" - ] - }, - "16/9": { - "name": "Minor seventh", - "short": "m7", - "alternateNames": [ - "Heptachordum minus", - "Semiditonus cum diapente", - "Pentatonus" - ] - }, - "15/8": { - "name": "Major seventh", - "short": "M7", - "alternateNames": [ - "Heptachordum maius", - "Ditonus cum diapente" - ] - }, - "2/1": { - "name": "Perfect octave", - "short": "P8", - "alternateNames": [ - "Diapason" - ] - }, + "1/1": { + "name": "Perfect unison", + "short": "P1" + }, + "32805/32768": { + "name": "Schisma" + }, + "81/80": { + "name": "Syntonic comma", + "alternateNames": [ + "Major comma", + "Chromatic diesis", + "Comma of Didymus" + ] + }, + "531441/524288": { + "name": "Pythagorean comma", + "alternateNames": [ + "Ditonic comma" + ] + }, + "128/125": { + "name": "Enharmonic diesis", + "alternateNames": [ + "Enharmonic diesis", + "Minor diesis", + "Diminished second" + ] + }, + "25/24": { + "name": "Augmented unison", + "short": "A1", + "alternateNames": [ + "Minor chromatic semitone", + "Minor semitone", + "Small semitone" + ] + }, + "256/243": { + "name": "Limma", + "alternateNames": [ + "Pythagorean limma" + ] + }, + "135/128": { + "name": "Chromatic semitone", + "alternateNames": [ + "Major limma" + ] + }, + "18/17": { + "name": "Small septendecimal semitone" + }, + "17/16": { + "name": "Large septendecimal semitone" + }, + "16/15": { + "name": "Minor second", + "short": "m2", + "alternateNames": [ + "Diatonic semitone", + "Major semitone", + "Half tone" + ] + }, + "2187/2048": { + "name": "Apotome", + "alternateNames": [ + "Pythagorean major semitone" + ] + }, + "15/14": { + "name": "Septimal diatonic semitone" + }, + "14/13": { + "name": "Supraminor second", + "alternateNames": [ + "Trienthird", + "Tridecimal supraminor second" + ] + }, + "27/25": { + "name": "Large limma" + }, + "13/12": { + "name": "Small neutral second", + "alternateNames": [ + "Tridecimal subtone", + "Three-quarters tone" + ] + }, + "12/11": { + "name": "Neutral second", + "short": "n2", + "alternateNames": [ + "Undecimal neutral second" + ] + }, + "11/10": { + "name": "Submajor second", + "short": "n2", + "alternateNames": [ + "Greater undecimal neutral second", + "Ptolemy's second" + ] + }, + "10/9": { + "name": "Minor tone" + }, + "9/8": { + "name": "Major second", + "short": "M2", + "alternateNames": [ + "Tone", + "Major tone", + "Whole tone", + ] + }, + "8/7": { + "name": "Supermajor second", + "short": "SM2", + "alternateNames": [ + "Septimal major second" + ] + }, + "7/6": { + "name": "Subminor third", + "short": "sm2", + "alternateNames": [ + "Septimal minor third", + ] + }, + "6/5": { + "name": "Minor third", + "short": "m3", + "alternateNames": [ + "Semiditonus" + ] + }, + "16/13": { + "name": "Tridecimal neutral third" + }, + "5/4": { + "name": "Major third", + "short": "M3", + "alternateNames": [ + "Ditonus" + ] + }, + "4/3": { + "name": "Perfect fourth", + "short": "P4", + "alternateNames": [ + "Diatessaron" + ] + }, + "11/8": { + "name": "Superfourth", + "alternateNames": [ + "Undecimal semi-augmented fourth" + ] + }, + "45/32": { + "name": "Augmented fourth", + "short": "A4", + "alternateNames": [ + "Tritone" + ] + }, + "64/45": { + "name": "Diminished fifth", + "short": "d5", + "alternateNames": [ + "Semitritone", + "Semi-diapente" + ] + }, + "16/11": { + "name": "Undecimal subfifth" + }, + "3/2": { + "name": "Perfect fifth", + "short": "P5", + "alternateNames": [ + "Diapente" + ] + }, + "8/5": { + "name": "Minor sixth", + "short": "m6", + "alternateNames": [ + "Tetratonus", + "Hexachordum minus", + "Semitonus maius cum diapente" + ] + }, + "13/8": { + "name": "Neutral sixth", + "short": "n6", + "alternateNames": [ + "Tridecimal neutral sixth" + ] + }, + "5/3": { + "name": "Major sixth", + "short": "M6", + "alternateNames": [ + "Hexachordum maius", + "Tonus cum diapente" + ] + }, + "7/4": { + "name": "Harmonic seventh", + "short": "m7", + "alternateNames": [ + "Septimal minor seventh", + "Subminor seventh", + "Augmented sixth" + ] + }, + "16/9": { + "name": "Minor seventh", + "short": "m7", + "alternateNames": [ + "Heptachordum minus", + "Semiditonus cum diapente", + "Pentatonus" + ] + }, + "15/8": { + "name": "Major seventh", + "short": "M7", + "alternateNames": [ + "Heptachordum maius", + "Ditonus cum diapente" + ] + }, + "2/1": { + "name": "Perfect octave", + "short": "P8", + "alternateNames": [ + "Diapason" + ] + }, } \ No newline at end of file diff --git a/assets/images/overtone-spiral.svg b/assets/images/overtone-spiral.svg index bc7e9e4..8c2bbd9 100644 --- a/assets/images/overtone-spiral.svg +++ b/assets/images/overtone-spiral.svg @@ -1,520 +1,520 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/js/analytics.js b/assets/js/analytics.js index 2f6d472..bc153cd 100644 --- a/assets/js/analytics.js +++ b/assets/js/analytics.js @@ -1,3 +1,9 @@ +/** + * Interface for the Analytics data layer + * + * @module + */ + 'use strict'; var jQuery = require('jquery'); @@ -6,8 +12,8 @@ window.dataLayer = window.dataLayer || []; module.exports = function($){ $(document) .on('overtones:play overtones:options:change', function(e){ - window.dataLayer.push({ event: e.type, data: e.details }); - }) + window.dataLayer.push({ event: e.type, data: e.details }); + }) .on('overtones:help:show overtones:help:close overtones:help:complete overtones:help:denied', function(e){ var action = e.type.split(':')[2], target = e.target.classList ? e.target.classList[0] : e.target; @@ -15,7 +21,7 @@ module.exports = function($){ e.idx = e.idx || null; window.dataLayer.push({ event: e.type, idx: e.idx, action: action, target: target }); - }) + }) .on('overtones:play:all overtones:easteregg:share', function(e){ window.dataLayer.push({ event: e.type }); }); diff --git a/assets/js/easter-egg.js b/assets/js/easter-egg.js index 198baf4..3f3c56b 100644 --- a/assets/js/easter-egg.js +++ b/assets/js/easter-egg.js @@ -1,14 +1,27 @@ +/** + * Easter egg module + * + * Manages easter eggs across the application. + * + * @module + */ + var $ = require("jquery"); var once = require("./lib/once"); var Tones = require("./lib/tones"); +/** + * Plays the Easter Egg unlocked secret music. + * + * @return void + */ function secretMusic(){ var opts = { attack: 75, decay: 100 }; - // Equal temperament + // Equal temperament Tones.playFrequency(780, opts) .then( ()=>Tones.playFrequency(739, opts) ) .then( ()=>Tones.playFrequency(622, opts) ) @@ -31,13 +44,16 @@ function secretMusic(){ module.exports = { init: function() { + // Closes the "easter egg found" announcement div on click on close link $(".easter-egg-announcement .shepherd-cancel-link").on("click", function(e){ e.preventDefault(); $(this).closest(".easter-egg-announcement").removeClass("show"); }); + // When all overtones are played together, unlock the easter egg $(document).on("overtones:play:all", function(){ + // Only show the "easter egg found" once once(function(){ $(".easter-egg-announcement").addClass("show"); }, 'easterEggFound'); $("body").addClass("easter-egg"); @@ -47,10 +63,9 @@ module.exports = { e.preventDefault(); try { FB.ui({ - method: "share", - href: "http://www.suonoterapia.org/overtones" + method: "share", + href: "http://www.suonoterapia.org/overtones" }, function(response){ - console.log(response); $(".easter-egg-announcement").removeClass("shepherd-open"); $(document).trigger("overtones:easteregg:share") diff --git a/assets/js/lib/once.js b/assets/js/lib/once.js index 6a9de3e..90c6601 100644 --- a/assets/js/lib/once.js +++ b/assets/js/lib/once.js @@ -1,12 +1,12 @@ module.exports = function executeOnce() { - var argc = arguments.length, bImplGlob = typeof arguments[argc - 1] === "string"; - if (bImplGlob) { argc++; } - if (argc < 3) { throw new TypeError("executeOnce - not enough arguments"); } - var fExec = arguments[0], sKey = arguments[argc - 2]; - if (typeof fExec !== "function") { throw new TypeError("executeOnce - first argument must be a function"); } - if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { throw new TypeError("executeOnce - invalid identifier"); } - if (decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) === "1") { return false; } - fExec.apply(argc > 3 ? arguments[1] : null, argc > 4 ? Array.prototype.slice.call(arguments, 2, argc - 2) : []); - document.cookie = encodeURIComponent(sKey) + "=1; expires=Fri, 31 Dec 9999 23:59:59 GMT" + (bImplGlob || !arguments[argc - 1] ? "; path=/" : ""); - return true; + var argc = arguments.length, bImplGlob = typeof arguments[argc - 1] === "string"; + if (bImplGlob) { argc++; } + if (argc < 3) { throw new TypeError("executeOnce - not enough arguments"); } + var fExec = arguments[0], sKey = arguments[argc - 2]; + if (typeof fExec !== "function") { throw new TypeError("executeOnce - first argument must be a function"); } + if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { throw new TypeError("executeOnce - invalid identifier"); } + if (decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) === "1") { return false; } + fExec.apply(argc > 3 ? arguments[1] : null, argc > 4 ? Array.prototype.slice.call(arguments, 2, argc - 2) : []); + document.cookie = encodeURIComponent(sKey) + "=1; expires=Fri, 31 Dec 9999 23:59:59 GMT" + (bImplGlob || !arguments[argc - 1] ? "; path=/" : ""); + return true; } \ No newline at end of file diff --git a/assets/js/lib/tones.js b/assets/js/lib/tones.js index da94268..6e7d523 100644 --- a/assets/js/lib/tones.js +++ b/assets/js/lib/tones.js @@ -8,23 +8,67 @@ "use strict"; +require("./webkit-audiocontext-patch")(); + var extend = require("lodash.assign"), utils = require("./utils.js"), - ctx = new (window.AudioContext || window.webkitAudioContext)(), + ctx = new window.AudioContext(), masterGain = ctx.createGain(), defaults = { - attack: 150, - decay: 200, - sustain: 0, - release: 1250, - volume: 1, - detune: 0, - type: "sine" + attack: 150, + decay: 200, + sustain: 0, + release: 1250, + volume: 1, + detune: 0, + type: "sine" }, sounds = []; masterGain.connect(ctx.destination); +/** + * Reduces the Sound pitch to a tone within an octave of the tone. + * + * @see {@link Sound.prototype.reduceToSameOctaveAs} + * @alias module:tones.reduceToSameOctave + * + * @param {Sound|Object} firstTone A Sound object (or an object containing a `frequency` property) + * @param {Sound|Object} referenceTone The first sound will adjust its frequency + * to the same octave as this tone + * @param {bool} excludeOctave If this option is `true`, the exact octave will be reduced + * to the unison of the original sound + * + * @return {Number} The frequency of the first sound within the same octave as the reference tone. + */ +function reduceToSameOctave(firstTone, referenceTone, excludeOctave){ + var targetFrequency = firstTone.frequency, + ratio = targetFrequency / referenceTone.frequency; + + if( excludeOctave ) { + while( ratio <= 0.5 || ratio >= 2 ){ + if( ratio <= 0.5 ) + targetFrequency = targetFrequency * 2; + else + targetFrequency = targetFrequency / 2; + + ratio = targetFrequency / referenceTone.frequency; + } + } + else { + while( ratio < 0.5 || ratio > 2 ){ + if( ratio < 0.5 ) + targetFrequency = targetFrequency * 2; + else + targetFrequency = targetFrequency / 2; + + ratio = targetFrequency / referenceTone.frequency; + } + } + +return targetFrequency; +} + /** * @typedef Envelope * @@ -51,17 +95,17 @@ masterGain.connect(ctx.destination); * @return {Envelope} The envelope object, containing the gain node. */ function _createEnvelope(attack, decay, sustain, release) { - var gainNode = ctx.createGain(); - - gainNode.gain.setValueAtTime(0, ctx.currentTime); - - return { - node: gainNode, - attack: attack / 1000, - decay: decay / 1000, - sustain: sustain / 1000, - release: release / 1000 - }; + var gainNode = ctx.createGain(); + + gainNode.gain.setValueAtTime(0, ctx.currentTime); + + return { + node: gainNode, + attack: attack / 1000, + decay: decay / 1000, + sustain: sustain / 1000, + release: release / 1000 + }; } /** @@ -75,13 +119,13 @@ function _createEnvelope(attack, decay, sustain, release) { * @return {OscillatorNode} The oscillator node */ function _createOscillator(frequency, detune, type) { - var oscillatorNode = ctx.createOscillator(); - - oscillatorNode.frequency.value = frequency; - oscillatorNode.detune.value = detune || 0; - oscillatorNode.type = type || "sine"; - - return oscillatorNode; + var oscillatorNode = ctx.createOscillator(); + + oscillatorNode.frequency.value = frequency; + oscillatorNode.detune.value = detune || 0; + oscillatorNode.type = type || "sine"; + + return oscillatorNode; } /** @@ -91,33 +135,33 @@ function _createOscillator(frequency, detune, type) { * @property {number} duration The total duration of the sound. */ function Sound(oscillator, envelope, opts){ - /** - * @type {object} - * @property {number} attack The attack duration of the sound (in secs). See {@link _createEnvelope} - * @property {number} decay The decay duration of the sound (in secs). See {@link _createEnvelope} - * @property {GainNode} node The Gain Node associated with the sound. It won't emit any sound unless - * connected to the `masterGain`. See {@link masterGain}. - * @property {number} sustain The sustain duration of the sound (in secs). See {@link _createEnvelope} - * @property {number} release The release duration of the sound (in secs). See {@link _createEnvelope} - * @property {number} volume The amplitude of the sound, after the decay. 1 is full amplitude. - * @property {number} maxVolume The peak amplitude of the sound. - */ - this.envelope = { - node: envelope.node, - attack: envelope.attack, - decay: envelope.decay, - sustain: envelope.sustain, - release: envelope.release, - volume: opts.volume, - maxVolume: opts.maxVolume - }; - /** @type {OscillatorNode} */ - this.oscillator = oscillator; - this.frequency = oscillator.frequency.value; - this.detune = oscillator.detune.value; - this.waveType = oscillator.type; - - this.duration = envelope.attack + envelope.decay + envelope.sustain + envelope.release; + /** + * @type {object} + * @property {number} attack The attack duration of the sound (in secs). See {@link _createEnvelope} + * @property {number} decay The decay duration of the sound (in secs). See {@link _createEnvelope} + * @property {GainNode} node The Gain Node associated with the sound. It won't emit any sound unless + * connected to the `masterGain`. See {@link masterGain}. + * @property {number} sustain The sustain duration of the sound (in secs). See {@link _createEnvelope} + * @property {number} release The release duration of the sound (in secs). See {@link _createEnvelope} + * @property {number} volume The amplitude of the sound, after the decay. 1 is full amplitude. + * @property {number} maxVolume The peak amplitude of the sound. + */ + this.envelope = { + node: envelope.node, + attack: envelope.attack, + decay: envelope.decay, + sustain: envelope.sustain, + release: envelope.release, + volume: opts.volume, + maxVolume: opts.maxVolume + }; + /** @type {OscillatorNode} */ + this.oscillator = oscillator; + this.frequency = oscillator.frequency.value; + this.detune = oscillator.detune.value; + this.waveType = oscillator.type; + + this.duration = envelope.attack + envelope.decay + envelope.sustain + envelope.release; } /** @@ -128,55 +172,55 @@ function Sound(oscillator, envelope, opts){ * @return {Sound} The played Sound */ Sound.prototype.play = function(){ - var now = ctx.currentTime, - self = this; - - this.oscillator.start(); + var now = ctx.currentTime, + self = this; + + this.oscillator.start(); this.isPlaying = true; - - /** - * Using `setTargetAtTime` because `exponentialRampToValueAtTime` doesn't seem to work properly under - * the current build of Chrome I'm developing in. Not sure if a bug, or I didn't get something. - * `setTargetAtTime` gets the `timeCostant` as third argument, which is the amount of time it takes - * for the curve to reach 1 - 1/e * 100% of the target. The reason why the provided arguments are divided - * by 5 is because after 5 times worth of the Time Constant the value reaches 99.32% of the target, which - * is an acceptable approximation for me. - * - * @see {@link https://en.wikipedia.org/wiki/Time_constant} - * - * @todo put an if with opts.linear = true to use linearRampToValueAtTime instead - */ - - // The note starts NOW from 0 and will get to `maxVolume` in approximately `attack` seconds - this.envelope.node.gain.setTargetAtTime( this.envelope.maxVolume, now, this.envelope.attack / 5 ); - // After `attack` seconds, start a transition to fade to sustain volume in `decay` seconds - this.envelope.node.gain - .setTargetAtTime( this.envelope.volume, now + this.envelope.attack, this.envelope.decay / 5 ); - - if( this.envelope.sustain >= 0 ) { - // Setting a "keyframe" for the volume to be kept until `sustain` seconds have passed - // (plus all the rest) - this.envelope.node.gain - .setValueAtTime( this.envelope.volume, - now + - this.envelope.attack + - this.envelope.decay + - this.envelope.sustain - ); - // Fade out completely starting at the end of the `sustain` in `release` seconds - this.envelope.node.gain - .setTargetAtTime( 0, - now + - this.envelope.attack + - this.envelope.decay + - this.envelope.sustain, - this.envelope.release / 5 - ); - - // Start the removal of the sound process after a little more than the sound duration to account for - // the approximation. (To make sure that the sound doesn't get cut off while still audible) - return new Promise(function(resolve, reject){ + + /** + * Using `setTargetAtTime` because `exponentialRampToValueAtTime` doesn't seem to work properly under + * the current build of Chrome I'm developing in. Not sure if a bug, or I didn't get something. + * `setTargetAtTime` gets the `timeCostant` as third argument, which is the amount of time it takes + * for the curve to reach 1 - 1/e * 100% of the target. The reason why the provided arguments are divided + * by 5 is because after 5 times worth of the Time Constant the value reaches 99.32% of the target, which + * is an acceptable approximation for me. + * + * @see {@link https://en.wikipedia.org/wiki/Time_constant} + * + * @todo put an if with opts.linear = true to use linearRampToValueAtTime instead + */ + + // The note starts NOW from 0 and will get to `maxVolume` in approximately `attack` seconds + this.envelope.node.gain.setTargetAtTime( this.envelope.maxVolume, now, this.envelope.attack / 5 ); + // After `attack` seconds, start a transition to fade to sustain volume in `decay` seconds + this.envelope.node.gain + .setTargetAtTime( this.envelope.volume, now + this.envelope.attack, this.envelope.decay / 5 ); + + if( this.envelope.sustain >= 0 ) { + // Setting a "keyframe" for the volume to be kept until `sustain` seconds have passed + // (plus all the rest) + this.envelope.node.gain + .setValueAtTime( this.envelope.volume, + now + + this.envelope.attack + + this.envelope.decay + + this.envelope.sustain + ); + // Fade out completely starting at the end of the `sustain` in `release` seconds + this.envelope.node.gain + .setTargetAtTime( 0, + now + + this.envelope.attack + + this.envelope.decay + + this.envelope.sustain, + this.envelope.release / 5 + ); + + // Start the removal of the sound process after a little more than the sound duration to account for + // the approximation. (To make sure that the sound doesn't get cut off while still audible) + return new Promise(function(resolve, reject){ let effectiveSoundDuration = self.envelope.attack + self.envelope.decay + self.envelope.sustain; setTimeout( ()=>resolve(self), effectiveSoundDuration * 1000 ); @@ -185,9 +229,9 @@ Sound.prototype.play = function(){ if( !self.isStopping ) self.stop(); }, self.duration * 1250 ); }); - } - - return this; + } + + return this; }; /** @@ -196,7 +240,7 @@ Sound.prototype.play = function(){ * @return {Sound} The Sound object that was removed */ Sound.prototype.remove = function(){ - try { + try { this.oscillator.disconnect(this.envelope.node); this.envelope.node.gain.cancelScheduledValues(ctx.currentTime); this.envelope.node.disconnect(masterGain); @@ -205,7 +249,7 @@ Sound.prototype.remove = function(){ console.trace(); } - if( sounds.indexOf(this) !== -1 ) + if( sounds.indexOf(this) !== -1 ) return sounds.splice( sounds.indexOf(this), 1 )[0]; }; @@ -217,9 +261,9 @@ Sound.prototype.remove = function(){ * @return {Sound} The stopped Sound */ Sound.prototype.stop = function(){ - this.oscillator.stop(); - - return this.remove(); + this.oscillator.stop(); + + return this.remove(); }; /** @@ -229,7 +273,7 @@ Sound.prototype.stop = function(){ */ Sound.prototype.fadeOut = function(){ var now = ctx.currentTime, - self = this; + self = this; this.envelope.node.gain.setTargetAtTime( 0, now, this.envelope.release / 5 ); this.isPlaying = false; @@ -255,9 +299,9 @@ Sound.prototype.fadeOut = function(){ * @return {int} The interval between the two sounds rounded to the closest cent. */ Sound.prototype.intervalInCents = function(tone){ - var ratio = this.frequency / tone.frequency; - - return Math.round( 1200 * utils.logBase(2, ratio) ); + var ratio = this.frequency / tone.frequency; + + return Math.round( 1200 * utils.logBase(2, ratio) ); }; /** @@ -272,7 +316,7 @@ Sound.prototype.intervalInCents = function(tone){ * @return {bool} True if it is, False if it isn't. */ Sound.prototype.isOctaveOf = function(tone){ - return utils.isPowerOfTwo( this.frequency / tone.frequency ); + return utils.isPowerOfTwo( this.frequency / tone.frequency ); }; /** @@ -298,10 +342,10 @@ Sound.prototype.isOctaveOf = function(tone){ */ Sound.prototype.reduceToSameOctaveAs = function(tone, excludeOctave){ this.frequency = reduceToSameOctave(this, tone, excludeOctave); - - this.oscillator.frequency.setValueAtTime( this.frequency, ctx.currentTime ); - return this; + this.oscillator.frequency.setValueAtTime( this.frequency, ctx.currentTime ); + + return this; }; /** @@ -327,21 +371,21 @@ Sound.prototype.reduceToSameOctaveAs = function(tone, excludeOctave){ * @return {Sound} The Sound object. */ function createSound(frequency, opts){ - var opts = extend( {}, defaults, opts ), - envelope = _createEnvelope(opts.attack, opts.decay, opts.sustain, opts.release), - oscillator = _createOscillator(frequency, opts.detune, opts.type), - thisSound; - - opts.maxVolume = opts.maxVolume || opts.volume; - - thisSound = new Sound(oscillator, envelope, opts); - - oscillator.connect(envelope.node); - envelope.node.connect(masterGain); - - sounds.push(thisSound); - - return thisSound; + var opts = extend( {}, defaults, opts ), + envelope = _createEnvelope(opts.attack, opts.decay, opts.sustain, opts.release), + oscillator = _createOscillator(frequency, opts.detune, opts.type), + thisSound; + + opts.maxVolume = opts.maxVolume || opts.volume; + + thisSound = new Sound(oscillator, envelope, opts); + + oscillator.connect(envelope.node); + envelope.node.connect(masterGain); + + sounds.push(thisSound); + + return thisSound; } /** @@ -355,70 +399,28 @@ function createSound(frequency, opts){ * @return {Sound} The Sound object. */ function playFrequency(frequency, opts) { - var thisSound = createSound(frequency, opts); - - return thisSound.play(); -} + var thisSound = createSound(frequency, opts); -/** - * Reduces the Sound pitch to a tone within an octave of the tone. - * - * @see {@link Sound.prototype.reduceToSameOctaveAs} - * @alias module:tones.reduceToSameOctave - * - * @param {Sound|Object} firstTone A Sound object (or an object containing a `frequency` property) - * @param {Sound|Object} referenceTone The first sound will adjust its frequency - * to the same octave as this tone - * @param {bool} excludeOctave If this option is `true`, the exact octave will be reduced - * to the unison of the original sound - * - * @return {Number} The frequency of the first sound within the same octave as the reference tone. - */ -function reduceToSameOctave(firstTone, referenceTone, excludeOctave){ - var targetFrequency = firstTone.frequency, - ratio = targetFrequency / referenceTone.frequency; - - if( excludeOctave ) { - while( ratio <= 0.5 || ratio >= 2 ){ - if( ratio <= 0.5 ) - targetFrequency = targetFrequency * 2; - else - targetFrequency = targetFrequency / 2; - - ratio = targetFrequency / referenceTone.frequency; - } - } - else { - while( ratio < 0.5 || ratio > 2 ){ - if( ratio < 0.5 ) - targetFrequency = targetFrequency * 2; - else - targetFrequency = targetFrequency / 2; - - ratio = targetFrequency / referenceTone.frequency; - } - } - - return targetFrequency; -}; + return thisSound.play(); +} module.exports = { - /** - * The Audio Context where the module operates - * @type {AudioContext} - */ - context: ctx, - createSound: createSound, - /** - * The Master Gain node that is attached to the output device - * @type {GainNode} - */ - masterGain: masterGain, - playFrequency: playFrequency, + /** + * The Audio Context where the module operates + * @type {AudioContext} + */ + context: ctx, + createSound: createSound, + /** + * The Master Gain node that is attached to the output device + * @type {GainNode} + */ + masterGain: masterGain, + playFrequency: playFrequency, reduceToSameOctave: reduceToSameOctave, - /** - * A list of currently active sounds for manipulation - * @type {array} - */ - sounds: sounds, + /** + * A list of currently active sounds for manipulation + * @type {array} + */ + sounds: sounds, }; \ No newline at end of file diff --git a/assets/js/lib/utils.js b/assets/js/lib/utils.js index f067b07..854bd16 100644 --- a/assets/js/lib/utils.js +++ b/assets/js/lib/utils.js @@ -7,58 +7,58 @@ "use strict"; module.exports = { - /** - * Searches within an array the value closest to a target value. - * - * @param {number} target A target value - * @param {array} array An array of values to search within - * - * @return {number} The closest value to the target - */ - binarySearch: function(target, array) { - var highestIndex = array.length - 1, - lowestIndex = 0, - middlePoint; - - while(highestIndex - lowestIndex > 1) { - middlePoint = Math.floor( (lowestIndex + highestIndex) / 2); - - if(array[middlePoint] < target) { - lowestIndex = middlePoint; - } else { - highestIndex = middlePoint; - } - } + /** + * Searches within an array the value closest to a target value. + * + * @param {number} target A target value + * @param {array} array An array of values to search within + * + * @return {number} The closest value to the target + */ + binarySearch: function(target, array) { + var highestIndex = array.length - 1, + lowestIndex = 0, + middlePoint; + + while(highestIndex - lowestIndex > 1) { + middlePoint = Math.floor( (lowestIndex + highestIndex) / 2); + + if(array[middlePoint] < target) { + lowestIndex = middlePoint; + } else { + highestIndex = middlePoint; + } + } - if( target - array[lowestIndex] <= array[highestIndex] - target ) { - return array[lowestIndex]; - } - - return array[highestIndex]; - }, - /** - * Checks if a number is a power of two - * - * @param {number} n The number to check - * - * @return {bool} - */ - isPowerOfTwo: function(n){ - // Another idea would be isInteger( logBase(2, n) ) - // @see http://www.graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 - return ( n !== 0 && ( n & (n - 1) ) === 0 ); - }, - /** - * Calculates a logarithm of a number in an arbitrary base - * - * @param {number} base The logarithm base - * @param {number} n The number - * - * @return {number} logbase(n) - */ - logBase: function(base,n) { - return Math.log(n) / Math.log(base); - }, + if( target - array[lowestIndex] <= array[highestIndex] - target ) { + return array[lowestIndex]; + } + + return array[highestIndex]; + }, + /** + * Checks if a number is a power of two + * + * @param {number} n The number to check + * + * @return {bool} + */ + isPowerOfTwo: function(n){ + // Another idea would be isInteger( logBase(2, n) ) + // @see http://www.graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 + return ( n !== 0 && ( n & (n - 1) ) === 0 ); + }, + /** + * Calculates a logarithm of a number in an arbitrary base + * + * @param {number} base The logarithm base + * @param {number} n The number + * + * @return {number} logbase(n) + */ + logBase: function(base,n) { + return Math.log(n) / Math.log(base); + }, /** * Derives the note number in an equal tempered system given a reference frequency. * @@ -99,7 +99,7 @@ module.exports = { */ MIDIToName: function(n, pitchSet){ let name, - octave; + octave; n = Math.round(n); pitchSet = pitchSet ? this.pitchSort(pitchSet) : @@ -158,45 +158,45 @@ module.exports = { return null; }, - /** - * Transforms an rgb value into an hex value - * - * @param {string} rgb The rgb value, must be in format `rgb(r,g,b)` - * - * @return {string} The hex value of the color - */ - rgbToHex: function(rgb) { - if (/^#[0-9A-F]{6}$/i.test(rgb)) return rgb; + /** + * Transforms an rgb value into an hex value + * + * @param {string} rgb The rgb value, must be in format `rgb(r,g,b)` + * + * @return {string} The hex value of the color + */ + rgbToHex: function(rgb) { + if (/^#[0-9A-F]{6}$/i.test(rgb)) return rgb; - rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); - function hex(x) { - return ( "0" + parseInt(x).toString(16) ).slice(-2); - } - return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]); - }, - - /** - * @see {@link https://lodash.com/docs#debounce|lodash.debounce} - */ - debounce: require("lodash.debounce"), - /** - * @see {@link https://lodash.com/docs#findkey|lodash.findkey} - */ - findKey: require("lodash.findkey"), - /** - * @see {@link https://www.npmjs.com/package/frac|SheetJS's frac} - */ - fraction: require("frac"), - /** - * @see {@link https://lodash.com/docs#debounce|lodash.values} - */ - values: require("lodash.values"), + rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); + function hex(x) { + return ( "0" + parseInt(x).toString(16) ).slice(-2); + } + return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]); +}, + + /** + * @see {@link https://lodash.com/docs#debounce|lodash.debounce} + */ + debounce: require("lodash.debounce"), + /** + * @see {@link https://lodash.com/docs#findkey|lodash.findkey} + */ + findKey: require("lodash.findkey"), + /** + * @see {@link https://www.npmjs.com/package/frac|SheetJS's frac} + */ + fraction: require("frac"), + /** + * @see {@link https://lodash.com/docs#debounce|lodash.values} + */ + values: require("lodash.values"), /** - * @see {@link https://github.com/danigb/tonal/tree/master/packages/pitch-set|pitch-set} - */ - pitchSet: require("pitch-set"), + * @see {@link https://github.com/danigb/tonal/tree/master/packages/pitch-set|pitch-set} + */ + pitchSet: require("pitch-set"), /** - * @see {@link https://github.com/danigb/tonal/tree/master/packages/music-gamut|music-gamut.sort} - */ - pitchSort: require("music-gamut").sort + * @see {@link https://github.com/danigb/tonal/tree/master/packages/music-gamut|music-gamut.sort} + */ + pitchSort: require("music-gamut").sort }; \ No newline at end of file diff --git a/assets/js/lib/webkit-audiocontext-patch.js b/assets/js/lib/webkit-audiocontext-patch.js new file mode 100644 index 0000000..3cf8441 --- /dev/null +++ b/assets/js/lib/webkit-audiocontext-patch.js @@ -0,0 +1,49 @@ +/** + * Patch to port old implementation of `webkitAudioContext` + * to standards based `audioContext`. + * + * Props to Lajos György Mészáros for this patch. + * @see https://github.com/meszaros-lajos-gyorgy/meszaros-lajos-gyorgy.github.io/blob/master/microtonal/monochord/js/webkit-audio-context-patch.js + */ + +/* globals OscillatorNode */ + +"use strict"; + +module.exports = function(){ + if( !window.hasOwnProperty("AudioContext") && + window.hasOwnProperty("webkitAudioContext") ){ + var a = window.AudioContext = window.webkitAudioContext; + + if(!a.prototype.hasOwnProperty("createGain")){ + a.prototype.createGain = a.prototype.createGainNode; + } + + if(!OscillatorNode.prototype.hasOwnProperty("start")){ + OscillatorNode.prototype.start = OscillatorNode.prototype.noteOn; + } + // make the first parameter optional for firefox <30 + var oldStart = OscillatorNode.prototype.start; + OscillatorNode.prototype.start = function(t){ + oldStart(t || 0); + }; + + if(!OscillatorNode.prototype.hasOwnProperty("stop")){ + OscillatorNode.prototype.stop = OscillatorNode.prototype.noteOff; + } + // make the first parameter optional for firefox <30 + var oldStop = OscillatorNode.prototype.stop; + OscillatorNode.prototype.stop = function(t){ + oldStop(t || 0); + }; + + Object.defineProperty(OscillatorNode.prototype, "type", { + get : function(){ + return ["sine", "square", "sawtooth", "triangle", "custom"][this.type]; + }, + set : function(type){ + this.type = OscillatorNode.prototype[type.toUpperCase()]; + } + }); + } +}; \ No newline at end of file diff --git a/assets/js/overtones.js b/assets/js/overtones.js index 8dbf14a..929b5a5 100644 --- a/assets/js/overtones.js +++ b/assets/js/overtones.js @@ -32,8 +32,8 @@ const PITCH_SET = utils.pitchSet("P1 m2 M2 m3 M3 P4 4A P5 m6 M6 m7 M7"), * @param {jQuery} $element The jQuery object of the element */ var hideElementWhenIdle = utils.debounce(function($element){ - $element.removeClass("visible"); - }, 5000); + $element.removeClass("visible"); +}, 5000); /** * Given a frequency, it gets the closest A440 12T Equal tempered note. @@ -86,49 +86,49 @@ function stopAllPlayingSounds() { * @return void */ function animateOvertone(el, duration) { - var $el = $(el), - $spacesGroup = $el.find(".spaces"), - // We'll animate the circles from the inner to the outer, that's - // why we are reversing the array - $circles = $( $spacesGroup.find("g").get().reverse() ), - numbersOfCircles = $circles.length, - originalFill = "#FFFFFF", - fillColor = "#FFE08D"; - - // If it's already animating, it won't animate again - if( $el.find(".velocity-animating").length ) - return; - - el.classList.add("active"); - - // If there are no inner circles, the animation only fills the spaces - // with the fillColor - if( !numbersOfCircles ) { - $.Velocity( $spacesGroup, { - fill: fillColor - }, { duration: duration.attack * 1000 } ) - .then( function(spaces){ - $.Velocity( spaces, { fill: originalFill }); - el.classList.remove("active"); - }, { duration: duration.release * 1000 } ); - } - // If there are inner circles, we iterate through the circles and fill - // them progressively - else { - $circles.each(function(i){ - $.Velocity( this, { - fill: fillColor - }, { - delay: i * (duration.attack * 1000 / numbersOfCircles), - duration: duration.attack * 1000, - } ) - .then( function(circle){ - $.Velocity( circle, { fill: originalFill }); - if( i === $circles.length - 1 ) - el.classList.remove("active"); - }, { duration: duration.release * 1000 } ); - }); - } + var $el = $(el), + $spacesGroup = $el.find(".spaces"), + // We'll animate the circles from the inner to the outer, that's + // why we are reversing the array + $circles = $( $spacesGroup.find("g").get().reverse() ), + numbersOfCircles = $circles.length, + originalFill = "#FFFFFF", + fillColor = "#FFE08D"; + + // If it's already animating, it won't animate again + if( $el.find(".velocity-animating").length ) + return; + + el.classList.add("active"); + + // If there are no inner circles, the animation only fills the spaces + // with the fillColor + if( !numbersOfCircles ) { + $.Velocity( $spacesGroup, { + fill: fillColor + }, { duration: duration.attack * 1000 } ) + .then( function(spaces){ + $.Velocity( spaces, { fill: originalFill }); + el.classList.remove("active"); + }, { duration: duration.release * 1000 } ); + } + // If there are inner circles, we iterate through the circles and fill + // them progressively + else { + $circles.each(function(i){ + $.Velocity( this, { + fill: fillColor + }, { + delay: i * (duration.attack * 1000 / numbersOfCircles), + duration: duration.attack * 1000, + } ) + .then( function(circle){ + $.Velocity( circle, { fill: originalFill }); + if( i === $circles.length - 1 ) + el.classList.remove("active"); + }, { duration: duration.release * 1000 } ); + }); + } } /** @@ -142,47 +142,47 @@ function animateOvertone(el, duration) { * @return void */ function showIntervalDifferenceWithTuning(tone, tuning) { - var tuning = tuning || "12-TET", // @todo this doesn't do anything currently, placeholder + var tuning = tuning || "12-TET", // @todo this doesn't do anything currently, placeholder + + note = frequencyToNoteDetails(tone.frequency, App.baseTone.name), + centsDifference = note.centsDifference; + + $("#note-frequency") + // Set the base number from which to animate to the current frequency + .prop( "number", $("#note-frequency").text().match(/\d+/)[0] ) + .animateNumber({ + number: tone.frequency, + numberStep: function(now, tween){ + var flooredNumber = Math.floor(now), + $target = $(tween.elem); + + $target.text(flooredNumber + " Hz"); + } + }, 200); + + // Fills up the note name (disregarding the accidental) + $("#note-name").text( note.name[0] ); + // Fills up the note accidentals + $("#note-accidentals").html( note.accidentals ); + // Fills up the note octave + $("#note sub").text( note.octave ); + + // Fills up the bar indicating the cents difference: a difference of 0 + // would have the pointer at the center, with the extremes being 50 + $(".cents-difference.tuning") + .css("text-indent", centsDifference + "%") + .find(".cents").prop( "number", $(".tuning .cents").text() ).animateNumber({ + number: centsDifference, + numberStep: function(now, tween){ + var flooredNumber = Math.floor(now), + target = tween.elem; + target.innerHTML = flooredNumber > 0 ? "+" + flooredNumber : flooredNumber; + } + }, 200); - note = frequencyToNoteDetails(tone.frequency, App.baseTone.name), - centsDifference = note.centsDifference; - - $("#note-frequency") - // Set the base number from which to animate to the current frequency - .prop( "number", $("#note-frequency").text().match(/\d+/)[0] ) - .animateNumber({ - number: tone.frequency, - numberStep: function(now, tween){ - var flooredNumber = Math.floor(now), - $target = $(tween.elem); - - $target.text(flooredNumber + " Hz"); - } - }, 200); - - // Fills up the note name (disregarding the accidental) - $("#note-name").text( note.name[0] ); - // Fills up the note accidentals - $("#note-accidentals").html( note.accidentals ); - // Fills up the note octave - $("#note sub").text( note.octave ); - - // Fills up the bar indicating the cents difference: a difference of 0 - // would have the pointer at the center, with the extremes being 50 - $(".cents-difference.tuning") - .css("text-indent", centsDifference + "%") - .find(".cents").prop( "number", $(".tuning .cents").text() ).animateNumber({ - number: centsDifference, - numberStep: function(now, tween){ - var flooredNumber = Math.floor(now), - target = tween.elem; - target.innerHTML = flooredNumber > 0 ? "+" + flooredNumber : flooredNumber; - } - }, 200); - - $(".tuning .cent-bar").css("left", 50 + centsDifference / 2 + "%"); - - console.log(note.name, centsDifference); + $(".tuning .cent-bar").css("left", 50 + centsDifference / 2 + "%"); + + console.log(note.name, centsDifference); } /** @@ -203,16 +203,16 @@ function getIntervalName(a, b) { ratio = a; try { - intervalName = intervals[ ratio[1] + "/" + ratio[2] ].name; - } - catch(e) { - try { - intervalName = intervals[ ratio[2] + "/" + ratio[1] ].name; + intervalName = intervals[ ratio[1] + "/" + ratio[2] ].name; + } + catch(e) { + try { + intervalName = intervals[ ratio[2] + "/" + ratio[1] ].name; } catch(e) { intervalName = "Unknown interval"; } - } + } return intervalName; } @@ -227,25 +227,25 @@ function getIntervalName(a, b) { */ function showIntervalName(firstTone, secondTone) { var octaveReducedFrequency = tones.reduceToSameOctave(secondTone, firstTone), - ratio = utils.fraction(octaveReducedFrequency/firstTone.frequency, 999), - intervalName = getIntervalName(ratio), - centsDifference = Math.abs( firstTone.intervalInCents( - { frequency: octaveReducedFrequency } - ) ); - - $("#interval-name").text(intervalName); - - $("#interval sup").text( ratio[1] ); - $("#interval sub").text( ratio[2] ); - - $(".cents-difference.interval") - .css("text-indent", centsDifference / 12 + "%") - .find(".cents").prop( "number", $(".interval .cents").text() ) - .animateNumber( {number: centsDifference }, 200 ); - - $(".interval .cent-bar").css("left", centsDifference / 12 + "%"); - - return intervalName; + ratio = utils.fraction(octaveReducedFrequency/firstTone.frequency, 999), + intervalName = getIntervalName(ratio), + centsDifference = Math.abs( firstTone.intervalInCents( + { frequency: octaveReducedFrequency } + ) ); + + $("#interval-name").text(intervalName); + + $("#interval sup").text( ratio[1] ); + $("#interval sub").text( ratio[2] ); + + $(".cents-difference.interval") + .css("text-indent", centsDifference / 12 + "%") + .find(".cents").prop( "number", $(".interval .cents").text() ) + .animateNumber( {number: centsDifference }, 200 ); + + $(".interval .cent-bar").css("left", centsDifference / 12 + "%"); + + return intervalName; } /** @@ -260,17 +260,17 @@ function showIntervalName(firstTone, secondTone) { * @return void */ function fillSoundDetails(tones) { - $("#sound-details").addClass("visible"); - hideElementWhenIdle( $("#sound-details") ); - - if( !tones.length ) { - $("#sound-details").addClass("show-note").removeClass("show-interval"); - showIntervalDifferenceWithTuning(tones); - } - else { - $("#sound-details").addClass("show-interval").removeClass("show-note"); - showIntervalName( tones[0], tones[1] ); - } + $("#sound-details").addClass("visible"); + hideElementWhenIdle( $("#sound-details") ); + + if( !tones.length ) { + $("#sound-details").addClass("show-note").removeClass("show-interval"); + showIntervalDifferenceWithTuning(tones); + } + else { + $("#sound-details").addClass("show-interval").removeClass("show-note"); + showIntervalName( tones[0], tones[1] ); + } } /** @@ -285,22 +285,22 @@ function fillSoundDetails(tones) { * @return void */ function playIntervalOnSpiral(tones, idx) { - if ( !tones.length ) - return; - - tones[0].play(); - animateOvertone( $(".overtone")[idx - 1], tones[0].envelope ); - - if( App.options.groupNotes ) { - tones[1].play(); - animateOvertone( $(".overtone")[idx], tones[1].envelope ); - } - else { - setTimeout( function(){ - tones[1].play(); - animateOvertone( $(".overtone")[idx], tones[1].envelope ); - }, 250); - } + if ( !tones.length ) + return; + + tones[0].play(); + animateOvertone( $(".overtone")[idx - 1], tones[0].envelope ); + + if( App.options.groupNotes ) { + tones[1].play(); + animateOvertone( $(".overtone")[idx], tones[1].envelope ); + } + else { + setTimeout( function(){ + tones[1].play(); + animateOvertone( $(".overtone")[idx], tones[1].envelope ); + }, 250); + } } /** @@ -316,59 +316,59 @@ function playIntervalOnSpiral(tones, idx) { * @return void */ function playIntervalOnAxis(interval, tone) { - tones.playFrequency( App.baseTone.frequency ); - animateOvertone( $(".overtone")[0], App.baseTone.envelope ); - - /* - * Notes are grouped - */ - if( App.options.groupNotes ){ - if( App.options.octaveReduction ) { - tone.play(); - animateOvertone( $(".overtone")[interval - 1], tone.envelope ); - } - else { - // Loop through the first overtone and all the octaves of the same interval - while( $("#overtone-" + interval).length ) { - tones.playFrequency( interval * App.baseTone.frequency ); - animateOvertone( $(".overtone")[interval - 1], tone.envelope ); - interval = interval * 2; - } - - tone.remove(); - } - } - /* - * Notes are played sequentially - */ - else { - if( App.options.octaveReduction ){ - setTimeout(function(){ - tone.play(); - animateOvertone( $(".overtone")[interval - 1], tone.envelope ); - }, 250); - } - else { - var axisIntervals = []; - - // Push into an array all the octaves of the same interval present - // on one particular axis - while( $("#overtone-" + interval).length ) { - axisIntervals.push(interval); - interval = interval * 2; - } - - // For each of them, play them sequentially with a delay of 250ms - axisIntervals.forEach(function(interval, idx){ - setTimeout(function(){ - tones.playFrequency( interval * App.baseTone.frequency ); - animateOvertone( $(".overtone")[interval - 1], tone.envelope ); - }, 250 * (idx + 1)); - }); - - tone.remove(); - } - } + tones.playFrequency( App.baseTone.frequency ); + animateOvertone( $(".overtone")[0], App.baseTone.envelope ); + + /* + * Notes are grouped + */ + if( App.options.groupNotes ){ + if( App.options.octaveReduction ) { + tone.play(); + animateOvertone( $(".overtone")[interval - 1], tone.envelope ); + } + else { + // Loop through the first overtone and all the octaves of the same interval + while( $("#overtone-" + interval).length ) { + tones.playFrequency( interval * App.baseTone.frequency ); + animateOvertone( $(".overtone")[interval - 1], tone.envelope ); + interval = interval * 2; + } + + tone.remove(); + } + } + /* + * Notes are played sequentially + */ + else { + if( App.options.octaveReduction ){ + setTimeout(function(){ + tone.play(); + animateOvertone( $(".overtone")[interval - 1], tone.envelope ); + }, 250); + } + else { + var axisIntervals = []; + + // Push into an array all the octaves of the same interval present + // on one particular axis + while( $("#overtone-" + interval).length ) { + axisIntervals.push(interval); + interval = interval * 2; + } + + // For each of them, play them sequentially with a delay of 250ms + axisIntervals.forEach(function(interval, idx){ + setTimeout(function(){ + tones.playFrequency( interval * App.baseTone.frequency ); + animateOvertone( $(".overtone")[interval - 1], tone.envelope ); + }, 250 * (idx + 1)); + }); + + tone.remove(); + } + } } /** @@ -379,14 +379,14 @@ function playIntervalOnAxis(interval, tone) { * @return {bool} The new option state */ function toggleOption(option) { - App.options[option] = !App.options[option]; - $("[data-option=" + option + "]").toggleClass("off"); + App.options[option] = !App.options[option]; + $("[data-option=" + option + "]").toggleClass("off"); $(document).trigger({ type: "overtones:options:change", details: { optionName: option, optionValue: App.options[option] } }); - - return App.options[option]; + + return App.options[option]; } /** @@ -412,20 +412,20 @@ function updateBaseFrequency(val, mute) { tones.sounds[ tones.sounds.indexOf(App.baseTone) ].remove(); // Remove the base tone stopAllPlayingSounds(); - App.baseTone = tones.createSound(val); + App.baseTone = tones.createSound(val); App.baseTone.name = frequencyToNoteDetails(val).name; - $("#base, #base-detail").val(val); - - if( !mute && !App.options.sustain ) - $("#overtone-1").click(); + $("#base, #base-detail").val(val); + + if( !mute && !App.options.sustain ) + $("#overtone-1").click(); $(document).trigger({ type: "overtones:options:change", details: { optionName: "baseFrequency", optionValue: val } }); - - return val; + + return val; } /** @@ -437,11 +437,11 @@ function updateBaseFrequency(val, mute) { * @return {number} The new volume (scale 0–1) */ function updateVolume(val, mute) { - val /= 100; + val /= 100; - App.currentVolume = val; - tones.masterGain.gain.setValueAtTime(App.currentVolume, tones.context.currentTime); - if (!mute) { + App.currentVolume = val; + tones.masterGain.gain.setValueAtTime(App.currentVolume, tones.context.currentTime); + if (!mute) { tones.playFrequency( App.baseTone.frequency ); $(document).trigger({ @@ -449,8 +449,8 @@ function updateVolume(val, mute) { details: { optionName: "mainVolume", optionValue: val } }); } - - return val; + + return val; } /** @@ -464,11 +464,11 @@ function updateVolume(val, mute) { * @return void */ function overtoneClickHandler() { - var idx = $(this).index() + 1, - soundPlaying = $(this).data("isPlaying"), - self = this, - noteFrequency = idx * App.baseTone.frequency, - tone; + var idx = $(this).index() + 1, + soundPlaying = $(this).data("isPlaying"), + self = this, + noteFrequency = idx * App.baseTone.frequency, + tone; if(soundPlaying){ soundPlaying.fadeOut(); @@ -484,8 +484,8 @@ function overtoneClickHandler() { tone = tones.createSound(noteFrequency); tone.fromClick = true; - if( App.options.octaveReduction && tone.frequency !== App.baseTone.frequency ) - tone.reduceToSameOctaveAs(App.baseTone); + if( App.options.octaveReduction && tone.frequency !== App.baseTone.frequency ) + tone.reduceToSameOctaveAs(App.baseTone); if( App.options.sustain ){ let lastSoundPlaying = utils.findLast(tones.sounds, function(){ return this.isPlaying; }, 1); @@ -508,9 +508,9 @@ function overtoneClickHandler() { } else fillSoundDetails(tone); - tone.play(); + tone.play(); - animateOvertone( self, tone.envelope ); + animateOvertone( self, tone.envelope ); $(document).trigger({ type: "overtones:play", @@ -529,24 +529,24 @@ function overtoneClickHandler() { * @return void */ function spiralPieceClickHandler() { - var idx = $(this).index() + 1, - firstTone = tones.createSound(idx * App.baseTone.frequency), - secondTone = tones.createSound( (idx + 1) * App.baseTone.frequency ); + var idx = $(this).index() + 1, + firstTone = tones.createSound(idx * App.baseTone.frequency), + secondTone = tones.createSound( (idx + 1) * App.baseTone.frequency ); - if( App.options.octaveReduction ){ - firstTone.reduceToSameOctaveAs(App.baseTone, true); - secondTone.reduceToSameOctaveAs(App.baseTone); - } + if( App.options.octaveReduction ){ + firstTone.reduceToSameOctaveAs(App.baseTone, true); + secondTone.reduceToSameOctaveAs(App.baseTone); + } - playIntervalOnSpiral( [firstTone, secondTone], idx ); + playIntervalOnSpiral( [firstTone, secondTone], idx ); - fillSoundDetails( [firstTone, secondTone] ); + fillSoundDetails( [firstTone, secondTone] ); $(document).trigger({ type: "overtones:play", - details: { - element: "spiral", - idx: idx, + details: { + element: "spiral", + idx: idx, interval: getIntervalName(firstTone, secondTone), options: App.options } @@ -564,14 +564,14 @@ function spiralPieceClickHandler() { * @return void */ function axisClickHandler() { - var interval = parseInt( $(this).data("interval") ), - tone = tones - .createSound(interval * App.baseTone.frequency) - .reduceToSameOctaveAs(App.baseTone); + var interval = parseInt( $(this).data("interval") ), + tone = tones + .createSound(interval * App.baseTone.frequency) + .reduceToSameOctaveAs(App.baseTone); - playIntervalOnAxis(interval, tone); + playIntervalOnAxis(interval, tone); - fillSoundDetails( [App.baseTone, tone] ); + fillSoundDetails( [App.baseTone, tone] ); $(document).trigger({ type: "overtones:play", @@ -595,8 +595,8 @@ function axisClickHandler() { */ function baseInputHandler(e){ const ARROW_UP = 38, - ARROW_DOWN = 40, - $this = $(this); + ARROW_DOWN = 40, + $this = $(this); let currentValue = +$this.get(0).value; @@ -633,67 +633,66 @@ function baseInputHandler(e){ * @return void */ function init() { - updateVolume( $("#volume-control").val(), true ); + updateVolume( $("#volume-control").val(), true ); App.baseTone.name = frequencyToNoteDetails(App.baseTone.frequency).name; - - $(".overtone").on("click", overtoneClickHandler); - $(".spiral-piece").on("click", spiralPieceClickHandler); - $(".axis").on("click", axisClickHandler); + $(".overtone").on("click", overtoneClickHandler); + $(".spiral-piece").on("click", spiralPieceClickHandler); + $(".axis").on("click", axisClickHandler); + $("#base-detail").on("keydown", baseInputHandler); - - $("#base, #base-detail").on("change", function(){ - updateBaseFrequency( $(this).val() ); - }); - - $("#volume-control").on("change", function(){ - updateVolume( $(this).val() ); - }); - - $("[data-option]").on("click", function(){ - toggleOption( $(this).data("option") ); - }); - + + $("#base, #base-detail").on("change", function(){ + updateBaseFrequency( $(this).val() ); + }); + + $("#volume-control").on("change", function(){ + updateVolume( $(this).val() ); + }); + + $("[data-option]").on("click", function(){ + toggleOption( $(this).data("option") ); + }); + $(document).on("overtones:options:change", function(e){ if( e.details.optionName === "sustain" && e.details.optionValue === false ) stopAllPlayingSounds(); }); - } var App = { - /** - * The fundamental tone from which to calculate the overtones values - * - * @alias module:overtones.baseTone - * - * @type {Sound} - */ - baseTone: tones.createSound( $("#base").val() ), - init: init, - /** - * @alias module:overtones.options - * - * @type {object} - * @property {bool} groupNotes If set to `true`, it will play the notes - * at the same time. - * @property {bool} octaveReduction If set to `true` all notes will be played - * on the same octave of the fundamental tone. - * See {@link baseTone}. - */ - options: { - groupNotes: true, - octaveReduction: false - }, - /** - * Frequency data for various tunings - * - * @alias module:overtones.tunings - * - * @type {object} - */ - tunings: { - } + /** + * The fundamental tone from which to calculate the overtones values + * + * @alias module:overtones.baseTone + * + * @type {Sound} + */ + baseTone: tones.createSound( $("#base").val() ), + init: init, + /** + * @alias module:overtones.options + * + * @type {object} + * @property {bool} groupNotes If set to `true`, it will play the notes + * at the same time. + * @property {bool} octaveReduction If set to `true` all notes will be played + * on the same octave of the fundamental tone. + * See {@link baseTone}. + */ + options: { + groupNotes: true, + octaveReduction: false + }, + /** + * Frequency data for various tunings + * + * @alias module:overtones.tunings + * + * @type {object} + */ + tunings: { + } }; module.exports = App; \ No newline at end of file diff --git a/assets/js/script.js b/assets/js/script.js index 77d58f8..8ed1810 100644 --- a/assets/js/script.js +++ b/assets/js/script.js @@ -1,21 +1,19 @@ -/* globals Overtones */ - "use strict"; var jQuery = require("jquery"), browser = require("detect-browser"), - analytics = require("./analytics"), + analytics = require("./analytics"), tour = require("./shepherd.conf.js"), - easterEgg = require("./easter-egg"); + easterEgg = require("./easter-egg"); window.Tones = require("./lib/tones"); window.Overtones = require("./overtones"); jQuery(document).ready(function($){ - $("body").addClass(browser.name); // This makes me sad, it's 2016 Firefox! - - tour.init(); - Overtones.init(); + $("body").addClass(browser.name); // This makes me sad, it's 2016 Firefox! + + tour.init(); + window.Overtones.init(); analytics($); easterEgg.init(); diff --git a/assets/js/shepherd.conf.js b/assets/js/shepherd.conf.js index f78848d..078f853 100644 --- a/assets/js/shepherd.conf.js +++ b/assets/js/shepherd.conf.js @@ -14,41 +14,41 @@ require('chardin.js'); // `Sheperd.on` is sadly not chainable Shepherd.on('show', function(tour){ var step = tour.step, - stepIndex = tour.tour.steps.indexOf(step) + 1; + stepIndex = tour.tour.steps.indexOf(step) + 1; $(document).trigger({ type: "overtones:help:show", idx: stepIndex }); }); Shepherd.on('cancel', function(tour){ var step = tour.tour.currentStep, - stepIndex = tour.tour.steps.indexOf(step) + 1; + stepIndex = tour.tour.steps.indexOf(step) + 1; $(document).trigger({ type: "overtones:help:close", idx: stepIndex }); }); Shepherd.on('complete', function(tour){ var step = tour.step, - stepIndex = tour.tour.steps.indexOf(step) + 1; + stepIndex = tour.tour.steps.indexOf(step) + 1; $(document).trigger({ type: "overtones:help:complete" }); }); mainTour = new Shepherd.Tour({ - defaults: { - classes: 'shepherd-theme-dark', - showCancelLink: true - } + defaults: { + classes: 'shepherd-theme-dark', + showCancelLink: true + } }); mainTour .addStep('overtone-spiral', { - text: ['This is a visual representation of a fundamental sound and its overtones.', - 'Each complete spiral revolution represents an octave.'], - attachTo: '#Overtone-Spiral right', - title: '1/11', - tetherOptions: { - offset: '0 -220px' - }, + text: ['This is a visual representation of a fundamental sound and its overtones.', + 'Each complete spiral revolution represents an octave.'], + attachTo: '#Overtone-Spiral right', + title: '1/11', + tetherOptions: { + offset: '0 -220px' + }, when: { 'before-show': function() { if( $('body').data('shepherd-step') === 'newbie-help' ){ @@ -60,145 +60,145 @@ mainTour } }) .addStep('fundamental-overtone', { - text: ['The circle at the center of the spiral represents the fundamental tone, ' + + text: ['The circle at the center of the spiral represents the fundamental tone, ' + 'first partial of the harmonic series.', 'Each other circle represents the following partials, up to the 16th.', - 'Click on the circle to hear the sound.'], - attachTo: '#overtone-1 bottom', - title: '2/11', - buttons: {}, - advanceOn: { - selector: '.overtone, .space', - event: 'click' - } + 'Click on the circle to hear the sound.'], + attachTo: '#overtone-1 bottom', + title: '2/11', + buttons: {}, + advanceOn: { + selector: '.overtone, .space', + event: 'click' + } }) .addStep('note-details', { - text: ['Here you can see information about the sound you just heard'], - attachTo: '#sound-details bottom', - title: '3/11', - when: { - show: function() { - $('body').chardinJs('start'); - $('#overtone-1').addClass('shepherd-enabled'); - }, - hide: function() { - $('body').chardinJs('stop'); - $('#overtone-1').removeClass('shepherd-enabled'); - } - }, - tetherOptions: { - offset: '-20px 70px' - } + text: ['Here you can see information about the sound you just heard'], + attachTo: '#sound-details bottom', + title: '3/11', + when: { + show: function() { + $('body').chardinJs('start'); + $('#overtone-1').addClass('shepherd-enabled'); + }, + hide: function() { + $('body').chardinJs('stop'); + $('#overtone-1').removeClass('shepherd-enabled'); + } + }, + tetherOptions: { + offset: '-20px 70px' + } }) .addStep('spiral-pieces', { - text: ['You can also click on other places of the spiral, such as the purple pieces connecting two circles.'], - attachTo: '.spiral-piece:nth-of-type(2) bottom', - title: '4/11', - buttons: {}, - advanceOn: { - selector: '.spiral-piece', - event: 'click' - } + text: ['You can also click on other places of the spiral, such as the purple pieces connecting two circles.'], + attachTo: '.spiral-piece:nth-of-type(2) bottom', + title: '4/11', + buttons: {}, + advanceOn: { + selector: '.spiral-piece', + event: 'click' + } }) .addStep('interval-details', { - text: ['In this case you see the information about the relationship between the notes you just heard.', + text: ['In this case you see the information about the relationship between the notes you just heard.', 'The spiral pieces are thus representations of intervals in the harmonic series.'], - attachTo: '#sound-details bottom', - title: '5/11', - when: { - show: function() { - $('body').chardinJs('start'); - $('.spiral-piece').eq(1).addClass('shepherd-enabled'); - }, - hide: function() { - $('body').chardinJs('stop'); - $('.spiral-piece').eq(1).removeClass('shepherd-enabled'); - } - }, - tetherOptions: { - offset: '-20px 70px' - } + attachTo: '#sound-details bottom', + title: '5/11', + when: { + show: function() { + $('body').chardinJs('start'); + $('.spiral-piece').eq(1).addClass('shepherd-enabled'); + }, + hide: function() { + $('body').chardinJs('stop'); + $('.spiral-piece').eq(1).removeClass('shepherd-enabled'); + } + }, + tetherOptions: { + offset: '-20px 70px' + } }) .addStep('options-base', { - text: ['You can change the frequency of the fundamental note by using this slider.', - 'You can also directly change the number if you want finer tuning!'], - attachTo: '#base-wrapper right', - title: '6/11', - advanceOn: { - selector: '#base', - event: 'change' - }, - tetherOptions: { - offset: '-20px 0' - } + text: ['You can change the frequency of the fundamental note by using this slider.', + 'You can also directly change the number if you want finer tuning!'], + attachTo: '#base-wrapper right', + title: '6/11', + advanceOn: { + selector: '#base', + event: 'change' + }, + tetherOptions: { + offset: '-20px 0' + } }) .addStep('options-volume', { - text: ['You can change the volume of all the sounds through this slider.', - 'Be careful if you are wearing headphones! Higher overtones especially are going to sound piercing loud.'], - attachTo: '#volume-control-wrapper right', - advanceOn: { - selector: '#volume-control', - event: 'change' - }, - title: '7/11' + text: ['You can change the volume of all the sounds through this slider.', + 'Be careful if you are wearing headphones! Higher overtones especially are going to sound piercing loud.'], + attachTo: '#volume-control-wrapper right', + advanceOn: { + selector: '#volume-control', + event: 'change' + }, + title: '7/11' }) .addStep('options-group', { - text: ['If you turn this option off, you will hear notes separately when playing intervals.'], - attachTo: '#group-notes bottom', - title: '8/11', - tetherOptions: { - offset: '-20px 0' - } + text: ['If you turn this option off, you will hear notes separately when playing intervals.'], + attachTo: '#group-notes bottom', + title: '8/11', + tetherOptions: { + offset: '-20px 0' + } }) .addStep('options-octave', { - text: ['If you turn this option on, all the sounds will be played on frequencies within one octave of the fundamental tone.'], - attachTo: '#reduce-to-octave bottom', - title: '9/11', - tetherOptions: { - offset: '-20px 0' - } + text: ['If you turn this option on, all the sounds will be played on frequencies within one octave of the fundamental tone.'], + attachTo: '#reduce-to-octave bottom', + title: '9/11', + tetherOptions: { + offset: '-20px 0' + } }) .addStep('options-sustain', { - text: ['If you turn this option on, the sounds will play continuously until manually stopped.', - 'You can manually stop the sounds by clicking again on the circle, turning this option off, or changing the base tone', - 'When this option is on, the information displayed is the interval between the sound you play and the last active sound. This allows you to explore all the intervals within the spiral.'], - attachTo: '#sustain bottom', - title: '10/11', - tetherOptions: { - offset: '-20px 0' - } + text: ['If you turn this option on, the sounds will play continuously until manually stopped.', + 'You can manually stop the sounds by clicking again on the circle, turning this option off, or changing the base tone', + 'When this option is on, the information displayed is the interval between the sound you play and the last active sound. This allows you to explore all the intervals within the spiral.'], + attachTo: '#sustain bottom', + title: '10/11', + tetherOptions: { + offset: '-20px 0' + } }) .addStep('end-tour', { - text: ['That is all! Enjoy!'], - title: '11/11', - buttons: [ - { - text: 'Thank you!', - action: mainTour.next - } - ] + text: ['That is all! Enjoy!'], + title: '11/11', + buttons: [ + { + text: 'Thank you!', + action: mainTour.next + } + ] }); newbieTour = new Shepherd.Tour({ - defaults: { - classes: 'shepherd-theme-dark newbie-tour', - showCancelLink: true, - tetherOptions: { - offset: '80px -20px', - } - } + defaults: { + classes: 'shepherd-theme-dark newbie-tour', + showCancelLink: true, + tetherOptions: { + offset: '80px -20px', + } + } }); newbieTour .addStep('newbie-help', { - text: 'You can click this button at any time to have a quick rundown of what\'s going on.', - attachTo: '#help right', - title: 'Confused by what you see?', - buttons: [ - { - text: 'No, thanks, I\'m good', - action: function(){ + text: 'You can click this button at any time to have a quick rundown of what\'s going on.', + attachTo: '#help right', + title: 'Confused by what you see?', + buttons: [ + { + text: 'No, thanks, I\'m good', + action: function(){ newbieTour.next(); $(document).trigger({ @@ -206,49 +206,49 @@ newbieTour idx: 0 }); }, - classes: "button-cancel" - }, - { - text: 'Show me around!', - action: function() { - newbieTour.next(); - mainTour.start(); + classes: "button-cancel" + }, + { + text: 'Show me around!', + action: function() { + newbieTour.next(); + mainTour.start(); $(document).trigger({ type: "overtones:help:next", idx: 0 }); - } - } - ] + } + } + ] }); module.exports = { - mainTour: mainTour, - init: function(){ - setTimeout(function(){ - once(function(){ newbieTour.start(); }, 'newbieTour'); - }, 2000); - - $('#help').on('click', function(e){ - e.preventDefault(); - mainTour.start(); - }); - - $(".spiral-piece, .overtone, .axis").on("click", function(e){ - var $body = $("body"), - currentStep = mainTour.currentStep, - idx = currentStep ? currentStep.tour.steps.indexOf(currentStep) + 1 : 0; - - if( $body.hasClass("shepherd-active") && !$(e.delegateTarget).hasClass("shepherd-enabled") ){ - e.stopImmediatePropagation(); - - $(document).trigger({ - type: "overtones:help:denied", - target: e.target, + mainTour: mainTour, + init: function(){ + setTimeout(function(){ + once(function(){ newbieTour.start(); }, 'newbieTour'); + }, 2000); + + $('#help').on('click', function(e){ + e.preventDefault(); + mainTour.start(); + }); + + $(".spiral-piece, .overtone, .axis").on("click", function(e){ + var $body = $("body"), + currentStep = mainTour.currentStep, + idx = currentStep ? currentStep.tour.steps.indexOf(currentStep) + 1 : 0; + + if( $body.hasClass("shepherd-active") && !$(e.delegateTarget).hasClass("shepherd-enabled") ){ + e.stopImmediatePropagation(); + + $(document).trigger({ + type: "overtones:help:denied", + target: e.target, idx: idx }); } - }); - }, + }); + }, }; \ No newline at end of file diff --git a/assets/js/webfont.js b/assets/js/webfont.js index fb9dea0..f335337 100644 --- a/assets/js/webfont.js +++ b/assets/js/webfont.js @@ -1,7 +1,7 @@ var WebFont = require("webfontloader"); WebFont.load({ - google: { - families: ["Sintony:400,700", "Judson"] - } + google: { + families: ["Sintony:400,700", "Judson"] + } }); \ No newline at end of file diff --git a/assets/styles/_base.scss b/assets/styles/_base.scss index 326017e..ba7d51c 100644 --- a/assets/styles/_base.scss +++ b/assets/styles/_base.scss @@ -1,70 +1,70 @@ -body { - background-color: #222; - color: #fff; - font-family: $baseFontFamily; +body { + background-color: #222; + color: #fff; + font-family: $baseFontFamily; } h2 { - font-weight: 100; - margin: 0; + font-weight: 100; + margin: 0; } a { - color: #3288e6; - text-decoration: none; - - &:hover { - color: #007EA1; - } + color: #3288e6; + text-decoration: none; + + &:hover { + color: #007EA1; + } } img { - max-width: 100%; + max-width: 100%; } area { - cursor: pointer; + cursor: pointer; } svg { - display: block; - margin: 0px auto; - max-width: 100%; - z-index: 2; - height: 97vh; - position: relative; + display: block; + margin: 0px auto; + max-width: 100%; + z-index: 2; + height: 97vh; + position: relative; } body > footer { - box-sizing: border-box; - padding: 10px 20px; - z-index: 3; - width: 100%; - position: absolute; - bottom: 0; - left: 0; - - .social { - line-height: 35px; - margin-top: 20px; - height: 35px; - position: absolute; - top: 0; - right: 10px; - - > * { - line-height: 20px; - margin-right: 10px; - vertical-align: middle; - } - } + box-sizing: border-box; + padding: 10px 20px; + z-index: 3; + width: 100%; + position: absolute; + bottom: 0; + left: 0; + + .social { + line-height: 35px; + margin-top: 20px; + height: 35px; + position: absolute; + top: 0; + right: 10px; + + > * { + line-height: 20px; + margin-right: 10px; + vertical-align: middle; + } + } } p.credits { - display: inline-block; - font-size: 12px; - line-height: 35px; - margin-left: 50px; + display: inline-block; + font-size: 12px; + line-height: 35px; + margin-left: 50px; } .version { margin-left: 1em; } diff --git a/assets/styles/_browsers.scss b/assets/styles/_browsers.scss index d2f7ca0..9051b04 100644 --- a/assets/styles/_browsers.scss +++ b/assets/styles/_browsers.scss @@ -10,12 +10,12 @@ */ .firefox { - .overtone { opacity: 0.9; } - - .overtone:hover, - .overtone:active, - .overtone.active { - opacity: 1; - transform: scale(1); - } + .overtone { opacity: 0.9; } + + .overtone:hover, + .overtone:active, + .overtone.active { + opacity: 1; + transform: scale(1); + } } \ No newline at end of file diff --git a/assets/styles/_components/_help.scss b/assets/styles/_components/_help.scss index 6057a79..5d1cb06 100644 --- a/assets/styles/_components/_help.scss +++ b/assets/styles/_components/_help.scss @@ -1,502 +1,502 @@ #help { - @include has-tooltip("Explain me what I'm seeing", top); - - background-color: #666; - border: 0; - border-radius: 100%; - color: #222; - cursor: pointer; - font-family: $serifFontFamily; - font-size: 28px; - line-height: 35px; - padding: 0; - text-align: center; - z-index: 3; - width: 35px; - height: 35px; - - &:hover { - background-color: #d4d4d4; - } - - &:focus { - outline: 0; - } + @include has-tooltip("Explain me what I'm seeing", top); + + background-color: #666; + border: 0; + border-radius: 100%; + color: #222; + cursor: pointer; + font-family: $serifFontFamily; + font-size: 28px; + line-height: 35px; + padding: 0; + text-align: center; + z-index: 3; + width: 35px; + height: 35px; + + &:hover { + background-color: #d4d4d4; + } + + &:focus { + outline: 0; + } } .chardinjs-overlay { - background-color: #000; - opacity: 0; - transition: all 0.3s ease-out; - z-index: 5; - position: absolute; + background-color: #000; + opacity: 0; + transition: all 0.3s ease-out; + z-index: 5; + position: absolute; } .chardinjs-helper-layer { - color: #fff; - transition: all 0.3s ease-out; - z-index: 4; - position: absolute; - - &.chardinjs-left { - border-left: solid #fff 1px; - margin-left: -10px; - } - - &.chardinjs-right { - border-right: solid #fff 1px; - margin-right: -10px; - } - - &.chardinjs-bottom { - border-bottom: solid #fff 1px; - margin-bottom: -10px; - } - - &.chardinjs-top { - border-top: solid #fff 1px; - margin-top: -10px; - } + color: #fff; + transition: all 0.3s ease-out; + z-index: 4; + position: absolute; + + &.chardinjs-left { + border-left: solid #fff 1px; + margin-left: -10px; + } + + &.chardinjs-right { + border-right: solid #fff 1px; + margin-right: -10px; + } + + &.chardinjs-bottom { + border-bottom: solid #fff 1px; + margin-bottom: -10px; + } + + &.chardinjs-top { + border-top: solid #fff 1px; + margin-top: -10px; + } } .chardinjs-tooltip { - max-width: 250px; - transition: opacity 0.1s ease-out; - position: absolute; - - &.chardinjs-left { - margin-left: -135px; - padding-right: 10px; - } - - &.chardinjs-right { - margin-right: -135px; - padding-left: 10px; - } - - &.chardinjs-bottom { - margin-bottom: -135px; - padding-top: 10px; - } - - &.chardinjs-top { - margin-top: -135px; - padding-bottom: 10px; - } - - &.chardinjs-right:before, &.chardinjs-left:after, &.chardinjs-bottom:before, &.chardinjs-top:after { - background-color: #fff; - content: "."; - display: inline-block; - overflow: hidden; - height: 1px; - position: absolute; - } + max-width: 250px; + transition: opacity 0.1s ease-out; + position: absolute; + + &.chardinjs-left { + margin-left: -135px; + padding-right: 10px; + } + + &.chardinjs-right { + margin-right: -135px; + padding-left: 10px; + } + + &.chardinjs-bottom { + margin-bottom: -135px; + padding-top: 10px; + } + + &.chardinjs-top { + margin-top: -135px; + padding-bottom: 10px; + } + + &.chardinjs-right:before, &.chardinjs-left:after, &.chardinjs-bottom:before, &.chardinjs-top:after { + background-color: #fff; + content: "."; + display: inline-block; + overflow: hidden; + height: 1px; + position: absolute; + } - &.chardinjs-right:before, &.chardinjs-left:after { - width: 100px; - top: 50%; - } + &.chardinjs-right:before, &.chardinjs-left:after { + width: 100px; + top: 50%; + } - &.chardinjs-bottom:before, &.chardinjs-top:after { - width: 1px; - height: 50px; - left: 50%; - } + &.chardinjs-bottom:before, &.chardinjs-top:after { + width: 1px; + height: 50px; + left: 50%; + } - &.chardinjs-bottom:before { top: -50px; } - &.chardinjs-top:after { bottom: -50px; } - &.chardinjs-right:before { left: -100px; } - &.chardinjs-left:after { right: -100px; } + &.chardinjs-bottom:before { top: -50px; } + &.chardinjs-top:after { bottom: -50px; } + &.chardinjs-right:before { left: -100px; } + &.chardinjs-left:after { right: -100px; } } .chardinjs-tooltiptext { - box-shadow: 0 0 16px rgba(0, 0, 0, 0.5); - line-height: 16px; + box-shadow: 0 0 16px rgba(0, 0, 0, 0.5); + line-height: 16px; } .chardinjs-show-element { - opacity: 0.8; - z-index: 5; + opacity: 0.8; + z-index: 5; } .chardinjs-relative-position { position: relative; } .shepherd-element { - box-sizing: border-box; - display: none; - font-size: 1em; - max-height: 100%; - max-width: 100%; - max-width: 24em; - z-index: 4; - position: absolute; - - &.shepherd-open { - display: block; - } - - &.shepherd-has-cancel-link .shepherd-content header h3 { - float: left; } - - &.shepherd-has-title .shepherd-content header { - background: #303030; - padding: 1em; - - a.shepherd-cancel-link { - margin-bottom: 0; - padding: 0; - } - } - - * { box-sizing: border-box; } - - .shepherd-content { - background: #232323; - border-radius: 5px; - box-shadow: 0 0 1em rgba(0, 0, 0, 0.2); - color: #eee; - font-family: inherit; - font-size: 16px; - line-height: 24px; - padding: 1em; - padding: 0; - position: relative; - - &::before { - border-bottom-color: #303030; - border-color: transparent; - border-style: solid; - border-width: 16px; - content: ""; - display: block; - pointer-events: none; - width: 0; - height: 0; - position: absolute; - } - - header { - border-radius: 5px 5px 0 0; - *zoom: 1; - - &::after { - clear: both; - content: ""; - display: table; - } - - h3 { - font-weight: normal; - line-height: 1; - margin: 0; - } - - a.shepherd-cancel-link { - color: rgba(255, 255, 255, 0.8); - float: right; - font-size: 1.25em; - font-weight: normal; - line-height: 0.8em; - margin-bottom: -0.8em; - opacity: 0.25; - padding: 0.8em; - text-decoration: none; - position: relative; - top: 0.1em; - - &:hover { opacity: 1; } - } - } - - footer { - padding: 0 1em 1em; - - .shepherd-buttons { - list-style: none; - margin: 0; - padding: 0; - text-align: right; - - li { - display: inline; - margin: 0; - padding: 0; - - &:last-child .shepherd-button { - margin-right: 0; - } - } - } - - .shepherd-button { - background: #3288e6; - border: 0; - border-radius: 3px; - color: #fff; - cursor: pointer; - display: inline-block; - font-family: inherit; - font-size: .8em; - letter-spacing: .1em; - line-height: 1em; - margin: 0 .5em 0 0; - padding: .75em 2em; - text-transform: uppercase; - vertical-align: middle; - - &.shepherd-button-secondary, - &.button-cancel { - background: #eee; - color: #888; - } - } - } - - small { - display: block; - font-size: 12px; - line-height: 16px; - } - - .shepherd-title { - color: rgba(255, 255, 255, 0.5); - } - - .shepherd-text { - padding: 1em; - - p { - margin: 0 0 0.5em 0; - - &:last-child { margin-bottom: 0; } - } - } - } - - &.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content { - margin-bottom: 16px; - - &::before { - border-top-color: #232323; - margin-left: -16px; - top: 100%; - left: 50%; - } - } - - &.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content { - margin-top: 16px; - - &::before { - border-bottom-color: #232323; - margin-left: -16px; - bottom: 100%; - left: 50%; - } - } - - &.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content { - margin-right: 16px; - - &::before { - border-left-color: #232323; - margin-top: -16px; - top: 50%; - left: 100%; - } - } - - &.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content { - margin-left: 16px; - - &::before { - border-right-color: #232323; - margin-top: -16px; - top: 50%; - right: 100%; - } - } - - &.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content { - margin-top: 16px; - - &::before { - bottom: 100%; - left: 16px; - border-bottom-color: #232323; - } - } - - &.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content { - margin-top: 16px; - - &::before { - border-bottom-color: #232323; - right: 16px; - bottom: 100%; - } - } - - &.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content { - margin-bottom: 16px; - - &::before { - border-top-color: #232323; - top: 100%; - left: 16px; - } - } - - &.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content { - margin-bottom: 16px; - - &::before { - border-top-color: #232323; - top: 100%; - right: 16px; - } - } - - &.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content { - margin-right: 16px; - - &::before { - border-left-color: #232323; - top: 16px; - left: 100%; - } - } - - &.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content { - margin-left: 16px; - - &::before { - border-right-color: #232323; - top: 16px; - right: 100%; - } - } - - &.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content { - margin-right: 16px; - - &::before { - border-left-color: #232323; - bottom: 16px; - left: 100%; - } - } - - &.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content { - margin-left: 16px; - - &::before { - border-right-color: #232323; - right: 100%; - bottom: 16px; - } - } - - &.shepherd-element-attached-top.shepherd-element-attached-center.shepherd-has-title, - &.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-has-title, - &.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-has-title { - .shepherd-content::before { border-bottom-color: #303030; } - } - - &.newbie-tour { - max-width: none; + box-sizing: border-box; + display: none; + font-size: 1em; + max-height: 100%; + max-width: 100%; + max-width: 24em; + z-index: 4; + position: absolute; + + &.shepherd-open { + display: block; + } + + &.shepherd-has-cancel-link .shepherd-content header h3 { + float: left; } + + &.shepherd-has-title .shepherd-content header { + background: #303030; + padding: 1em; + + a.shepherd-cancel-link { + margin-bottom: 0; + padding: 0; + } + } + + * { box-sizing: border-box; } + + .shepherd-content { + background: #232323; + border-radius: 5px; + box-shadow: 0 0 1em rgba(0, 0, 0, 0.2); + color: #eee; + font-family: inherit; + font-size: 16px; + line-height: 24px; + padding: 1em; + padding: 0; + position: relative; + + &::before { + border-bottom-color: #303030; + border-color: transparent; + border-style: solid; + border-width: 16px; + content: ""; + display: block; + pointer-events: none; + width: 0; + height: 0; + position: absolute; + } + + header { + border-radius: 5px 5px 0 0; + *zoom: 1; + + &::after { + clear: both; + content: ""; + display: table; + } + + h3 { + font-weight: normal; + line-height: 1; + margin: 0; + } + + a.shepherd-cancel-link { + color: rgba(255, 255, 255, 0.8); + float: right; + font-size: 1.25em; + font-weight: normal; + line-height: 0.8em; + margin-bottom: -0.8em; + opacity: 0.25; + padding: 0.8em; + text-decoration: none; + position: relative; + top: 0.1em; + + &:hover { opacity: 1; } + } + } + + footer { + padding: 0 1em 1em; + + .shepherd-buttons { + list-style: none; + margin: 0; + padding: 0; + text-align: right; + + li { + display: inline; + margin: 0; + padding: 0; + + &:last-child .shepherd-button { + margin-right: 0; + } + } + } + + .shepherd-button { + background: #3288e6; + border: 0; + border-radius: 3px; + color: #fff; + cursor: pointer; + display: inline-block; + font-family: inherit; + font-size: .8em; + letter-spacing: .1em; + line-height: 1em; + margin: 0 .5em 0 0; + padding: .75em 2em; + text-transform: uppercase; + vertical-align: middle; + + &.shepherd-button-secondary, + &.button-cancel { + background: #eee; + color: #888; + } + } + } + + small { + display: block; + font-size: 12px; + line-height: 16px; + } + + .shepherd-title { + color: rgba(255, 255, 255, 0.5); + } + + .shepherd-text { + padding: 1em; + + p { + margin: 0 0 0.5em 0; + + &:last-child { margin-bottom: 0; } + } + } + } + + &.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content { + margin-bottom: 16px; + + &::before { + border-top-color: #232323; + margin-left: -16px; + top: 100%; + left: 50%; + } + } + + &.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content { + margin-top: 16px; + + &::before { + border-bottom-color: #232323; + margin-left: -16px; + bottom: 100%; + left: 50%; + } + } + + &.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content { + margin-right: 16px; + + &::before { + border-left-color: #232323; + margin-top: -16px; + top: 50%; + left: 100%; + } + } + + &.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content { + margin-left: 16px; + + &::before { + border-right-color: #232323; + margin-top: -16px; + top: 50%; + right: 100%; + } + } + + &.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content { + margin-top: 16px; + + &::before { + bottom: 100%; + left: 16px; + border-bottom-color: #232323; + } + } + + &.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content { + margin-top: 16px; + + &::before { + border-bottom-color: #232323; + right: 16px; + bottom: 100%; + } + } + + &.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content { + margin-bottom: 16px; + + &::before { + border-top-color: #232323; + top: 100%; + left: 16px; + } + } + + &.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content { + margin-bottom: 16px; + + &::before { + border-top-color: #232323; + top: 100%; + right: 16px; + } + } + + &.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content { + margin-right: 16px; + + &::before { + border-left-color: #232323; + top: 16px; + left: 100%; + } + } + + &.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content { + margin-left: 16px; + + &::before { + border-right-color: #232323; + top: 16px; + right: 100%; + } + } + + &.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content { + margin-right: 16px; + + &::before { + border-left-color: #232323; + bottom: 16px; + left: 100%; + } + } + + &.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content { + margin-left: 16px; + + &::before { + border-right-color: #232323; + right: 100%; + bottom: 16px; + } + } + + &.shepherd-element-attached-top.shepherd-element-attached-center.shepherd-has-title, + &.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-has-title, + &.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-has-title { + .shepherd-content::before { border-bottom-color: #303030; } + } + + &.newbie-tour { + max-width: none; - .shepherd-content h3 { - color: #fff; + .shepherd-content h3 { + color: #fff; - &::before { - @extend #help; - content: "?"; - display: inline-block; - margin-right: 10px; - position: static; - } + &::before { + @extend #help; + content: "?"; + display: inline-block; + margin-right: 10px; + position: static; + } - &:hover::before { - background-color: #666; - cursor: auto; - } - } + &:hover::before { + background-color: #666; + cursor: auto; + } + } - &.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content:before { - display: none; - } - } + &.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content:before { + display: none; + } + } } body.shepherd-active { - .chardinjs-overlay { display: none; } - - .chardinjs-tooltiptext { - background-color: #222; - border-radius: 5px; - font-size: 12px; - padding: 10px; - } - - #options, - .spiral-piece, - .axis, - .overtone { - opacity: 0.2; - } - - .overtone:not(.shepherd-enabled), - .spiral-piece:not(.shepherd-enabled), + .chardinjs-overlay { display: none; } + + .chardinjs-tooltiptext { + background-color: #222; + border-radius: 5px; + font-size: 12px; + padding: 10px; + } + + #options, + .spiral-piece, + .axis, + .overtone { + opacity: 0.2; + } + + .overtone:not(.shepherd-enabled), + .spiral-piece:not(.shepherd-enabled), .axis:not(.shepherd-enabled) { - &:active, - &:hover { - cursor: auto; - opacity: 0.2; - stroke: inherit; - transform: scale(1); - } - } - - .shepherd-enabled { opacity: 1 !important; } - - &[data-shepherd-step=overtone-spiral] { - .spiral-piece, - .axis, - .overtone { opacity: 1 !important; } - } - - &[data-shepherd-step=spiral-pieces] { - #overtone-2, - #overtone-3 { opacity: 1; } - } - - &[data-shepherd-step=interval-details] { - #overtone-2, - #overtone-3 { opacity: 1; } - } - - &[data-shepherd-step^=options-] { - #options { - opacity: 1; - - h2, - .input-wrapper, - .option-button { opacity: 0.4; } - } - } - - &[data-shepherd-step=options-octave], - &[data-shepherd-step=newbie-help] { - .shepherd-target.shepherd-enabled { - box-shadow: 0 0 10px #FF8A0B; - } - } + &:active, + &:hover { + cursor: auto; + opacity: 0.2; + stroke: inherit; + transform: scale(1); + } + } + + .shepherd-enabled { opacity: 1 !important; } + + &[data-shepherd-step=overtone-spiral] { + .spiral-piece, + .axis, + .overtone { opacity: 1 !important; } + } + + &[data-shepherd-step=spiral-pieces] { + #overtone-2, + #overtone-3 { opacity: 1; } + } + &[data-shepherd-step=interval-details] { + #overtone-2, + #overtone-3 { opacity: 1; } + } + + &[data-shepherd-step^=options-] { + #options { + opacity: 1; + + h2, + .input-wrapper, + .option-button { opacity: 0.4; } + } + } + + &[data-shepherd-step=options-octave], &[data-shepherd-step=newbie-help] { - #help { - background-color: #d4d4d4; - } - } -} \ No newline at end of file + .shepherd-target.shepherd-enabled { + box-shadow: 0 0 10px #FF8A0B; + } + } + + &[data-shepherd-step=newbie-help] { + #help { + background-color: #d4d4d4; + } + } +} diff --git a/assets/styles/_components/_options.scss b/assets/styles/_components/_options.scss index 4caea5e..31647ab 100644 --- a/assets/styles/_components/_options.scss +++ b/assets/styles/_components/_options.scss @@ -1,103 +1,103 @@ #base-wrapper, #volume-control-wrapper { - display: block; - margin: 30px 0 30px 30px; - position: relative; - - &::before { - background-position: center center; - background-repeat: no-repeat; - content: ""; - width: 20px; - height: 20px; - position: absolute; - top: 2px; - left: -30px; - } + display: block; + margin: 30px 0 30px 30px; + position: relative; + + &::before { + background-position: center center; + background-repeat: no-repeat; + content: ""; + width: 20px; + height: 20px; + position: absolute; + top: 2px; + left: -30px; + } } #base-wrapper { - &::before { - background-image: url("../images/ico-base.svg"); - } + &::before { + background-image: url("../images/ico-base.svg"); + } } #base-detail { - background: transparent; - border: 0; - color: #909090; - text-align: right; - - -moz-appearance: textfield; - - &::-webkit-inner-spin-button { display: none; } + background: transparent; + border: 0; + color: #909090; + text-align: right; + + -moz-appearance: textfield; + + &::-webkit-inner-spin-button { display: none; } } #base-detail-wrapper { - color: #909090; - font-size: 11px; - position: absolute; - top: -10px; - right: 0; + color: #909090; + font-size: 11px; + position: absolute; + top: -10px; + right: 0; } #volume-control-wrapper { - &::before { - background-image: url("../images/ico-sound.svg"); - } + &::before { + background-image: url("../images/ico-sound.svg"); + } } #options { - background-color: rgba(64, 64, 64, 0.1); - box-sizing: border-box; - padding: 20px; - z-index: 3; - width: 240px; - position: absolute; + background-color: rgba(64, 64, 64, 0.1); + box-sizing: border-box; + padding: 20px; + z-index: 3; + width: 240px; + position: absolute; } .options-buttons { - border-top: 1px solid #4E4E4E; - margin-top: 20px; - padding: 20px 0; + border-top: 1px solid #4E4E4E; + margin-top: 20px; + padding: 20px 0; } a.option-button { - @include has-tooltip(); - - background: no-repeat center center #666; - background-size: 60%; - border-radius: 20px; - display: inline-block; - margin-right: 10px; - text-decoration: none; - width: 35px; - height: 35px; - position: relative; + @include has-tooltip(); + + background: no-repeat center center #666; + background-size: 60%; + border-radius: 20px; + display: inline-block; + margin-right: 10px; + text-decoration: none; + width: 35px; + height: 35px; + position: relative; &:not(.off) { background-color: #d4d4d4; } } a#group-notes { - background-image: url("../images/ico-link-broken.svg"); - - &::after { content: "Group notes"; } - - &:not(.off) { - background-image: url("../images/ico-link.svg"); - - &::after { content: "Ungroup notes"; } - } + background-image: url("../images/ico-link-broken.svg"); + + &::after { content: "Group notes"; } + + &:not(.off) { + background-image: url("../images/ico-link.svg"); + + &::after { content: "Ungroup notes"; } + } } a#reduce-to-octave { - background-image: url("../images/ico-octave-reduction.svg"); - - &::after { content: "Reduce to octave"; } + background-image: url("../images/ico-octave-reduction.svg"); + + &::after { content: "Reduce to octave"; } } a#sustain { - background-image: url("../images/ico-sustain.svg"); - - &::after { content: "Sustain notes"; } -} \ No newline at end of file + background-image: url("../images/ico-sustain.svg"); + + &::after { content: "Sustain notes"; } +} diff --git a/assets/styles/_components/_overtone-spiral.scss b/assets/styles/_components/_overtone-spiral.scss index 2409356..d6e7c90 100644 --- a/assets/styles/_components/_overtone-spiral.scss +++ b/assets/styles/_components/_overtone-spiral.scss @@ -1,36 +1,36 @@ .spaces { - fill: #FFFFFF; - } - -.axis { - fill: #FF8A0B; - opacity: 0.5; - transition: opacity 0.2s ease-in-out; + fill: #FFFFFF; + } + +.axis { + fill: #FF8A0B; + opacity: 0.5; + transition: opacity 0.2s ease-in-out; } .axis:hover { - cursor: pointer; - opacity: 1; + cursor: pointer; + opacity: 1; } .spiral-piece { - fill: #6565B3; - opacity: 0.9; + fill: #6565B3; + opacity: 0.9; } .spiral-piece:hover { - cursor: pointer; - opacity: 1; - stroke: #9A9AF1; + cursor: pointer; + opacity: 1; + stroke: #9A9AF1; } .overtone-pattern { - fill:#0080A3; + fill:#0080A3; } .overtone { transform: scale(1); - transform-origin: 50% 50%; - transition: all 0.2s ease-in-out; + transform-origin: 50% 50%; + transition: all 0.2s ease-in-out; &:hover { transform: scale(1.1); @@ -61,4 +61,4 @@ @keyframes is-playing-color { from { fill: #FFE08D; } to { fill: #FFFFFF; } -} \ No newline at end of file +} diff --git a/assets/styles/_components/_range.scss b/assets/styles/_components/_range.scss index 681d98d..bd5ebda 100644 --- a/assets/styles/_components/_range.scss +++ b/assets/styles/_components/_range.scss @@ -4,64 +4,64 @@ */ @mixin track() { - background: #e0e0e0; - border: 0px solid #010101; - border-radius: 25px; - box-shadow: 2.3px 2.3px 6.4px rgba(0, 0, 0, 0.15), 0px 0px 2.3px rgba(13, 13, 13, 0.15); - cursor: pointer; - width: 100%; - height: 5px; + background: #e0e0e0; + border: 0px solid #010101; + border-radius: 25px; + box-shadow: 2.3px 2.3px 6.4px rgba(0, 0, 0, 0.15), 0px 0px 2.3px rgba(13, 13, 13, 0.15); + cursor: pointer; + width: 100%; + height: 5px; } @mixin thumb() { - -webkit-appearance: none; - background: #ffffff; - border: 2px solid #0080a3; - border-radius: 50px; - box-shadow: 0.1px 0.1px 8.7px rgba(0, 0, 49, 0.14), 0px 0px 0.1px rgba(0, 0, 75, 0.14); - cursor: pointer; - margin-top: -5px; - width: 15px; - height: 15px; + -webkit-appearance: none; + background: #ffffff; + border: 2px solid #0080a3; + border-radius: 50px; + box-shadow: 0.1px 0.1px 8.7px rgba(0, 0, 49, 0.14), 0px 0px 0.1px rgba(0, 0, 75, 0.14); + cursor: pointer; + margin-top: -5px; + width: 15px; + height: 15px; } input[type=range] { - -webkit-appearance: none; - margin: 5px 0; - width: 170px; - - &:focus { - outline: none; - - &::-webkit-slider-runnable-track { background: #eaeaea; } - - &::-ms-fill-lower { background: #dddddd; } - &::-ms-fill-upper { background: #eaeaea; } - } - - &::-webkit-slider-runnable-track { @include track(); } - &::-moz-range-track { @include track(); } - - &::-webkit-slider-thumb { @include thumb(); } - &::-moz-range-thumb { @include thumb(); } - &::-ms-thumb { @include thumb(); } - - &::-ms-track { - background: transparent; - border-color: transparent; - color: transparent; - cursor: pointer; - width: 100%; - height: 5px; - } - - &::-ms-fill-lower, - &::-ms-fill-upper { - background: #d0d0d0; - border: 0px solid #010101; - border-radius: 50px; - box-shadow: 2.3px 2.3px 6.4px rgba(0, 0, 0, 0.15), 0px 0px 2.3px rgba(13, 13, 13, 0.15); - } - - &::-ms-fill-upper { background: #ddd; } + -webkit-appearance: none; + margin: 5px 0; + width: 170px; + + &:focus { + outline: none; + + &::-webkit-slider-runnable-track { background: #eaeaea; } + + &::-ms-fill-lower { background: #dddddd; } + &::-ms-fill-upper { background: #eaeaea; } + } + + &::-webkit-slider-runnable-track { @include track(); } + &::-moz-range-track { @include track(); } + + &::-webkit-slider-thumb { @include thumb(); } + &::-moz-range-thumb { @include thumb(); } + &::-ms-thumb { @include thumb(); } + + &::-ms-track { + background: transparent; + border-color: transparent; + color: transparent; + cursor: pointer; + width: 100%; + height: 5px; + } + + &::-ms-fill-lower, + &::-ms-fill-upper { + background: #d0d0d0; + border: 0px solid #010101; + border-radius: 50px; + box-shadow: 2.3px 2.3px 6.4px rgba(0, 0, 0, 0.15), 0px 0px 2.3px rgba(13, 13, 13, 0.15); + } + + &::-ms-fill-upper { background: #ddd; } } diff --git a/assets/styles/_components/_sound-details.scss b/assets/styles/_components/_sound-details.scss index fa9791c..5f92e04 100644 --- a/assets/styles/_components/_sound-details.scss +++ b/assets/styles/_components/_sound-details.scss @@ -1,86 +1,86 @@ #sound-details { - border: 1px solid rgb(51, 51, 51); - box-sizing: border-box; - font-family: $serifFontFamily; - font-weight: 100; - opacity: 0; - padding: 10px; - text-align: center; - z-index: 1; - width: 240px; - height: 240px; - position: absolute; - top: 20px; - right: 20px; + border: 1px solid rgb(51, 51, 51); + box-sizing: border-box; + font-family: $serifFontFamily; + font-weight: 100; + opacity: 0; + padding: 10px; + text-align: center; + z-index: 1; + width: 240px; + height: 240px; + position: absolute; + top: 20px; + right: 20px; - transition: opacity 0.5s ease-in-out; - - &.visible { - opacity: 1; - } + transition: opacity 0.5s ease-in-out; + + &.visible { + opacity: 1; + } } #note, #interval { - font-size: 128px; - text-transform: uppercase; - height: 185px; - - sup, - sub { font-size: 0.7em; } + font-size: 128px; + text-transform: uppercase; + height: 185px; + + sup, + sub { font-size: 0.7em; } } - .show-interval #note, - .show-interval #note-frequency { display: none; } + .show-interval #note, + .show-interval #note-frequency { display: none; } - .show-note #interval, - .show-note #interval-name { display: none; } + .show-note #interval, + .show-note #interval-name { display: none; } #note-frequency, #interval-name { - display: block; - text-align: right; - width: 218px; + display: block; + text-align: right; + width: 218px; } .note-detail { - font-size: 14px; - position: absolute; - top: 10px; - right: 10px; + font-size: 14px; + position: absolute; + top: 10px; + right: 10px; } .cents-difference { - border-top: 1px solid white; - opacity: 0.5; - padding-top: 10px; - position: relative; + border-top: 1px solid white; + opacity: 0.5; + padding-top: 10px; + position: relative; - transition: text-indent 0.2s ease-in-out; + transition: text-indent 0.2s ease-in-out; } - .show-note .interval.cents-difference { display: none; } - .show-interval .tuning.cents-difference { display: none; } + .show-note .interval.cents-difference { display: none; } + .show-interval .tuning.cents-difference { display: none; } - .show-interval .cents-difference { text-align: left; } + .show-interval .cents-difference { text-align: left; } .cent-bar { - border-bottom: 5px solid white; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - content: ""; - position: absolute; - top: 0; - left: 50%; - margin-left: -5px; + border-bottom: 5px solid white; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + content: ""; + position: absolute; + top: 0; + left: 50%; + margin-left: -5px; - transition: left 0.2s ease-in-out; + transition: left 0.2s ease-in-out; } .interval { - .cents { margin-left: -13px; } - - .cent-bar { left: 0; } + .cents { margin-left: -13px; } + + .cent-bar { left: 0; } } -.accidental { font-size: 0.5em; } \ No newline at end of file +.accidental { font-size: 0.5em; } diff --git a/assets/styles/_variables.scss b/assets/styles/_variables.scss index 8713107..90f6ad2 100644 --- a/assets/styles/_variables.scss +++ b/assets/styles/_variables.scss @@ -2,22 +2,22 @@ $baseFontFamily: Sintony, Roboto, Helvetica, Arial, sans-serif; $serifFontFamily: Judson, Georgia, serif; @mixin has-tooltip($text: "", $position: bottom) { - &::after { - background: rgba(80, 80, 80, 0.5); - border-radius: 5px; - box-shadow: 0 0 5px #1D1C1C; - color: #fff; - content: $text; - display: none; - font-family: $baseFontFamily; - font-size: 10px; - font-weight: bold; - line-height: 10px; - padding: 5px; - white-space: nowrap; - position: absolute; - #{$position}: -25px; - } - - &:hover::after { display: inline-block; } + &::after { + background: rgba(80, 80, 80, 0.5); + border-radius: 5px; + box-shadow: 0 0 5px #1D1C1C; + color: #fff; + content: $text; + display: none; + font-family: $baseFontFamily; + font-size: 10px; + font-weight: bold; + line-height: 10px; + padding: 5px; + white-space: nowrap; + position: absolute; + #{$position}: -25px; + } + + &:hover::after { display: inline-block; } } \ No newline at end of file diff --git a/components/_facebook.html b/components/_facebook.html index 6bfe4c4..b8f3748 100644 --- a/components/_facebook.html +++ b/components/_facebook.html @@ -1,12 +1,12 @@
+ data-href="http://www.suonoterapia.org/overtones" + data-layout="button_count">
\ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 7a28480..57d03f2 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -22,129 +22,129 @@ var sourcemaps = require('gulp-sourcemaps'); var ftp = require( 'vinyl-ftp' ); gulp.task('assets', function() { - return gulp.src('./favicons.zip') - .pipe( decompress() ) - .pipe( gulp.dest('build/') ) + return gulp.src('./favicons.zip') + .pipe( decompress() ) + .pipe( gulp.dest('build/') ) }); gulp.task('default', function () { - return gulp.src('logo.svg') - .pipe(svgmin()) - .pipe(gulp.dest('./out')); + return gulp.src('logo.svg') + .pipe(svgmin()) + .pipe(gulp.dest('./out')); }); gulp.task('css', function () { - return gulp.src('./assets/styles/*.scss') - .pipe( sass({ outputStyle: 'compressed'}).on('error', sass.logError) ) - .pipe( autoprefixer('> 0.4%') ) - .pipe( cachebust.resources() ) - .pipe( gulp.dest('build/assets/styles/') ); + return gulp.src('./assets/styles/*.scss') + .pipe( sass({ outputStyle: 'compressed'}).on('error', sass.logError) ) + .pipe( autoprefixer('> 0.4%') ) + .pipe( cachebust.resources() ) + .pipe( gulp.dest('build/assets/styles/') ); }); gulp.task('css:dev', function () { - return gulp.src('./assets/styles/*.scss') - .pipe( sass().on('error', sass.logError) ) - .pipe( autoprefixer('> 0.4%') ) - .pipe( gulp.dest('build/assets/styles/') ); + return gulp.src('./assets/styles/*.scss') + .pipe( sass().on('error', sass.logError) ) + .pipe( autoprefixer('> 0.4%') ) + .pipe( gulp.dest('build/assets/styles/') ); }); gulp.task('css:watch', function () { - gulp.watch('./sass/**/*.scss', ['sass']); + gulp.watch('./sass/**/*.scss', ['sass']); }); gulp.task('html', function () { - return gulp.src('index.html') - .pipe( include() ) - .pipe( minifyHTML({collapseWhitespace: true}) ) - .pipe( gulp.dest('build/') ); + return gulp.src('index.html') + .pipe( include() ) + .pipe( minifyHTML({collapseWhitespace: true}) ) + .pipe( gulp.dest('build/') ); }); gulp.task('javascript:dev', function () { - var b = browserify({ - entries: './assets/js/script.js', - debug: true - }) - .transform("babelify", { presets: ["es2015"] }); - - var webfont = browserify({ - entries: './assets/js/webfont.js', - debug: true - }); - - webfont.bundle() - .pipe( source('./assets/js/webfont.js') ) - .pipe( buffer() ) - .pipe( sourcemaps.init({loadMaps: true}) ) - .pipe( sourcemaps.write('./') ) - .pipe( gulp.dest('./build/') ); - - return b.bundle() - .pipe( source('./assets/js/script.js') ) - .pipe( buffer() ) - .pipe( sourcemaps.init({loadMaps: true}) ) - .pipe( sourcemaps.write('./') ) - .pipe( gulp.dest('./build/') ); + var b = browserify({ + entries: './assets/js/script.js', + debug: true + }) + .transform("babelify", { presets: ["es2015"] }); + + var webfont = browserify({ + entries: './assets/js/webfont.js', + debug: true + }); + + webfont.bundle() + .pipe( source('./assets/js/webfont.js') ) + .pipe( buffer() ) + .pipe( sourcemaps.init({loadMaps: true}) ) + .pipe( sourcemaps.write('./') ) + .pipe( gulp.dest('./build/') ); + + return b.bundle() + .pipe( source('./assets/js/script.js') ) + .pipe( buffer() ) + .pipe( sourcemaps.init({loadMaps: true}) ) + .pipe( sourcemaps.write('./') ) + .pipe( gulp.dest('./build/') ); }); gulp.task('javascript', function () { - var b = browserify({ - entries: './assets/js/script.js' - }) - .transform("babelify", { presets: ["es2015"] }); - - var webfont = browserify({ - entries: './assets/js/webfont.js', - }); - - webfont.bundle() - .pipe( source('./assets/js/webfont.js') ) - .pipe( buffer() ) - .pipe( uglify() ) - .pipe( cachebust.resources() ) - .pipe( gulp.dest('./build/') ); - - return b.bundle() - .pipe( source('./assets/js/script.js') ) - .pipe( buffer() ) - .pipe( uglify() ) - .pipe( cachebust.resources() ) - .pipe( gulp.dest('./build/') ); + var b = browserify({ + entries: './assets/js/script.js' + }) + .transform("babelify", { presets: ["es2015"] }); + + var webfont = browserify({ + entries: './assets/js/webfont.js', + }); + + webfont.bundle() + .pipe( source('./assets/js/webfont.js') ) + .pipe( buffer() ) + .pipe( uglify() ) + .pipe( cachebust.resources() ) + .pipe( gulp.dest('./build/') ); + + return b.bundle() + .pipe( source('./assets/js/script.js') ) + .pipe( buffer() ) + .pipe( uglify() ) + .pipe( cachebust.resources() ) + .pipe( gulp.dest('./build/') ); }); gulp.task('watch', function() { - gulp.watch(['index.html', './assets/images/overtone-spiral.svg'], ['html']); - gulp.watch(['./assets/styles/*.scss', './assets/styles/**/*.scss'], ['css:dev']); - gulp.watch('./assets/js/**/*.js', ['javascript:dev']); + gulp.watch(['index.html', './assets/images/overtone-spiral.svg'], ['html']); + gulp.watch(['./assets/styles/*.scss', './assets/styles/**/*.scss'], ['css:dev']); + gulp.watch('./assets/js/**/*.js', ['javascript:dev']); }); gulp.task('build', ['css', 'javascript'], function(){ return gulp.src('index.html') - .pipe( include() ) - .pipe( minifyHTML({collapseWhitespace: true}) ) - .pipe( cachebust.references() ) - .pipe( gulp.dest('build/') ); + .pipe( include() ) + .pipe( minifyHTML({collapseWhitespace: true}) ) + .pipe( cachebust.references() ) + .pipe( gulp.dest('build/') ); }); gulp.task('deploy', ['build', 'clean'], function(){ var conn = ftp.create( { - host: process.env.FTP_HOST, - user: process.env.FTP_USER, - password: process.env.FTP_PASS, - parallel: 10, - log: gutil.log - } ); + host: process.env.FTP_HOST, + user: process.env.FTP_USER, + password: process.env.FTP_PASS, + parallel: 10, + log: gutil.log + } ); - return gulp.src( 'build/**', { buffer: false } ) - .pipe( conn.dest( '/asmi/overtones/.' ) ); + return gulp.src( 'build/**', { buffer: false } ) + .pipe( conn.dest( '/asmi/overtones/.' ) ); }); gulp.task('clean', function(cb) { var conn = ftp.create( { - host: process.env.FTP_HOST, - user: process.env.FTP_USER, - password: process.env.FTP_PASS, - parallel: 10, - log: gutil.log - } ); + host: process.env.FTP_HOST, + user: process.env.FTP_USER, + password: process.env.FTP_PASS, + parallel: 10, + log: gutil.log + } ); conn.rmdir('/asmi/overtones/', cb) -}); \ No newline at end of file +}); diff --git a/index.html b/index.html index 1f79e45..d3d5ab4 100644 --- a/index.html +++ b/index.html @@ -1,103 +1,103 @@ - - Overtone scale - + + Overtone scale + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - -
-

Options

-
-
- -
- - Hz -
-
-
- -
- -
- - - -
-
-
-
-
- -
-
- -
-
-
0 Hz
-
- 0 -
-
-
- 1200 -
-
-
- - - - - - + +
+

Options

+
+
+ +
+ + Hz +
+
+
+ +
+ +
+ + + +
+
+
+
+
+ +
+
+ +
+
+
0 Hz
+
+ 0 +
+
+
+ 1200 +
+
+
+ + + + + + - \ No newline at end of file + diff --git a/package.json b/package.json index df4ec58..31eed53 100644 --- a/package.json +++ b/package.json @@ -1,56 +1,56 @@ { "name": "overtones", - "version": "1.0.0", + "version": "1.1.1", "description": "Visualization of the overtones spiral of a sound", "main": "index.html", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { - "type": "git", - "url": "git+https://github.com/sunyatasattva/overtones.git" + "type": "git", + "url": "git+https://github.com/sunyatasattva/overtones.git" }, "author": "M. L. Giannotta", "license": "GNU", "bugs": { - "url": "https://github.com/sunyatasattva/overtones/issues" + "url": "https://github.com/sunyatasattva/overtones/issues" }, "homepage": "https://github.com/sunyatasattva/overtones#readme", "dependencies": { - "chardin.js": "^0.1.3", - "detect-browser": "^1.3.1", - "frac": "^1.0.2", - "jquery": "^2.2.0", - "jquery.animate-number": "0.0.12", - "lodash.assign": "^3.2.0", - "lodash.debounce": "^3.1.1", - "lodash.findkey": "^3.0.1", - "lodash.values": "^3.0.0", - "music-gamut": "0.7.7", - "pitch-set": "^1.0.2", - "tether-shepherd": "^1.7.0", - "velocity-animate": "^1.2.3", - "webfontloader": "^1.6.21" + "chardin.js": "^0.1.3", + "detect-browser": "^1.3.1", + "frac": "^1.0.2", + "jquery": "^2.2.0", + "jquery.animate-number": "0.0.12", + "lodash.assign": "^3.2.0", + "lodash.debounce": "^3.1.1", + "lodash.findkey": "^3.0.1", + "lodash.values": "^3.0.0", + "music-gamut": "0.7.7", + "pitch-set": "^1.0.2", + "tether-shepherd": "^1.7.0", + "velocity-animate": "^1.2.3", + "webfontloader": "^1.6.21" }, "devDependencies": { - "babel-preset-es2015": "^6.6.0", - "babelify": "^7.2.0", - "browserify": "^12.0.1", - "dotenv": "^2.0.0", - "gulp": "^3.9.0", - "gulp-autoprefixer": "^3.1.0", - "gulp-cachebust": "0.0.6", - "gulp-decompress": "^1.2.0", - "gulp-htmlmin": "^1.3.0", - "gulp-include": "^2.1.0", - "gulp-sass": "^2.2.0", - "gulp-sourcemaps": "^1.6.0", - "gulp-svgmin": "^1.2.1", - "gulp-uglify": "^1.5.2", - "gutil": "^1.6.4", - "vinyl-buffer": "^1.0.0", - "vinyl-ftp": "^0.4.5", - "vinyl-source-stream": "^1.1.0", - "watchify": "^3.6.0" + "babel-preset-es2015": "^6.6.0", + "babelify": "^7.2.0", + "browserify": "^12.0.1", + "dotenv": "^2.0.0", + "gulp": "^3.9.0", + "gulp-autoprefixer": "^3.1.0", + "gulp-cachebust": "0.0.6", + "gulp-decompress": "^1.2.0", + "gulp-htmlmin": "^1.3.0", + "gulp-include": "^2.1.0", + "gulp-sass": "^2.2.0", + "gulp-sourcemaps": "^1.6.0", + "gulp-svgmin": "^1.2.1", + "gulp-uglify": "^1.5.2", + "gutil": "^1.6.4", + "vinyl-buffer": "^1.0.0", + "vinyl-ftp": "^0.4.5", + "vinyl-source-stream": "^1.1.0", + "watchify": "^3.6.0" } }