diff --git a/src/audio.js b/src/audio.js index 5191cd4..153b3ae 100644 --- a/src/audio.js +++ b/src/audio.js @@ -4,9 +4,9 @@ import { Chord, Interval, Note, Scale } from "tonal"; // TODO find a better way to do this. Probably need a button to be clicked // on load? -addEventListener('mousemove', () => { - Tone.start() -}) +// addEventListener('mousemove', () => { +// Tone.start() +// }) export default class AmbientAudio { constructor() { diff --git a/src/audio.test.js b/src/audio.test.js new file mode 100644 index 0000000..91b9ff6 --- /dev/null +++ b/src/audio.test.js @@ -0,0 +1,7 @@ +'use strict' + +import * as a from './audio.js' + +test('filler test', () => { + expect(true).toBe(true) +}) diff --git a/src/chordexplorer.js b/src/chordexplorer.js index b246888..e7c1527 100644 --- a/src/chordexplorer.js +++ b/src/chordexplorer.js @@ -1,4 +1,4 @@ -import * as Chord from "tonal"; +import { Chord } from "tonal"; const defaultInversions = [ [1, 2, 3, 4], @@ -9,9 +9,11 @@ const defaultInversions = [ const defaultNotes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] const defaultChords = ["m", "M"] const defaultOctaves = [2, 3, 4] +let defaultDistance = 2 // Simple metric space on chords.Counts notes which belong to // one chord but not the other.Octave - sensitive. +// TODO this should be levenstein distance. const chordDistance = (a, b) => { let aSet = new Set(a) let bSet = new Set(b) @@ -40,36 +42,41 @@ class ChordExplorer { // TODO this could be a much more clever data structure. // But linear scanning is probably fine. - let allChords = [] + this.allChords = [] for (let i = 0; i < roots.length; i++) { for (let j = 0; j < chords.length; j++) { for (let k = 0; k < octaves.length; k++) { - let degrees = Chord.degrees(chords[j], roots[i] + octaves[k]) + let degrees = Chord.steps(chords[j], roots[i] + octaves[k]) for (let l = 0; l < inversions.length; l++) { let inversion = inversions[l] - allChords.push(inversion.map(degrees)) + this.allChords.push(inversion.map(degrees)) } } } } } - nextChord(chord) { + possibleNextChords(chord) { let possibles = [] for (let i = 0; i < this.allChords.length; i++) { - if (this.distance(chord, this.allChords[i]) < this.distance) { + if (chordDistance(chord, this.allChords[i]) === this.distance) { possibles.push(this.allChords[i]) } } + return possibles + } + + nextChord(chord) { + let possibles = this.possibleNextChords(chord) return possibles[Math.floor(Math.random() * possibles.length)] } } class DefaultChordExplorer extends ChordExplorer { constructor() { - super(defaultInversions, defaultNotes, defaultChords, defaultOctaves, chordDistance) + super(defaultInversions, defaultNotes, defaultChords, defaultOctaves, defaultDistance) } } -export { chordDistance, ChordExplorer, DefaultChordExplorer } +export { chordDistance, ChordExplorer, DefaultChordExplorer, defaultDistance } export default ChordExplorer \ No newline at end of file diff --git a/src/chordexplorer.test.js b/src/chordexplorer.test.js index 84483ac..c8516a6 100644 --- a/src/chordexplorer.test.js +++ b/src/chordexplorer.test.js @@ -1,22 +1,116 @@ 'use strict' -import { chordDistance } from './chordexplorer.js'; +import { chordDistance, ChordExplorer, DefaultChordExplorer, defaultDistance } from './chordexplorer.js'; + +// TODO figure out why proptests were so slow and maybe bring them back. test('distance between a chord and itself should be zero', () => { let chord = ["C4", "E4", "G4"] expect(chordDistance(chord, chord)).toBe(0) }) -test('distance between a chord and itself with different order should be zero', () => { +test('chordDistance between a chord and itself with different order should be zero', () => { let chord = ["C4", "E4", "G4"] let transposition = ["E4", "G4", "C4"] expect(chordDistance(chord, transposition)).toBe(0) expect(chordDistance(transposition, chord)).toBe(0) }) -test('distance between chords should be one if they have one note different', () => { +test('chordDistance between chords should be one if they have one note different', () => { let chord = ["C4", "E4"] let other = ["C4", "E4", "A4"] expect(chordDistance(chord, other)).toBe(1) expect(chordDistance(other, chord)).toBe(1) }) +test('ChordExplorer generates chords correctly', () => { + let chord = ["C4", "E4", "G4"] + let e = new ChordExplorer( + [[0, 1, 2]], + ["C"], + ["M"], + [4], + 0, + ) + expect(e.allChords).toEqual([chord]) +}) + +test('ChordExplorer generates possible chords correctly', () => { + let chord = ["C4", "E4", "G4"] + let invertedChords = [ + ["C4", "E4", "G4"], + ["E4", "G4", "C5"], + ["G4", "C5", "E5"] + ] + let inversions = [ + [0, 1, 2], + [1, 2, 3], + [2, 3, 4] + ] + let e = new ChordExplorer( + inversions, + ["C"], + ["M"], + [4], + 2, + ) + let possibles = e.possibleNextChords(chord) + expect(possibles).toEqual([invertedChords[1]]) +}) + +test('ChordExplorer never generates the same chord', () => { + let chord = ["C4", "E4", "G4"] + let e = new ChordExplorer( + [[0, 1, 2]], + ["C"], + ["M"], + [4], + 1, + ) + let next = e.nextChord(chord) + expect(next).toBeUndefined() +}) + +test('ChordExplorer generates inversions of C major', () => { + let chord = ["C4", "E4", "G4"] + let invertedChords = [ + ["C4", "E4", "G4"], + ["E4", "G4", "C5"], + ["G4", "C5", "E5"] + ] + let inversions = [ + [0, 1, 2], + [1, 2, 3], + [2, 3, 4] + ] + let e = new ChordExplorer( + inversions, + ["C"], + ["M"], + [4], + 2, + ) + for (let i = 0; i < 100; i++) { + let next = e.nextChord(chord) + expect(next).toBeDefined() + let found = false + for (let j = 0; j < invertedChords.length; j++) { + if (chordDistance(next, invertedChords[j]) === 0) { + found = true + break + } + } + expect(found).toBe(true) + } +}) + +test('DefaultChordExplorer always generates chords defaultDistance away', () => { + let e = new DefaultChordExplorer() + let chord = e.allChords[0] + for (let i = 0; i < 1000; i++) { + let next = e.nextChord(chord) + expect(next).toBeDefined() + expect(next).not.toEqual(chord) + expect(chordDistance(chord, next)).toBe(defaultDistance) + chord = next + } +}) diff --git a/src/script.js b/src/script.js index c7410df..5a394a3 100644 --- a/src/script.js +++ b/src/script.js @@ -9,8 +9,6 @@ import { TrackballControls } from 'three/addons/controls/TrackballControls.js'; import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js' import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js' -import ChordExplorer from './chordexplorer' - //import AmbientAudio from './audio' //let a = new AmbientAudio() // a.play() diff --git a/src/style.css b/src/style.css index 6eb548e..b01031d 100644 --- a/src/style.css +++ b/src/style.css @@ -1,19 +1,16 @@ -* -{ +* { margin: 0; padding: 0; } html, -body -{ +body { overflow: hidden; } -.webgl -{ +.webgl { position: fixed; top: 0; left: 0; outline: none; -} +} \ No newline at end of file