Backtrack #1
43
app.js
43
app.js
@@ -1,10 +1,3 @@
|
||||
import {
|
||||
sopranoTones, altoTones, tenorTones, bassTones,
|
||||
extractPitchset,
|
||||
} from "./js/toneGeneration.js";
|
||||
|
||||
const pitchsets = [sopranoTones, altoTones, tenorTones, bassTones];
|
||||
|
||||
// initialize four synth voices
|
||||
const soprano = new Tone.Synth().toDestination();
|
||||
const alto = new Tone.Synth().toDestination();
|
||||
@@ -20,47 +13,18 @@ export const audioTest = () => {
|
||||
}
|
||||
|
||||
// allows a chord to be generated with input from another function
|
||||
export const soundChord = (pitches) => {
|
||||
const [s,a,t,b] = pitches;
|
||||
// pitchNames: array of strings
|
||||
export const soundChord = (pitchNames) => {
|
||||
const [s,a,t,b] = pitchNames;
|
||||
soprano.triggerAttackRelease(s, "8n");
|
||||
alto.triggerAttackRelease(a, "8n");
|
||||
tenor.triggerAttackRelease(t, "8n");
|
||||
bass.triggerAttackRelease(b, "8n");
|
||||
}
|
||||
|
||||
// initial test: generate a single, random chord
|
||||
export const fullRandomChord = () => {
|
||||
let pitches = [];
|
||||
for (let voice of pitchsets) {
|
||||
// finds a random index, excluding any which may already exist in the array
|
||||
let index;
|
||||
|
||||
do {
|
||||
index = Math.floor(Math.random() * 100) % voice.length;
|
||||
} while (pitches.includes(voice[index]));
|
||||
|
||||
pitches.push(voice[index]);
|
||||
console.log(voice[index]);
|
||||
}
|
||||
|
||||
for (let i = 0; i < pitches.length; i++) {
|
||||
if (pitches[i] === pitches[i+1]) {
|
||||
console.log("CAUGHT");
|
||||
}
|
||||
}
|
||||
|
||||
soundChord(pitches);
|
||||
extractPitchset(pitches);
|
||||
}
|
||||
|
||||
// set up transport
|
||||
let clock = 0;
|
||||
let slowClock = 0;
|
||||
|
||||
const transportStart = document.getElementById('transport-start');
|
||||
|
||||
let transport;
|
||||
|
||||
const loop = new Tone.Loop((time) => {
|
||||
audioTest();
|
||||
}, "8n").start(0);
|
||||
@@ -70,4 +34,3 @@ loop.probability = 0.8;
|
||||
transportStart.onclick = () => {
|
||||
Tone.Transport.start();
|
||||
}
|
||||
|
||||
|
||||
10
index.html
10
index.html
@@ -112,15 +112,17 @@
|
||||
<button id="mute-all">Mute all oscillators</button>
|
||||
<button id="reset-lfos">Reset LFO values to default</button>
|
||||
<button id="rand-chord">Full random chord</button>
|
||||
<button id="eval-chord">Evaluated chord</button>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Tone.js library -->
|
||||
<script type="module" src="https://unpkg.com/tone@14.7.77/build/Tone.js"></script>
|
||||
<!-- internal scripts -->
|
||||
<script type="module" src="./js/inputHandling.js"></script>
|
||||
<script type="module" src="./js/toneGeneration.js"></script>
|
||||
<script type="module" src="./js/styleUtils.js"></script>
|
||||
<script type="module" src="app.js"></script>
|
||||
<script type="module" src="./src/styleUtils.js"></script>
|
||||
<script type="module" src="./src/inputHandling.js"></script>
|
||||
<script type="module" src="./src/harmonyUtil.js"></script>
|
||||
<script type="module" src="./src/audioUtil.js"></script>
|
||||
<script type="module" src="./app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,64 +0,0 @@
|
||||
import { extractPitchset } from "./toneGeneration.js";
|
||||
|
||||
// interval definitions:
|
||||
const intervals = {
|
||||
0: "unison",
|
||||
1: "minor second",
|
||||
2: "major second",
|
||||
3: "minor third",
|
||||
4: "major third",
|
||||
5: "perfect fourth",
|
||||
6: "tritone"
|
||||
// all intervals beyond this invert to one of the previous intervals
|
||||
}
|
||||
|
||||
// helper functions
|
||||
const transposePitches = (pitchNames, interval) => {
|
||||
let transposed = [];
|
||||
pitchNames.forEach(pitch => transposed.push((pitch + interval) % 12));
|
||||
return transposed;
|
||||
}
|
||||
|
||||
const findVector = (pitches) => {
|
||||
let sorted = pitches.sort((x,y) => x - y);
|
||||
// sorted = sorted.filter((num, idx) => {
|
||||
// return sorted.indexOf(num) === idx;
|
||||
// });
|
||||
|
||||
// finds each interval and logs it as a duple
|
||||
let intervalClasses = [];
|
||||
for (let i = 0; i < sorted.length; i++) {
|
||||
let j = i+1;
|
||||
|
||||
// does not allow out of range values in the proceeding loop
|
||||
if (j >= sorted.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
do {
|
||||
let thing = (sorted[j] - sorted[i]) % 6
|
||||
if (!(intervalClasses.includes(thing))) {
|
||||
intervalClasses.push(thing);
|
||||
}
|
||||
j++;
|
||||
} while (j < sorted.length);
|
||||
}
|
||||
|
||||
intervalClasses = intervalClasses.sort((x,y) => x-y);
|
||||
return intervalClasses;
|
||||
}
|
||||
|
||||
// analysis
|
||||
let dMajor = extractPitchset(["D", "F#", "A", "D"]);
|
||||
const eMajor = transposePitches(dMajor, 2);
|
||||
console.log(eMajor);
|
||||
console.log('');
|
||||
|
||||
let dMajVector = findVector(dMajor);
|
||||
console.log(dMajVector);
|
||||
|
||||
let complexVector = findVector([0,3,4,7,8,11]);
|
||||
console.log(complexVector);
|
||||
|
||||
let splitThird = findVector([0,3,4,7]);
|
||||
console.log(splitThird);
|
||||
@@ -1,35 +0,0 @@
|
||||
// we start with a selection of pitches that generally work okay together
|
||||
export const sopranoTones = ["B5", "A5", "G5", "F#5", "F5", "E5", "D5", "C5", "B4", "Bb4", "A4", "G4", "F#4", "F4", "E4"];
|
||||
export const altoTones = ["E5", "D5", "C5", "B4", "Bb4", "A4", "G4", "F#4", "F4", "E4", "D4", "C4", "B3", "Bb3", "A3", "G3"];
|
||||
export const tenorTones = ["G4", "F#4", "F4", "E4", "D4", "C4", "B3", "Bb3", "A3", "G3", "F3", "E3", "D3", "C3"];
|
||||
export const bassTones = ["C2", "D2", "E2", "F2", "G2", "A2", "Bb2", "B2", "C3", "D3", "E3", "F3", "G3"];
|
||||
|
||||
// now we define some rules to allow for the program to follow so it can some basic tenets of music theory
|
||||
// we're going to include all pitches, so that it can use semitone-based pitch logic.
|
||||
// this is focused on base-12, something computers understand quite well
|
||||
const musicalPitches = ['A', "Bb", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"];
|
||||
|
||||
export const extractPitchset = (pitches) => {
|
||||
// 1) determine pitch set from given array of pitches
|
||||
let pitchset = [];
|
||||
|
||||
for (let each of pitches) {
|
||||
// filters numbers from above tones
|
||||
const str = each;
|
||||
const regex = /[0-9]/g;
|
||||
const withoutNums = str.replace(regex, '');
|
||||
const pitchNumber = musicalPitches.indexOf(withoutNums);
|
||||
|
||||
// ... so that they may be mapped onto numbers corresponding to the chromatic scale
|
||||
pitchset.push(pitchNumber);
|
||||
}
|
||||
|
||||
// these are sorted from lowest to highest index (something like an interval vector)
|
||||
pitchset.sort((a,b) => a < b);
|
||||
console.log(pitchset);
|
||||
|
||||
return pitchset;
|
||||
}
|
||||
|
||||
// no tritones
|
||||
// no minor 2nds or major 7ths
|
||||
21
src/audioUtil.js
Normal file
21
src/audioUtil.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { soundChord } from "../app.js";
|
||||
import { getRandomPitches } from "./vector_logic/getRandomPitches.js";
|
||||
import { extractPitchset } from "./vector_logic/extractPitchset.js";
|
||||
import { getProceduralPitches } from "./vector_logic/getProceduralPitches.js";
|
||||
|
||||
// initial test: generate a single, random chord
|
||||
export const fullRandomChord = () => {
|
||||
let pitches = getRandomPitches();
|
||||
soundChord(pitches);
|
||||
let pitchset = extractPitchset(pitches);
|
||||
|
||||
return pitchset;
|
||||
}
|
||||
|
||||
export const evaluatedChord = (prevPitches = ["C3", "G3", "C4", "G4"]) => {
|
||||
let pitches = getProceduralPitches(prevPitches);
|
||||
soundChord(pitches);
|
||||
let pitchset = extractPitchset(pitches);
|
||||
|
||||
return pitchset;
|
||||
}
|
||||
27
src/harmonyUtil.js
Normal file
27
src/harmonyUtil.js
Normal file
@@ -0,0 +1,27 @@
|
||||
export const sopranoTones = ["B5", "A5", "G5", "F#5", "F5", "E5", "D5", "C5", "B4", "Bb4", "A4", "G4", "F#4", "F4", "E4"];
|
||||
export const altoTones = ["E5", "D5", "C5", "B4", "Bb4", "A4", "G4", "F#4", "F4", "E4", "D4", "C4", "B3", "Bb3", "A3", "G3"];
|
||||
export const tenorTones = ["G4", "F#4", "F4", "E4", "D4", "C4", "B3", "Bb3", "A3", "G3", "F3", "E3", "D3", "C3"];
|
||||
export const bassTones = ["C2", "D2", "E2", "F2", "G2", "A2", "Bb2", "B2", "C3", "D3", "E3", "F3", "G3"];
|
||||
|
||||
export const pitchsets = [bassTones, tenorTones, altoTones, sopranoTones];
|
||||
|
||||
export const musicalPitches = ['A', "Bb", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"];
|
||||
|
||||
// interval definitions:
|
||||
export const intervalNames = {
|
||||
0: "unison",
|
||||
1: "minor second",
|
||||
2: "major second",
|
||||
3: "minor third",
|
||||
4: "major third",
|
||||
5: "perfect fourth",
|
||||
6: "tritone"
|
||||
// all intervals beyond this invert to one of the previous intervals
|
||||
}
|
||||
|
||||
// helper functions
|
||||
const transposePitches = (pitchNames, interval) => {
|
||||
let transposed = [];
|
||||
pitchNames.forEach(pitch => transposed.push((pitch + interval) % 12));
|
||||
return transposed;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { audioTest, fullRandomChord } from '../app.js';
|
||||
import { audioTest } from '../app.js';
|
||||
import { fullRandomChord, evaluatedChord } from './audioUtil.js';
|
||||
|
||||
// slider variables referring to DOM
|
||||
export const sopranoVol = document.getElementById('soprano-vol');
|
||||
@@ -64,3 +65,6 @@ synthButton.onclick = audioTest;
|
||||
|
||||
const randChord = document.getElementById('rand-chord');
|
||||
randChord.onclick = fullRandomChord;
|
||||
|
||||
const evalChord = document.getElementById('eval-chord');
|
||||
evalChord.onclick = evaluatedChord;
|
||||
21
src/vector_logic/evaluateVector.js
Normal file
21
src/vector_logic/evaluateVector.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { getRandomPitches } from './getRandomPitches.js';
|
||||
import { extractPitchset } from "./extractPitchset.js";
|
||||
import { findVector } from "./findVector.js";
|
||||
|
||||
export const evaluateVector = (vector) => {
|
||||
return ((vector.includes(1) || vector.includes(6)));
|
||||
}
|
||||
|
||||
export const rejectDissonance = (pitchset) => {
|
||||
const vector = findVector(pitchset);
|
||||
|
||||
// returns the pitchset and its vector if evaluateVector returns true,
|
||||
if (evaluateVector(vector)) return vector;
|
||||
|
||||
// and recursively calls the function otherwise.
|
||||
if (!evaluateVector(vector)) {
|
||||
let newPitches = getRandomPitches();
|
||||
let newPitchset = extractPitchset(newPitches);
|
||||
rejectDissonance(newPitchset);
|
||||
};
|
||||
}
|
||||
2
src/vector_logic/extractPitchName.js
Normal file
2
src/vector_logic/extractPitchName.js
Normal file
@@ -0,0 +1,2 @@
|
||||
const regex = /[A-Gb#]/g;
|
||||
export const extractPitchName = (tonePitchName) => tonePitchName.match(regex).join('');
|
||||
22
src/vector_logic/extractPitchset.js
Normal file
22
src/vector_logic/extractPitchset.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { musicalPitches } from '../harmonyUtil.js';
|
||||
import { extractPitchName } from './extractPitchName.js';
|
||||
|
||||
// converts pitches in Tone.js string format to base-12 number pitchsets
|
||||
export const extractPitchset = (pitches) => {
|
||||
// 1) determine pitch set from given array of pitches
|
||||
let pitchset = [];
|
||||
|
||||
for (let each of pitches) {
|
||||
// filters numbers from above tones
|
||||
const str = each;
|
||||
const withoutNums = extractPitchName(str);
|
||||
const pitchNumber = musicalPitches.indexOf(withoutNums);
|
||||
|
||||
// ... so that they may be mapped onto numbers corresponding to the chromatic scale
|
||||
pitchset.push(pitchNumber);
|
||||
}
|
||||
|
||||
// these are sorted from lowest to highest index (something like an interval vector)
|
||||
pitchset.sort((a,b) => a < b);
|
||||
return pitchset;
|
||||
}
|
||||
26
src/vector_logic/findVector.js
Normal file
26
src/vector_logic/findVector.js
Normal file
@@ -0,0 +1,26 @@
|
||||
// takes pitches as numbers and converts them to a vector
|
||||
export const findVector = (pitches) => {
|
||||
let sorted = pitches.sort((x,y) => x - y);
|
||||
|
||||
// finds each interval and logs it as a duple
|
||||
let intervalClasses = [];
|
||||
for (let i = 0; i < sorted.length; i++) {
|
||||
let j = i+1;
|
||||
|
||||
// does not allow out of range values in the proceeding loop
|
||||
if (j >= sorted.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
do {
|
||||
let thing = (sorted[j] - sorted[i]) % 6
|
||||
if (!(intervalClasses.includes(thing))) {
|
||||
intervalClasses.push(thing);
|
||||
}
|
||||
j++;
|
||||
} while (j < sorted.length);
|
||||
}
|
||||
|
||||
intervalClasses = intervalClasses.sort((x,y) => x-y);
|
||||
return intervalClasses;
|
||||
}
|
||||
54
src/vector_logic/getProceduralPitches.js
Normal file
54
src/vector_logic/getProceduralPitches.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import { pitchsets, musicalPitches } from "../harmonyUtil.js";
|
||||
import { extractPitchName } from "./extractPitchName.js";
|
||||
import { getRandomIndex } from "./getRandomPitches.js";
|
||||
|
||||
// an additional method based on the structure of getRandomPitches,
|
||||
// but taking some principles of music theory into account.
|
||||
export const getProceduralPitches = (prevPitches) => {
|
||||
// prevPitches is passed in to ensure there is no linear dissonance within voices
|
||||
let pitches = [];
|
||||
let formattedPitches = [];
|
||||
|
||||
for (let voice of pitchsets) {
|
||||
let index;
|
||||
let formattedPitch;
|
||||
|
||||
while (pitches.length <= pitchsets.indexOf(voice)) {
|
||||
// the first section of this while loop is more free.
|
||||
index = getRandomIndex(voice);
|
||||
formattedPitch = extractPitchName(voice[index]);
|
||||
|
||||
// this initial condition will apply only to the bass voice,
|
||||
if (!pitches.length) {
|
||||
pitches.push(voice[index]);
|
||||
} else {
|
||||
// now we need some repeating logic for the remaining four voices
|
||||
while (pitches.length !== (pitchsets.indexOf(voice) + 1)) {
|
||||
index = getRandomIndex(voice);
|
||||
pitches.push(voice[index]);
|
||||
|
||||
|
||||
for (let i = 0; i < pitches.length; i++) {
|
||||
let numVals = [];
|
||||
|
||||
for (let j = i; j < i + 2; j++) {
|
||||
console.log(pitches[j]);
|
||||
let extracted = extractPitchName(pitches[j]);
|
||||
numVals.push(musicalPitches.indexOf(extracted) + 1);
|
||||
}
|
||||
|
||||
let difference = Math.abs(numVals.reduce((x,y) => x - y));
|
||||
if (difference === 1 || difference === 6) {
|
||||
pitches.pop();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(pitches);
|
||||
return pitches;
|
||||
}
|
||||
32
src/vector_logic/getRandomPitches.js
Normal file
32
src/vector_logic/getRandomPitches.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { pitchsets } from "../harmonyUtil.js";
|
||||
import { extractPitchName } from "./extractPitchName.js";
|
||||
|
||||
// helper function for assigning a random index for a given voice's pitchset
|
||||
export const getRandomIndex = (voice) => Math.floor(Math.random() * 100) % voice.length;
|
||||
|
||||
export const getRandomPitches = () => {
|
||||
// pitches stored in Tone.js string format
|
||||
let pitches = [];
|
||||
let formattedPitches = [];
|
||||
|
||||
for (let voice of pitchsets) {
|
||||
// finds a random index, excluding any which may already exist in the array
|
||||
let index;
|
||||
let formattedPitch;
|
||||
|
||||
// loops until four distinct chord members are received
|
||||
while (formattedPitches.length <= pitchsets.indexOf(voice)) {
|
||||
index = getRandomIndex(voice);
|
||||
|
||||
formattedPitch = extractPitchName(voice[index]);
|
||||
if (!formattedPitches.includes(formattedPitch)) {
|
||||
formattedPitches.push(formattedPitch);
|
||||
pitches.push(voice[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(formattedPitches);
|
||||
console.log(pitches);
|
||||
return pitches;
|
||||
}
|
||||
80
src/vector_logic/helper.js
Normal file
80
src/vector_logic/helper.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import { pitchsets, musicalPitches } from '../harmonyUtil.js';
|
||||
import { extractPitchName } from './extractPitchName.js';
|
||||
import { getRandomIndex } from './getRandomPitches.js';
|
||||
|
||||
const iteratePitchsets = () => {
|
||||
let pitches = [];
|
||||
let result = [];
|
||||
|
||||
const tryNewPitch = () => {
|
||||
result = [];
|
||||
|
||||
for (let voice of pitchsets) {
|
||||
let idx = getRandomIndex(voice);
|
||||
let pitchNum = musicalPitches.indexOf(extractPitchName(voice[idx]));
|
||||
|
||||
pitches.push([pitchNum, voice[idx]]);
|
||||
}
|
||||
|
||||
console.log(pitches);
|
||||
|
||||
for (let i = 0; i < pitches.length; i++) {
|
||||
for (let j = i; j < pitches.length; j++) {
|
||||
let difference = Math.abs(pitches[i][0] - pitches[j][0]);
|
||||
if (difference === 1 || difference === 6) {
|
||||
result.push(["BAD", [i, j], pitches[i], pitches[j]]);
|
||||
} else {
|
||||
result.push(["GOOD", [i, j], pitches[i], pitches[j]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(result);
|
||||
}
|
||||
|
||||
tryNewPitch();
|
||||
|
||||
for (let entry in result) {
|
||||
if (entry[0] === "BAD") {
|
||||
iteratePitchsets();
|
||||
}
|
||||
}
|
||||
|
||||
console.log(pitches);
|
||||
}
|
||||
|
||||
iteratePitchsets();
|
||||
|
||||
|
||||
|
||||
function twoPointIteration() {
|
||||
let caught = false;
|
||||
let data = [1,2,3,4,5,6];
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
console.log(data[i]);
|
||||
for (let j = 0; j < data.length; j++) {
|
||||
if (data[i] === data[j]) continue;
|
||||
|
||||
let difference = Math.abs(data[j] - data[i]);
|
||||
|
||||
if (difference === 3) {
|
||||
difference = "caught: 3";
|
||||
}
|
||||
|
||||
console.log([difference, [data[i], data[j]]]);
|
||||
}
|
||||
|
||||
console.log('next');
|
||||
}
|
||||
|
||||
return caught;
|
||||
}
|
||||
|
||||
function selfReferentialPointer() {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
console.log(`i: ${i}`);
|
||||
for (let j = i; j < i+2; j++) {
|
||||
console.log(`jjjjjjj: ${j % 3}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
0
src/vector_logic/numbersToPitches.js
Normal file
0
src/vector_logic/numbersToPitches.js
Normal file
Reference in New Issue
Block a user