From dfe55b78651b8b7a8f6aac5fc36f2ee0b3f1b400 Mon Sep 17 00:00:00 2001 From: groos Date: Tue, 5 Feb 2019 17:04:48 -0600 Subject: [PATCH 1/5] Modes Feature Added function to get new Scale object for the mode at the provided degree, relative to the current scale. --- lib/knowledge.js | 10 ++++++++++ lib/scale.js | 45 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/lib/knowledge.js b/lib/knowledge.js index 966a10f..73dc2a7 100644 --- a/lib/knowledge.js +++ b/lib/knowledge.js @@ -22,6 +22,16 @@ module.exports = { octave: [1, 0] }, + modes: { + ionian: 'ionian', + dorian: 'dorian', + phrygian: 'phrygian', + lydian: 'lydian', + mixolydian: 'mixolydian', + aeolian: 'aeolian', + locrian: 'locrian' + }, + intervalFromFifth: ['second', 'sixth', 'third', 'seventh', 'fourth', 'unison', 'fifth'], diff --git a/lib/scale.js b/lib/scale.js index 2d75232..0fde07e 100644 --- a/lib/scale.js +++ b/lib/scale.js @@ -2,22 +2,33 @@ var knowledge = require('./knowledge'); var Interval = require('./interval'); var scales = { - aeolian: ['P1', 'M2', 'm3', 'P4', 'P5', 'm6', 'm7'], + [knowledge.aeolian]: ['P1', 'M2', 'm3', 'P4', 'P5', 'm6', 'm7'], blues: ['P1', 'm3', 'P4', 'd5', 'P5', 'm7'], - chromatic: ['P1', 'm2', 'M2', 'm3', 'M3', 'P4', 'A4', 'P5', 'm6', 'M6', 'm7', 'M7'], - dorian: ['P1', 'M2', 'm3', 'P4', 'P5', 'M6', 'm7'], + chromatic: ['P1', 'm2', 'M2', 'm3', 'M3', 'P4', + 'A4', 'P5', 'm6', 'M6', 'm7', 'M7'], + [knowledge.dorian]: ['P1', 'M2', 'm3', 'P4', 'P5', 'M6', 'm7'], doubleharmonic: ['P1', 'm2', 'M3', 'P4', 'P5', 'm6', 'M7'], harmonicminor: ['P1', 'M2', 'm3', 'P4', 'P5', 'm6', 'M7'], - ionian: ['P1', 'M2', 'M3', 'P4', 'P5', 'M6', 'M7'], - locrian: ['P1', 'm2', 'm3', 'P4', 'd5', 'm6', 'm7'], - lydian: ['P1', 'M2', 'M3', 'A4', 'P5', 'M6', 'M7'], + [knowledge.ionian]: ['P1', 'M2', 'M3', 'P4', 'P5', 'M6', 'M7'], + [knowledge.locrian]: ['P1', 'm2', 'm3', 'P4', 'd5', 'm6', 'm7'], + [knowledge.lydian]: ['P1', 'M2', 'M3', 'A4', 'P5', 'M6', 'M7'], majorpentatonic: ['P1', 'M2', 'M3', 'P5', 'M6'], melodicminor: ['P1', 'M2', 'm3', 'P4', 'P5', 'M6', 'M7'], minorpentatonic: ['P1', 'm3', 'P4', 'P5', 'm7'], - mixolydian: ['P1', 'M2', 'M3', 'P4', 'P5', 'M6', 'm7'], - phrygian: ['P1', 'm2', 'm3', 'P4', 'P5', 'm6', 'm7'], + [knowledge.mixolydian]: ['P1', 'M2', 'M3', 'P4', 'P5', 'M6', 'm7'], + [knowledge.phrygian]: ['P1', 'm2', 'm3', 'P4', 'P5', 'm6', 'm7'], wholetone: ['P1', 'M2', 'M3', 'A4', 'A5', 'A6'] }; + +var orderedModes = { + 1: knowledge.ionian, + 2: knowledge.dorian, + 3: knowledge.phrygian, + 4: knowledge.lydian, + 5: knowledge.mixolydian, + 6: knowledge.aeolian, + 7: knowledge.locrian +}; // synonyms scales.harmonicchromatic = scales.chromatic; @@ -51,6 +62,13 @@ function Scale(tonic, scale) { this.name = scaleName; this.tonic = tonic; this.scale = scale; + + var modeDegree = Object.keys(orderedModes).filter(function(m) { + return orderedModes[m] === this.name; + }.bind(this)); + + this.modeDegree = modeDegree && modeDegree.length ? parseInt(modeDegree[0]) : ''; + this.modeName = this.modeDegree ? orderedModes[this.modeDegree] : ''; } Scale.prototype = { @@ -64,6 +82,17 @@ Scale.prototype = { return notes; }, + // provided a scale degree, returns the scale representing the + // mode at the degree relative to the current scale + mode: function(degree) { + if (this.modeDegree) { + var newDegree = this.modeDegree + degree > 7 ? (this.modeDegree + degree) - 7 : this.modeDegree + degree; + return new Note(this.tonic).scale(orderedModes[newDegree]) + } + + return 'Scale does not support Modes'; + }, + simple: function() { return this.notes().map(function(n) { return n.toString(true); }); }, From 6ec2c6680af43b8c361f9b570c7fea2564b3a386 Mon Sep 17 00:00:00 2001 From: groos Date: Tue, 5 Feb 2019 17:12:29 -0600 Subject: [PATCH 2/5] Fixed Knowledge import --- lib/scale.js | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/scale.js b/lib/scale.js index 0fde07e..9ba5d3d 100644 --- a/lib/scale.js +++ b/lib/scale.js @@ -1,33 +1,34 @@ var knowledge = require('./knowledge'); var Interval = require('./interval'); +var Note = require('./note'); var scales = { - [knowledge.aeolian]: ['P1', 'M2', 'm3', 'P4', 'P5', 'm6', 'm7'], + [knowledge.modes.aeolian]: ['P1', 'M2', 'm3', 'P4', 'P5', 'm6', 'm7'], blues: ['P1', 'm3', 'P4', 'd5', 'P5', 'm7'], chromatic: ['P1', 'm2', 'M2', 'm3', 'M3', 'P4', 'A4', 'P5', 'm6', 'M6', 'm7', 'M7'], - [knowledge.dorian]: ['P1', 'M2', 'm3', 'P4', 'P5', 'M6', 'm7'], + [knowledge.modes.dorian]: ['P1', 'M2', 'm3', 'P4', 'P5', 'M6', 'm7'], doubleharmonic: ['P1', 'm2', 'M3', 'P4', 'P5', 'm6', 'M7'], harmonicminor: ['P1', 'M2', 'm3', 'P4', 'P5', 'm6', 'M7'], - [knowledge.ionian]: ['P1', 'M2', 'M3', 'P4', 'P5', 'M6', 'M7'], - [knowledge.locrian]: ['P1', 'm2', 'm3', 'P4', 'd5', 'm6', 'm7'], - [knowledge.lydian]: ['P1', 'M2', 'M3', 'A4', 'P5', 'M6', 'M7'], + [knowledge.modes.ionian]: ['P1', 'M2', 'M3', 'P4', 'P5', 'M6', 'M7'], + [knowledge.modes.locrian]: ['P1', 'm2', 'm3', 'P4', 'd5', 'm6', 'm7'], + [knowledge.modes.lydian]: ['P1', 'M2', 'M3', 'A4', 'P5', 'M6', 'M7'], majorpentatonic: ['P1', 'M2', 'M3', 'P5', 'M6'], melodicminor: ['P1', 'M2', 'm3', 'P4', 'P5', 'M6', 'M7'], minorpentatonic: ['P1', 'm3', 'P4', 'P5', 'm7'], - [knowledge.mixolydian]: ['P1', 'M2', 'M3', 'P4', 'P5', 'M6', 'm7'], - [knowledge.phrygian]: ['P1', 'm2', 'm3', 'P4', 'P5', 'm6', 'm7'], + [knowledge.modes.mixolydian]: ['P1', 'M2', 'M3', 'P4', 'P5', 'M6', 'm7'], + [knowledge.modes.phrygian]: ['P1', 'm2', 'm3', 'P4', 'P5', 'm6', 'm7'], wholetone: ['P1', 'M2', 'M3', 'A4', 'A5', 'A6'] }; var orderedModes = { - 1: knowledge.ionian, - 2: knowledge.dorian, - 3: knowledge.phrygian, - 4: knowledge.lydian, - 5: knowledge.mixolydian, - 6: knowledge.aeolian, - 7: knowledge.locrian + 1: knowledge.modes.ionian, + 2: knowledge.modes.dorian, + 3: knowledge.modes.phrygian, + 4: knowledge.modes.lydian, + 5: knowledge.modes.mixolydian, + 6: knowledge.modes.aeolian, + 7: knowledge.modes.locrian }; // synonyms @@ -86,6 +87,7 @@ Scale.prototype = { // mode at the degree relative to the current scale mode: function(degree) { if (this.modeDegree) { + degree--; var newDegree = this.modeDegree + degree > 7 ? (this.modeDegree + degree) - 7 : this.modeDegree + degree; return new Note(this.tonic).scale(orderedModes[newDegree]) } From 99d9701b230772a62317f0fc37f5b5037f9b602a Mon Sep 17 00:00:00 2001 From: groos Date: Tue, 5 Feb 2019 18:17:24 -0600 Subject: [PATCH 3/5] Fixed Scale instantiation. Added tests --- lib/scale.js | 7 ++++--- test/scales.js | 10 ++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/scale.js b/lib/scale.js index 9ba5d3d..08c70c0 100644 --- a/lib/scale.js +++ b/lib/scale.js @@ -87,9 +87,10 @@ Scale.prototype = { // mode at the degree relative to the current scale mode: function(degree) { if (this.modeDegree) { - degree--; - var newDegree = this.modeDegree + degree > 7 ? (this.modeDegree + degree) - 7 : this.modeDegree + degree; - return new Note(this.tonic).scale(orderedModes[newDegree]) + degree -= 1; + let degreeArrayIndex = this.modeDegree - 1; + var newDegree = degreeArrayIndex + degree > 7 ? (degreeArrayIndex + degree) - 7 : degreeArrayIndex + degree; + return new Note.fromString(this.simple()[newDegree]).scale(orderedModes[newDegree + 1]); } return 'Scale does not support Modes'; diff --git a/test/scales.js b/test/scales.js index fa91750..2fb37b2 100644 --- a/test/scales.js +++ b/test/scales.js @@ -67,6 +67,16 @@ vows.describe('Scales').addBatch({ 'Whole Tone': function(note) { assert.deepEqual(teoria.note('c').scale('wholetone').simple(), ["c", "d", "e", "f#", "g#", "a#"]); + }, + + 'Get First Mode' : function(note) { + assert.deepEqual(teoria.note('c4').scale('ionian').mode(1).simple(), + ['c', 'd', 'e', 'f', 'g', 'a', 'b']); + }, + + 'Get Third Mode' : function(note) { + assert.deepEqual(teoria.note('c4').scale('ionian').mode(3).simple(), + ['e', 'f', 'g', 'a', 'b', 'c', 'd']); } } }).export(module); From ce8255b52e8e8abfa69c6daf4b21fe4676db07b3 Mon Sep 17 00:00:00 2001 From: groos Date: Tue, 5 Feb 2019 19:06:34 -0600 Subject: [PATCH 4/5] started negative modes feature --- lib/scale.js | 26 +++++++++++++---- teoria.js | 78 +++++++++++++++++++++++++++++++++++++++++++------- test/scales.js | 15 ++++++++++ 3 files changed, 104 insertions(+), 15 deletions(-) diff --git a/lib/scale.js b/lib/scale.js index 3e81059..7f85101 100644 --- a/lib/scale.js +++ b/lib/scale.js @@ -83,14 +83,30 @@ Scale.prototype = { return notes; }, - // provided a scale degree, returns the scale representing the - // mode at the degree relative to the current scale + // returns a new Scale object for the mode at the degree relative to the current scale mode: function(degree) { if (this.modeDegree) { - degree -= 1; let degreeArrayIndex = this.modeDegree - 1; - var newDegree = degreeArrayIndex + degree > 7 ? (degreeArrayIndex + degree) - 7 : degreeArrayIndex + degree; - return new Note.fromString(this.simple()[newDegree]).scale(orderedModes[newDegree + 1]); + + if (degree > 0) { + degree -= 1; + let movedDegree = degreeArrayIndex + degree; + let newDegree = movedDegree > 7 ? movedDegree % 7 : movedDegree; + return new Note.fromString(this.simple()[newDegree]).scale(orderedModes[newDegree + 1]); + } + + // treat a degree < 0 as a subtraction, i.e. ionian -1 = locrian + if (degree < 0) { + let movedDegree = this.modeDegree + degree; + + let newDegree = 1; + if (movedDegree % -8 == 0) { + newDegree = movedDegree < -8 ? 8 + movedDegree % -8 : 8 + movedDegree; + } + + console.log(newDegree); + return new Note.fromString(this.simple()[newDegree - 1]).scale(orderedModes[newDegree]); + } } return 'Scale does not support Modes'; diff --git a/teoria.js b/teoria.js index 439d7ed..60cd254 100644 --- a/teoria.js +++ b/teoria.js @@ -1,4 +1,4 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.teoria = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) { + degree -= 1; + let movedDegree = degreeArrayIndex + degree; + let newDegree = movedDegree > 7 ? movedDegree % 7 : movedDegree; + return new Note.fromString(this.simple()[newDegree]).scale(orderedModes[newDegree + 1]); + } + + // treat a degree < 0 as a subtraction, i.e. ionian -1 = locrian + if (degree < 0) { + let movedDegree = this.modeDegree + degree; + + let newDegree = 1; + if (movedDegree % -8 == 0) { + newDegree = movedDegree < -8 ? 8 + movedDegree % -8 : 8 + movedDegree; + } + + console.log(-9 % -8); + console.log(newDegree); + return new Note.fromString(this.simple()[newDegree - 1]).scale(orderedModes[newDegree]); + } + } + + return 'Scale does not support Modes'; + }, + simple: function() { return this.notes().map(function(n) { return n.toString(true); }); }, @@ -999,7 +1057,7 @@ Scale.KNOWN_SCALES = Object.keys(scales); module.exports = Scale; -},{"./interval":3,"./knowledge":4}],7:[function(require,module,exports){ +},{"./interval":3,"./knowledge":4,"./note":5}],7:[function(require,module,exports){ var knowledge = require('./knowledge'); module.exports = function(teoria) { @@ -1401,4 +1459,4 @@ module.exports = function scientific(name) { }; },{"accidental-value":9,"notecoord":13}]},{},[1])(1) -}); \ No newline at end of file +}); diff --git a/test/scales.js b/test/scales.js index 3045030..626c375 100644 --- a/test/scales.js +++ b/test/scales.js @@ -77,6 +77,21 @@ vows.describe('Scales').addBatch({ 'Get Third Mode' : function(note) { assert.deepEqual(teoria.note('c4').scale('ionian').mode(3).simple(), ['e', 'f', 'g', 'a', 'b', 'c', 'd']); + }, + + 'Get Negative Mode' : function(note) { + assert.deepEqual(teoria.note('c4').scale('ionian').mode(-1).simple(), + ['b', 'c', 'd', 'e', 'f', 'g', 'a',]); + }, + + 'Get Negative Tonic via Mode' : function(note) { + assert.deepEqual(teoria.note('c4').scale('ionian').mode(-8).simple(), + ['c', 'd', 'e', 'f', 'g', 'a', 'b']); + }, + + 'Get Really Negative Mode' : function(note) { + assert.deepEqual(teoria.note('c4').scale('ionian').mode(-9).simple(), + ['b', 'c', 'd', 'e', 'f', 'g', 'a',]); } }, From a39f7c14ed4748cc7c1616d7c3308c92a88e32d6 Mon Sep 17 00:00:00 2001 From: Nick Groos Date: Tue, 12 Feb 2019 10:59:13 -0600 Subject: [PATCH 5/5] Added logic for negative transforms. Added tests. --- lib/scale.js | 35 +++++++++++++++++++---------------- test/scales.js | 34 +++++++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/lib/scale.js b/lib/scale.js index 7f85101..a293690 100644 --- a/lib/scale.js +++ b/lib/scale.js @@ -86,26 +86,29 @@ Scale.prototype = { // returns a new Scale object for the mode at the degree relative to the current scale mode: function(degree) { if (this.modeDegree) { - let degreeArrayIndex = this.modeDegree - 1; - - if (degree > 0) { - degree -= 1; - let movedDegree = degreeArrayIndex + degree; - let newDegree = movedDegree > 7 ? movedDegree % 7 : movedDegree; - return new Note.fromString(this.simple()[newDegree]).scale(orderedModes[newDegree + 1]); + if (degree % 8 > 0) { + let shift = degree -= 1; + shift = shift % 8; + + let newModeDegree = this.modeDegree + shift; + + return new Note.fromString(this.simple()[shift]).scale(orderedModes[newModeDegree]); } // treat a degree < 0 as a subtraction, i.e. ionian -1 = locrian - if (degree < 0) { - let movedDegree = this.modeDegree + degree; + else if (degree < 0) { + let negativeShift = degree % -8; + let newDegree = negativeShift !== 0 ? 8 + negativeShift : 1; - let newDegree = 1; - if (movedDegree % -8 == 0) { - newDegree = movedDegree < -8 ? 8 + movedDegree % -8 : 8 + movedDegree; - } - - console.log(newDegree); - return new Note.fromString(this.simple()[newDegree - 1]).scale(orderedModes[newDegree]); + let newModeDegree = this.modeDegree + negativeShift; + newModeDegree = newModeDegree === 0 ? 7 : newModeDegree; + + return new Note.fromString(this.simple()[newDegree - 1]).scale(orderedModes[newModeDegree]); + } + + else { + // return new instance of this same scale/mode + return new Note.fromString(this.simple()[0]).scale(orderedModes[this.modeDegree]); } } diff --git a/test/scales.js b/test/scales.js index 626c375..2d80902 100644 --- a/test/scales.js +++ b/test/scales.js @@ -69,30 +69,50 @@ vows.describe('Scales').addBatch({ ["c", "d", "e", "f#", "g#", "a#"]); }, - 'Get First Mode' : function(note) { + 'Get Ionian First Mode' : function(note) { assert.deepEqual(teoria.note('c4').scale('ionian').mode(1).simple(), ['c', 'd', 'e', 'f', 'g', 'a', 'b']); }, - 'Get Third Mode' : function(note) { + 'Get Ionian Third Mode' : function(note) { assert.deepEqual(teoria.note('c4').scale('ionian').mode(3).simple(), ['e', 'f', 'g', 'a', 'b', 'c', 'd']); }, - 'Get Negative Mode' : function(note) { + 'Get Ionian "Eleventh" Mode' : function(note) { + assert.deepEqual(teoria.note('c4').scale('ionian').mode(11).simple(), + ['e', 'f', 'g', 'a', 'b', 'c', 'd']); + }, + + 'Get Mixolydian Third Mode' : function(note) { + assert.deepEqual(teoria.note('c4').scale('mixolydian').mode(1).simple(), + ['c', 'd', 'e', 'f', 'g', 'a', 'bb']); + }, + + 'Get Ionian Negative Mode' : function(note) { assert.deepEqual(teoria.note('c4').scale('ionian').mode(-1).simple(), - ['b', 'c', 'd', 'e', 'f', 'g', 'a',]); + ['b', 'c', 'd', 'e', 'f', 'g', 'a']); }, - 'Get Negative Tonic via Mode' : function(note) { + 'Get Mixolydian Negative Mode' : function(note) { + assert.deepEqual(teoria.note('c4').scale('mixolydian').mode(-1).simple(), + ['bb', 'c', 'd', 'e', 'f', 'g', 'a']); + }, + + 'Get Ionian Negative Tonic via Mode' : function(note) { assert.deepEqual(teoria.note('c4').scale('ionian').mode(-8).simple(), ['c', 'd', 'e', 'f', 'g', 'a', 'b']); }, - 'Get Really Negative Mode' : function(note) { + 'Get Ionian "Really" Negative Mode' : function(note) { assert.deepEqual(teoria.note('c4').scale('ionian').mode(-9).simple(), ['b', 'c', 'd', 'e', 'f', 'g', 'a',]); - } + }, + + 'Get Mixolydian "Really" Negative Mode' : function(note) { + assert.deepEqual(teoria.note('c4').scale('mixolydian').mode(-9).simple(), + ['bb', 'c', 'd', 'e', 'f', 'g', 'a']); + }, }, 'Is the #get() method octave-relative (pentatonic)?': {