From 2facb3826b26d1c29ec09d9d0810bf8d1f64e561 Mon Sep 17 00:00:00 2001 From: Mikayla Dobson <93477693+innocuous-symmetry@users.noreply.github.com> Date: Mon, 9 May 2022 19:46:57 -0500 Subject: [PATCH] moving to typescript --- .gitignore | 3 +- app.js | 2 +- built/app.js | 54 +++++++++++++++++++++++++++ {js => built/src}/harmonyUtil.js | 29 +++++---------- built/src/inputHandling.js | 52 ++++++++++++++++++++++++++ built/src/styleUtils.js | 44 ++++++++++++++++++++++ built/src/toneGeneration.js | 28 ++++++++++++++ index.html | 6 +-- package-lock.json | 20 +++++++++- package.json | 6 ++- src/harmonyUtil.ts | 64 ++++++++++++++++++++++++++++++++ {js => src}/inputHandling.js | 0 {js => src}/styleUtils.js | 0 {js => src}/toneGeneration.js | 0 tsconfig.json | 8 ++++ 15 files changed, 288 insertions(+), 28 deletions(-) create mode 100644 built/app.js rename {js => built/src}/harmonyUtil.js (82%) create mode 100644 built/src/inputHandling.js create mode 100644 built/src/styleUtils.js create mode 100644 built/src/toneGeneration.js create mode 100644 src/harmonyUtil.ts rename {js => src}/inputHandling.js (100%) rename {js => src}/styleUtils.js (100%) rename {js => src}/toneGeneration.js (100%) create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 30bc162..08f01db 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/node_modules \ No newline at end of file +/node_modules +.gitignore \ No newline at end of file diff --git a/app.js b/app.js index 75cf0c8..cefddb8 100644 --- a/app.js +++ b/app.js @@ -1,7 +1,7 @@ import { sopranoTones, altoTones, tenorTones, bassTones, extractPitchset, -} from "./js/toneGeneration.js"; +} from "./src/toneGeneration.js"; const pitchsets = [sopranoTones, altoTones, tenorTones, bassTones]; diff --git a/built/app.js b/built/app.js new file mode 100644 index 0000000..d03acf3 --- /dev/null +++ b/built/app.js @@ -0,0 +1,54 @@ +import { sopranoTones, altoTones, tenorTones, bassTones, extractPitchset, } from "./src/toneGeneration.js"; +const pitchsets = [sopranoTones, altoTones, tenorTones, bassTones]; +// initialize four synth voices +const soprano = new Tone.Synth().toDestination(); +const alto = new Tone.Synth().toDestination(); +const tenor = new Tone.Synth().toDestination(); +const bass = new Tone.Synth().toDestination(); +// test function for audio is armed +export const audioTest = () => { + soprano.triggerAttackRelease("C5", "16n"); + alto.triggerAttackRelease("F4", "16n"); + tenor.triggerAttackRelease("E4", "16n"); + bass.triggerAttackRelease("G3", "16n"); +}; +// allows a chord to be generated with input from another function +export const soundChord = (pitches) => { + const [s, a, t, b] = pitches; + 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); +loop.probability = 0.8; +transportStart.onclick = () => { + Tone.Transport.start(); +}; diff --git a/js/harmonyUtil.js b/built/src/harmonyUtil.js similarity index 82% rename from js/harmonyUtil.js rename to built/src/harmonyUtil.js index f433593..71944c3 100644 --- a/js/harmonyUtil.js +++ b/built/src/harmonyUtil.js @@ -1,5 +1,4 @@ import { extractPitchset } from "./toneGeneration.js"; - // interval definitions: const intervals = { 0: "unison", @@ -10,55 +9,45 @@ const intervals = { 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); + 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; - + 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 + 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); + 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]); +let complexVector = findVector([0, 3, 4, 7, 8, 11]); console.log(complexVector); - -let splitThird = findVector([0,3,4,7]); +let splitThird = findVector([0, 3, 4, 7]); console.log(splitThird); diff --git a/built/src/inputHandling.js b/built/src/inputHandling.js new file mode 100644 index 0000000..7841d5a --- /dev/null +++ b/built/src/inputHandling.js @@ -0,0 +1,52 @@ +import { audioTest, fullRandomChord } from '../app.js'; +// slider variables referring to DOM +export const sopranoVol = document.getElementById('soprano-vol'); +const sopranoVolTarget = document.getElementById('soprano-vol-target'); +export const sopLFO = document.getElementById('sop-lfo'); +const sopLFOTarget = document.getElementById('sop-lfo-target'); +export const altoVol = document.getElementById('alto-vol'); +const altoVolTarget = document.getElementById('alto-vol-target'); +export const altoLFO = document.getElementById('alto-lfo'); +const altoLFOTarget = document.getElementById('alto-lfo-target'); +export const tenVol = document.getElementById('ten-vol'); +const tenVolTarget = document.getElementById('ten-vol-target'); +export const tenLFO = document.getElementById('ten-lfo'); +const tenLFOTarget = document.getElementById('ten-lfo-target'); +export const bassVol = document.getElementById('bass-vol'); +const bassVolTarget = document.getElementById('bass-vol-target'); +export const bassLFO = document.getElementById('bass-lfo'); +const bassLFOTarget = document.getElementById('bass-lfo-target'); +// logic for displaying values on HTML labels +// S +sopranoVol.oninput = (e) => { + sopranoVolTarget.innerHTML = e.target.value; +}; +sopLFO.oninput = (e) => { + sopLFOTarget.innerHTML = ` ${e.target.value} Hz.`; +}; +// A +altoVol.oninput = (e) => { + altoVolTarget.innerHTML = e.target.value; +}; +altoLFO.oninput = (e) => { + altoLFOTarget.innerHTML = ` ${e.target.value} Hz.`; +}; +// T +tenVol.oninput = (e) => { + tenVolTarget.innerHTML = e.target.value; +}; +tenLFO.oninput = (e) => { + tenLFOTarget.innerHTML = ` ${e.target.value} Hz.`; +}; +// B +bassVol.oninput = (e) => { + bassVolTarget.innerHTML = e.target.value; +}; +bassLFO.oninput = (e) => { + bassLFOTarget.innerHTML = ` ${e.target.value.toString()} Hz.`; +}; +// audio-adjacent input handling +const synthButton = document.getElementById('synth-button'); +synthButton.onclick = audioTest; +const randChord = document.getElementById('rand-chord'); +randChord.onclick = fullRandomChord; diff --git a/built/src/styleUtils.js b/built/src/styleUtils.js new file mode 100644 index 0000000..06f598b --- /dev/null +++ b/built/src/styleUtils.js @@ -0,0 +1,44 @@ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +export const startButton = document.getElementById("start-tone"); +export const synthButton = document.getElementById("synth-button"); +export const showStart = document.getElementsByClassName('show-on-start'); +export const hideStart = document.getElementsByClassName('hide-on-start'); +export const showMore = document.getElementById('info-button'); +export const moreInfo = document.getElementsByClassName('more-info'); +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 = () => __awaiter(void 0, void 0, void 0, function* () { + yield 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/built/src/toneGeneration.js b/built/src/toneGeneration.js new file mode 100644 index 0000000..fbc809e --- /dev/null +++ b/built/src/toneGeneration.js @@ -0,0 +1,28 @@ +// 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 diff --git a/index.html b/index.html index bb9cd11..d0f15eb 100644 --- a/index.html +++ b/index.html @@ -118,9 +118,9 @@ - - - + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index aee9800..9ea39a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "tone": "^14.7.77" + "tone": "^14.7.77", + "typescript": "^4.6.4" } }, "node_modules/@babel/runtime": { @@ -63,6 +64,18 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/typescript": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", + "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } } }, "dependencies": { @@ -111,6 +124,11 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "typescript": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", + "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==" } } } diff --git a/package.json b/package.json index b348265..8609a1d 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,14 @@ "description": "", "main": "app.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "build": "tsc -p ." }, "keywords": [], "author": "", "license": "ISC", "dependencies": { - "tone": "^14.7.77" + "tone": "^14.7.77", + "typescript": "^4.6.4" } } diff --git a/src/harmonyUtil.ts b/src/harmonyUtil.ts new file mode 100644 index 0000000..144f998 --- /dev/null +++ b/src/harmonyUtil.ts @@ -0,0 +1,64 @@ +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 = (pitches: number[], interval: number) => { + let transposed = []; + pitches.forEach(pitch => transposed.push((pitch + interval) % 12)); + return transposed; +} + +const findVector = (pitches: number[]) => { + 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: number[] = []; + 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: number = (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); diff --git a/js/inputHandling.js b/src/inputHandling.js similarity index 100% rename from js/inputHandling.js rename to src/inputHandling.js diff --git a/js/styleUtils.js b/src/styleUtils.js similarity index 100% rename from js/styleUtils.js rename to src/styleUtils.js diff --git a/js/toneGeneration.js b/src/toneGeneration.js similarity index 100% rename from js/toneGeneration.js rename to src/toneGeneration.js diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e583438 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "outDir": "./built", + "allowJs": true, + "target": "es2016" + }, + "include": ["./src/**/*"] +} \ No newline at end of file