diff --git a/js/data/mn-to-try.js b/js/data/mn-to-try.js index ae06e37..7ebdf60 100644 --- a/js/data/mn-to-try.js +++ b/js/data/mn-to-try.js @@ -1,3 +1,8 @@ -rafale + reotini = { 1,3,4,4,4b,4b } ? +Possible alteration rule: + +rafale + rebotini = either { 1,3,4,I} or { 1,1,4,I} depending on what we choose the bassline to be. +It comes from https://en.wikipedia.org/wiki/Picardy_third + +to find out: lars and.. "me,me,me" (trentemoller remix) space art diff --git a/js/players/mn-sequence-renderer.js b/js/players/mn-sequence-renderer.js new file mode 100644 index 0000000..1a16496 --- /dev/null +++ b/js/players/mn-sequence-renderer.js @@ -0,0 +1,107 @@ +var renderSequenceWithTicks = function(harmonicStructure, baseSequence, ticksPerBeat) +{ + var result = new Object; + result.length = createSequencingPosition(harmonicStructure.length, ticksPerBeat); + result.sequence = []; + + var harmonyIndex = 0; + // Do all of the harmonic steps + while (harmonyIndex < harmonicStructure.structure.length) + { + var harmonyStep = harmonicStructure.structure[harmonyIndex]; + + var stepEndPosition = + harmonyIndex < harmonicStructure.structure.length - 1 + ? harmonicStructure.structure[harmonyIndex + 1].tickCount + : harmonicStructure.length; + + // At this point, we'll a new copy the base sequence and loop it until the next or final step + + var sequenceIndex = 0; + var render = true; + + var notes = harmonyStep.element.notes; + var baseSequenceOffset = 0; + + while (render) + { + var sequenceStep = baseSequence.sequence[sequenceIndex]; + var currentPosition = harmonyStep.tickCount + sequenceStep.tickCount + baseSequenceOffset; + + if (currentPosition < stepEndPosition) + { + var step = + { + position: createSequencingPosition(currentPosition, ticksPerBeat), + notes: [] + } + + sequenceStep.degrees.forEach(function(degree){ + if (degree <= notes.length) + { + step.notes.push(notes[degree-1]) + } + }) + + result.sequence.push(step); + + // next one and wrap sequence if needed + sequenceIndex++; + if (sequenceIndex >= baseSequence.sequence.length) + { + sequenceIndex = 0; + baseSequenceOffset += baseSequence.length; +// render = false; // for a single iteration + } + } + else { + render = false; + } + } + + harmonyIndex++; + } + return result; +} + +// renders a serie of note events to be played from +// a base array sequence in the form: +// { position: "1.1.1", degrees: [1,3] } +// and a harmonic structure +// structure: [{ position: "1.1.1", midiNoteList: [35,67] }] +// length: "4.1.1" // In bars + +renderSequence = function(harmonicStructure, baseSequence, signature, ticksPerBeat) +{ + // Convert base sequence to use ticks for position + var tickBaseSequence = new Object; + tickBaseSequence.length = stringPositionToTicks(baseSequence.length, signature, ticksPerBeat); + tickBaseSequence.sequence = []; + + baseSequence.sequence.forEach(function(element) + { + tickBaseSequence.sequence.push( + { + tickCount: stringPositionToTicks(element.position, signature, ticksPerBeat), + degrees: element.degrees + }); + }) + + // Convert harmonicStructure to use ticks for position + + var tickBaseStructure = new Object; + tickBaseStructure.length = stringPositionToTicks(harmonicStructure.length, signature, ticksPerBeat); + tickBaseStructure.structure = [] ; + + harmonicStructure.structure.forEach(function(item) + { + tickBaseStructure.structure.push( + { + tickCount: stringPositionToTicks(item.position, signature, ticksPerBeat), + element: item.element + } + ) + }); + + return renderSequenceWithTicks(tickBaseStructure, tickBaseSequence, ticksPerBeat); +} diff --git a/js/players/mn-sequencer-renderer.js b/js/players/mn-sequencer-renderer.js deleted file mode 100644 index 0f676cb..0000000 --- a/js/players/mn-sequencer-renderer.js +++ /dev/null @@ -1,10 +0,0 @@ - -// renders a serie of note events to be played from -// a base array sequence in the form: -// { position: "1.1.1", notes: [1,3] } -// and a harmonic structure - -renderSequence = function(harmonicStructure, baseSequence) -{ - -} diff --git a/js/players/players.js b/js/players/players.js new file mode 100644 index 0000000..dda0a10 --- /dev/null +++ b/js/players/players.js @@ -0,0 +1 @@ +require("./mn-sequence-renderer.js") diff --git a/js/progression/mn-chord-progression.js b/js/progression/mn-chord-progression.js index 9e23d40..eaa4b16 100644 --- a/js/progression/mn-chord-progression.js +++ b/js/progression/mn-chord-progression.js @@ -1,16 +1,8 @@ // Build a chord object to manipulate the content -ProgressionElement = function (notes, bass) +ProgressionElement = function (notes) { - this.notes_ = notes; - this.bass_ = bass; -} - -// return the notes - -ProgressionElement.prototype.notes = function() -{ - return this.notes_; + this.notes = notes; } // Chord progression helper object @@ -31,7 +23,6 @@ ChordProgression.prototype.makeChord = function(degree, alteration) { var n = this.scaleNotes_; var root = n[degree-1]; - var bass = root - 24; // If there's an alteration, force it if (alteration && alteration.length_ != 0) @@ -43,10 +34,10 @@ ChordProgression.prototype.makeChord = function(degree, alteration) current += interval; notes.push(current); }) - return new ProgressionElement(notes, bass ) + return new ProgressionElement(notes) } // return the default chord for the scale - return new ProgressionElement([n[degree-1], n[degree+1], n[degree+3]], bass); + return new ProgressionElement([n[degree-1], n[degree+1], n[degree+3]]); } // creates a chord progression from a list of scale degree diff --git a/js/progression/mn-voicing.js b/js/progression/mn-voicing.js index 39e49e1..8ba7989 100644 --- a/js/progression/mn-voicing.js +++ b/js/progression/mn-voicing.js @@ -42,7 +42,7 @@ var rectify_progression_sequential = function(sequence) { for (var i = 0; i < sequence.length-1; i++) { - sequence[i+1].notes_ = rectify_closest(sequence[i].notes_,sequence[i+1].notes_); + sequence[i+1].notes = rectify_closest(sequence[i].notes,sequence[i+1].notes); } } @@ -50,7 +50,7 @@ var rectify_progression_to_first = function(sequence) { for (var i = 0; i < sequence.length-1; i++) { - sequence[i+1].notes_ = rectify_closest(sequence[0].notes_,sequence[i+1].notes_); + sequence[i+1].notes = rectify_closest(sequence[0].notes,sequence[i+1].notes); } } @@ -59,15 +59,15 @@ var rectify_progression_inwards = function(sequence) var leftIndex = 1; var rightIndex = sequence.length -1; - sequence[rightIndex].notes_ = rectify_closest(sequence[0].notes_, sequence[rightIndex].notes_); + sequence[rightIndex].notes = rectify_closest(sequence[0].notes, sequence[rightIndex].notes); rightIndex--; while (leftIndex < rightIndex) { - sequence[leftIndex].notes_ = rectify_closest(sequence[leftIndex -1].notes_, sequence[leftIndex].notes_); + sequence[leftIndex].notes = rectify_closest(sequence[leftIndex -1].notes, sequence[leftIndex].notes); if (rightIndex > leftIndex) { - sequence[rightIndex].notes_ = rectify_closest(sequence[rightIndex+1].notes_, sequence[rightIndex].notes_); + sequence[rightIndex].notes = rectify_closest(sequence[rightIndex+1].notes, sequence[rightIndex].notes); } rightIndex--; leftIndex++; diff --git a/js/sequencing/mn-beat-time-line.js b/js/sequencing/mn-beat-time-line.js index 7dd9636..9b06128 100644 --- a/js/sequencing/mn-beat-time-line.js +++ b/js/sequencing/mn-beat-time-line.js @@ -30,11 +30,56 @@ createSequencingPosition = function(tickCount, ticksPerBeat) return position; } +ticksFromPosition = function(position) +{ + var tickCount = position.beats_; + tickCount = 4 * tickCount + position.sixteenth_; + tickCount = tickCount * (position.ticksPerBeat_ / 4) + position.ticks_; + return tickCount; +} + +comparePositions = function(pos1, pos2) +{ + var ticks1 = ticksFromPosition(pos1); + var ticks2 = ticksFromPosition(pos2); + if (ticks1 == ticks2) return 0; + return ticks1 < ticks2 ? -1 : 1; +} + +addPositions = function(pos1, pos2) +{ + var ticks1 = ticksFromPosition(pos1); + var ticks2 = ticksFromPosition(pos2); + return createSequencingPosition(ticks1 + ticks2, pos1.ticksPerBeat_); +} + +moduloPosition = function(pos1, pos2) +{ + var ticks1 = ticksFromPosition(pos1); + var ticks2 = ticksFromPosition(pos2); + return createSequencingPosition(ticks1 % ticks2, pos1.ticksPerBeat_); +} + +subPositions = function(pos1, pos2) +{ + var ticks1 = ticksFromPosition(pos1); + var ticks2 = ticksFromPosition(pos2); + return createSequencingPosition(ticks1 - ticks2, pos1.ticksPerBeat_); +} + sixteenthCount = function(position) { return position.sixteenth_ + 4 * position.beats_; } + +stringPositionToTicks = function(position, signature, ticksPerBeat) +{ + var elementPosition = convertToPosition(position, signature, ticksPerBeat); + var elementPositionInTicks = ticksFromPosition(elementPosition); + return elementPositionInTicks; +} + // converts a beat string ("1.1.3.2") to a sequencing position convertToPosition = function(beatString, signature, ticksPerBeat) diff --git a/package.json b/package.json index b3bcb52..2854c6d 100644 --- a/package.json +++ b/package.json @@ -10,5 +10,6 @@ "pegjs":"x.x", "hapi":"x.x", "inert":"x.x" + "lodash":"x.x" } } diff --git a/terminal/application.js b/terminal/application.js index 493e3c4..190608a 100644 --- a/terminal/application.js +++ b/terminal/application.js @@ -77,7 +77,7 @@ Application.prototype.updateSequence = function() // create chord progression var chordSequence = makeChordProgression(this.rootNote_, this.scale_, this.progression_); // apply desired inversion to the first chord - chordSequence[0].notes_ = invertChord(chordSequence[0].notes_,this.inversion_); + chordSequence[0].notes = invertChord(chordSequence[0].notes,this.inversion_); // apply voicing rectify_progression(chordSequence, this.rectificationMethod_); console.log("should send chord seaquence") @@ -91,7 +91,7 @@ Application.prototype.currentSequenceString = function() var chordSequence = this.chordSequencer_.getContent(); chordSequence.forEach(function (chord) { - chordnameList += chordname(chord.notes_) + ","; + chordnameList += chordname(chord.notes) + ","; }) return chordnameList;*/ } @@ -112,7 +112,10 @@ Application.prototype.exit = function(arguments) Application.prototype.rebuild = function() { var chordSequence = this.harmony_.rebuild(); - this.engine_.setChordSequence(chordSequence); + if (chordSequence) + { + this.engine_.setChordSequence(chordSequence); + } } Application.prototype.setScale = function(arguments) @@ -127,7 +130,7 @@ Application.prototype.setScale = function(arguments) scaleChordsNameList = ""; scaleChords.forEach(function (chord) { - scaleChordsNameList += chordname(chord.notes_) + ","; + scaleChordsNameList += chordname(chord.notes) + ","; }) return "Scale chords: " + scaleChordsNameList; diff --git a/terminal/harmony-engine.js b/terminal/harmony-engine.js index 7db2293..63e310b 100644 --- a/terminal/harmony-engine.js +++ b/terminal/harmony-engine.js @@ -36,7 +36,7 @@ HarmonyEngine.prototype.rebuild = function() // create chord progression var chordSequence = makeChordProgression(this.rootNote_, this.scale_, this.progression_); // apply desired inversion to the first chord - chordSequence[0].notes_ = invertChord(chordSequence[0].notes_,this.inversion_); + chordSequence[0].notes = invertChord(chordSequence[0].notes,this.inversion_); // apply voicing rectify_progression(chordSequence, this.rectificationMethod_); return chordSequence; diff --git a/terminal/main.js b/terminal/main.js index 4f86570..e417007 100644 --- a/terminal/main.js +++ b/terminal/main.js @@ -1,8 +1,8 @@ 'use strict'; -//if ( global.v8debug) { -// global.v8debug.Debug.setBreakOnException(); // enable it, global.v8debug is only defined when the --debug or --debug-brk flag is set -//} +if ( global.v8debug) { + global.v8debug.Debug.setBreakOnException(); // enable it, global.v8debug is only defined when the --debug or --debug-brk flag is set +} var app = require('./application.js'); app.init({ diff --git a/terminal/playback-engine.js b/terminal/playback-engine.js index 5ca3e88..a54418f 100644 --- a/terminal/playback-engine.js +++ b/terminal/playback-engine.js @@ -33,7 +33,7 @@ ChordPlayer.prototype.tick = function(position) &&(step.position.ticks_ == wrapped.ticks_)) { console.log("queing " + JSON.stringify(step)); - noteQueuer.queueNotes(step.chord.notes_); + noteQueuer.queueNotes(step.element.notes); } }) } @@ -123,20 +123,20 @@ PlaybackEngine.prototype.run = function() this.heartbeat_.run(); } -PlaybackEngine.prototype.setChordSequence = function(chordSequence) +PlaybackEngine.prototype.setChordSequence = function(harmonicProgression) { var currentBar = 1; var timeline = new Object; var signature = this.signature_; timeline.sequence_ = []; - chordSequence.forEach(function(chord) + harmonicProgression.forEach(function(element) { var position = new SequencingPosition(this.ticksPerBeat_); position.beats_ = (currentBar - 1) * signature.denominator; timeline.sequence_.push({ position : position, - chord : chord + element : element }); currentBar++; }) diff --git a/test/scale-detection.js b/test/scale-detection.js index 7109ae2..3cb08bf 100644 --- a/test/scale-detection.js +++ b/test/scale-detection.js @@ -17,7 +17,7 @@ function makeChordList() chordSequence.forEach(function (chord) { - chordList.push(chordname(chord.notes_)); + chordList.push(chordname(chord.notes)); }) return chordList; } diff --git a/test/test-chord-progression.js b/test/test-chord-progression.js index df4c7e4..1bdb4c8 100644 --- a/test/test-chord-progression.js +++ b/test/test-chord-progression.js @@ -7,18 +7,18 @@ require("../js/mn-scale.js"); var sequence = [1,6]; var progression = makeChordProgression("c3", "minor", sequence); -assert.equal(chordname(progression[0].notes_), "cm"); -assert.equal(chordname(progression[1].notes_), "g#"); +assert.equal(chordname(progression[0].notes), "cm"); +assert.equal(chordname(progression[1].notes), "g#"); var sequence = ["1","6"]; var progression = makeChordProgression("c3", "minor", sequence); -assert.equal(chordname(progression[0].notes_), "cm"); -assert.equal(chordname(progression[1].notes_), "g#"); +assert.equal(chordname(progression[0].notes), "cm"); +assert.equal(chordname(progression[1].notes), "g#"); var sequence = ["1","5M"]; var progression = makeChordProgression("c3", "major", sequence); -assert.equal(chordname(progression[0].notes_), "c"); -assert.equal(chordname(progression[1].notes_), "g"); +assert.equal(chordname(progression[0].notes), "c"); +assert.equal(chordname(progression[1].notes), "g"); // Rectification diff --git a/test/test-sequence-renderer.js b/test/test-sequence-renderer.js new file mode 100644 index 0000000..36b3f4b --- /dev/null +++ b/test/test-sequence-renderer.js @@ -0,0 +1,88 @@ +require("../js/players/players.js"); +require("../js/sequencing/sequencing.js"); +require("../js/progression/progression.js"); +require("../js/theory/theory.js"); +require("../js/mn-scale.js"); + +var assert = require("assert"); + +function testSequenceRendering(baseSequence, progression, expected) +{ + var signature = new Signature; + var ticksPerBeat = kTicksPerBeats; + + // Build the basic chord structure + + var chords = makeChordProgression(progression.root, progression.scale, progression.degrees); + + // Puts one chord at every bar, making a 'harmonic structure' + + var startBar = 1; + + var harmonicStructure = { + length: 0, + structure: [], + }; + + chords.forEach(function(element) + { + harmonicStructure.structure.push( + { + position: ""+ startBar +".1.1", + element: element, + } + ) + startBar++; + }) + + harmonicStructure.length = ""+ startBar +".1.1"; + + // render the combination + + var rendered = renderSequence(harmonicStructure, baseSequence, signature, ticksPerBeat); + + // Test against expected + + expected.length = convertToPosition(expected.length, signature, ticksPerBeat) + expected.sequence.forEach(function(element) + { + element.position = convertToPosition(element.position, signature, ticksPerBeat) + }); + + assert.deepEqual(rendered, expected); +} + +//============================================================================== + +var baseSequence = { + length : "1.4.1", + sequence: + [ + { position: "1.1.1", degrees: [1,2]}, + { position: "1.2.1", degrees: [2]}, + { position: "1.3.1", degrees: [3,1]}, + ] + } + +var progression = { + root: "c3", + scale: "major", + degrees: [1,5] +} + +var expected = { + length: "3.1.1", + sequence: + [ + { position: "1.1.1", notes: [48, 52]}, + { position: "1.2.1", notes: [52]}, + { position: "1.3.1", notes: [55, 48]}, + { position: "1.4.1", notes: [48, 52]}, + { position: "2.1.1", notes: [55, 59]}, + { position: "2.2.1", notes: [59]}, + { position: "2.3.1", notes: [62, 55]}, + { position: "2.4.1", notes: [55, 59]}, + ] +} + +testSequenceRendering(baseSequence, progression, expected); diff --git a/test/test-sequencing-position.js b/test/test-sequencing-position.js new file mode 100644 index 0000000..6bd5af0 --- /dev/null +++ b/test/test-sequencing-position.js @@ -0,0 +1,76 @@ +require("../js/sequencing/sequencing.js") + +var assert = require("assert"); + +function testAdd(b1,s1,t1,b2,s2,t2,br,sr,tr) +{ + var pos1 = new SequencingPosition(kTicksPerBeats); + var pos2 = new SequencingPosition(kTicksPerBeats); + + pos1.beats_ = b1; + pos1.sixteenth_ = s1; + pos1.ticks_ = t1; + + pos2.beats_ = b2; + pos2.sixteenth_ = s2; + pos2.ticks_ = t2; + + var result = addPositions(pos1, pos2); + assert.equal(result.beats_, br); + assert.equal(result.sixteenth_, sr); + assert.equal(result.ticks_, tr); +} + +function testSub(b1,s1,t1,b2,s2,t2,br,sr,tr) +{ + var pos1 = new SequencingPosition(kTicksPerBeats); + var pos2 = new SequencingPosition(kTicksPerBeats); + + pos1.beats_ = b1; + pos1.sixteenth_ = s1; + pos1.ticks_ = t1; + + pos2.beats_ = b2; + pos2.sixteenth_ = s2; + pos2.ticks_ = t2; + + var result = subPositions(pos1, pos2); + assert.equal(result.beats_, br); + assert.equal(result.sixteenth_, sr); + assert.equal(result.ticks_, tr); +} + +function testCompare(b1,s1,t1,b2,s2,t2,result) +{ + var pos1 = new SequencingPosition(kTicksPerBeats); + var pos2 = new SequencingPosition(kTicksPerBeats); + + pos1.beats_ = b1; + pos1.sixteenth_ = s1; + pos1.ticks_ = t1; + + pos2.beats_ = b2; + pos2.sixteenth_ = s2; + pos2.ticks_ = t2; + + assert.equal(comparePositions(pos1, pos2), result); +} + +function testStringConversion(s,br,sr,tr) +{ + var result = convertToPosition(s, new Signature, kTicksPerBeats); + assert.equal(result.beats_, br); + assert.equal(result.sixteenth_, sr); + assert.equal(result.ticks_, tr); +} + +testStringConversion("1.1.1",0,0,0); +testStringConversion("2.1.1",4,0,0); + +testAdd(1,1,0, 3,3,1, 5,0,1); + +testSub(3,3,0, 1,1,1, 2,1,5); + +testCompare(1,1,0, 3,3,1, -1); +testCompare(2,1,0, 2,1,0, 0); +testCompare(3,3,1, 1,1,0, 1);