diff --git a/app.ts b/app.ts index 4cd4655..9aba773 100644 --- a/app.ts +++ b/app.ts @@ -17,8 +17,9 @@ export const audioTest = () => { } // allows a chord to be generated with input from another function -export const soundChord = (pitches: string[]) => { - 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"); @@ -28,7 +29,6 @@ export const soundChord = (pitches: string[]) => { // set up transport const transportStart = document.getElementById('transport-start'); -// @ts-expect-error const loop = new Tone.Loop((time) => { audioTest(); }, "8n").start(0); diff --git a/index.html b/index.html index 931ca61..09e22de 100644 --- a/index.html +++ b/index.html @@ -119,13 +119,10 @@ - - - - - - - - + + + + + \ No newline at end of file diff --git a/src/audioUtil.js b/src/audioUtil.js new file mode 100644 index 0000000..39c74c4 --- /dev/null +++ b/src/audioUtil.js @@ -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; +} \ No newline at end of file diff --git a/src/harmonyUtil.js b/src/harmonyUtil.js new file mode 100644 index 0000000..696320b --- /dev/null +++ b/src/harmonyUtil.js @@ -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; +} diff --git a/src/harmonyUtil.ts b/src/harmonyUtil.ts deleted file mode 100644 index 1642599..0000000 --- a/src/harmonyUtil.ts +++ /dev/null @@ -1,70 +0,0 @@ -// pitchset definitions, grouped into matrix before export -const sopranoTones = ["B5", "A5", "G5", "F#5", "F5", "E5", "D5", "C5", "B4", "Bb4", "A4", "G4", "F#4", "F4", "E4"]; -const altoTones = ["E5", "D5", "C5", "B4", "Bb4", "A4", "G4", "F#4", "F4", "E4", "D4", "C4", "B3", "Bb3", "A3", "G3"]; -const tenorTones = ["G4", "F#4", "F4", "E4", "D4", "C4", "B3", "Bb3", "A3", "G3", "F3", "E3", "D3", "C3"]; -const bassTones = ["C2", "D2", "E2", "F2", "G2", "A2", "Bb2", "B2", "C3", "D3", "E3", "F3", "G3"]; - -export const pitchsets: string[][] = [sopranoTones, altoTones, tenorTones, bassTones]; - -// mapping of musical pitches, refer to this by index, maps onto base 12 -export const musicalPitches = ['A', "Bb", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"]; - -// interval definitions: -export interface IntervalDef { - number: string -} - -export const IntervalDefNames = { - 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 = (pitches: number[], interval: number) => { - let transposed = []; - pitches.forEach(pitch => transposed.push((pitch + interval) % 12)); - return transposed; -} - -// quality-of-life tool for debugging -export const labelIntervals = (vector: number[]): [number, IntervalDef][] => { - let result: [number, IntervalDef][] = []; - - for (let x of vector) { - result.push([x, IntervalDefNames[x]]); - } - - return result; -} - -// iterates through each voice's pitchset, and selects a random pitch from each -export const getRandomPitches = (): string[] => { - let pitches: string[]; - for (let voice of pitchsets) { - // will store a random index - let index: number; - - const regex = /[0-9]/g; - - // repeat this iteration until it returns a number not already included in the list - do { - if (!pitches) pitches = []; - - index = Math.floor(Math.random() * 100) % voice.length; - - console.log(`${voice[0]}: ${index}`); - } while (pitches.includes(voice[index])); - - // if pitches is not already initialized to an empty array, do so; otherwise, push the received value - pitches ? pitches.push(voice[index]) : pitches = [voice[index]]; - console.log(voice[index]); - } - - return pitches; -} diff --git a/src/inputHandling.ts b/src/inputHandling.js similarity index 94% rename from src/inputHandling.ts rename to src/inputHandling.js index 0aa4e7c..487879a 100644 --- a/src/inputHandling.ts +++ b/src/inputHandling.js @@ -1,5 +1,9 @@ import { audioTest } from '../app.js'; +<<<<<<< HEAD:src/inputHandling.ts import { evaluatedChord, fullRandomChord } from './audioUtils.js'; +======= +import { fullRandomChord, evaluatedChord } from './audioUtil.js'; +>>>>>>> backtrack:src/inputHandling.js // slider variables referring to DOM export const sopranoVol = document.getElementById('soprano-vol'); diff --git a/src/styleUtils.js b/src/styleUtils.js new file mode 100644 index 0000000..7f47476 --- /dev/null +++ b/src/styleUtils.js @@ -0,0 +1,41 @@ +export const startButton = document.getElementById("start-tone"); +export const synthButton = document.getElementById("synth-button"); + +export const showStart = document.getElementsByClassName('show-on-start') as HTMLCollectionOf; +export const hideStart = document.getElementsByClassName('hide-on-start') as HTMLCollectionOf; + +export const showMore = document.getElementById('info-button'); +export const moreInfo = document.getElementsByClassName('more-info') as HTMLCollectionOf; + +export let appReady = false; + +showMore.onclick = () => { + if (showMore.innerHTML === 'Show more info...') { + for (let element of moreInfo) { + element.style.display = 'block'; + document.querySelector('#info-button').innerHTML = "Hide info"; + } + } else if (showMore.innerHTML === 'Hide info') { + for (let element of moreInfo) { + element.style.display = 'none'; + document.querySelector('#info-button').innerHTML = "Show more info..."; + } + } +} + +startButton.onclick = async () => { + // @ts-expect-error - namespace again, failed import from Tone + await Tone.start() + .then(() => { + appReady = true; + synthButton.style.display = "block"; + startButton.style.display = "none"; + + for (let element of showStart) { + element.style.display = "flex"; + } + for (let element of hideStart) { + element.style.display = "none"; + } + }); +} diff --git a/src/vector_logic/evaluateVector.js b/src/vector_logic/evaluateVector.js new file mode 100644 index 0000000..db32ff6 --- /dev/null +++ b/src/vector_logic/evaluateVector.js @@ -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); + }; +} \ No newline at end of file diff --git a/src/vector_logic/extractPitchName.js b/src/vector_logic/extractPitchName.js new file mode 100644 index 0000000..016d63a --- /dev/null +++ b/src/vector_logic/extractPitchName.js @@ -0,0 +1,2 @@ +const regex = /[A-Gb#]/g; +export const extractPitchName = (tonePitchName) => tonePitchName.match(regex).join(''); diff --git a/src/vector_logic/extractPitchset.js b/src/vector_logic/extractPitchset.js new file mode 100644 index 0000000..62c9b1e --- /dev/null +++ b/src/vector_logic/extractPitchset.js @@ -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; +} \ No newline at end of file diff --git a/src/vector_logic/findVector.js b/src/vector_logic/findVector.js new file mode 100644 index 0000000..2457b00 --- /dev/null +++ b/src/vector_logic/findVector.js @@ -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; +} diff --git a/src/vector_logic/getProceduralPitches.js b/src/vector_logic/getProceduralPitches.js new file mode 100644 index 0000000..bd9c5e6 --- /dev/null +++ b/src/vector_logic/getProceduralPitches.js @@ -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; +} diff --git a/src/vector_logic/getRandomPitches.js b/src/vector_logic/getRandomPitches.js new file mode 100644 index 0000000..fb31de6 --- /dev/null +++ b/src/vector_logic/getRandomPitches.js @@ -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; +} diff --git a/src/vector_logic/helper.js b/src/vector_logic/helper.js new file mode 100644 index 0000000..77fd2d9 --- /dev/null +++ b/src/vector_logic/helper.js @@ -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}`); + } + } +} diff --git a/src/vector_logic/numbersToPitches.js b/src/vector_logic/numbersToPitches.js new file mode 100644 index 0000000..e69de29