program can randomly generate chords, removing any duplicate members

This commit is contained in:
Mikayla Dobson
2022-05-09 16:14:39 -05:00
parent 8ed66665fa
commit bb62ec75d4
8 changed files with 363 additions and 168 deletions

123
app.css
View File

@@ -2,6 +2,32 @@
* {
margin: 0;
font-family: sans-serif;
font-weight: lighter;
}
html {
color: white;
background-color: rgb(11, 8, 20);
}
button {
border: 1px solid white;
color: inherit;
background-color: inherit;
border-radius: 25rem;
padding: 0.2rem 0.7rem;
}
button:active {
background-color: darkgreen;
}
#start-tone {
width: 12vw;
height: 5vh;
position: absolute;
left: 45vw;
top: 50%;
}
/* javascript utilities */
@@ -9,36 +35,115 @@
display: none;
}
/* document styles */
/* header styles */
header {
display: block;
position: fixed;
position: sticky;
background-color: black;
border-bottom: 1px solid white;
max-height: 25vh;
width: 100%;
text-align: center;
min-height: 12rem;
top: 0;
padding: 2rem;
top: 0;
z-index: 9;
}
header h2 {
margin: 1rem 1rem 1.5rem;
}
header button {
margin-top: 0.75rem;
}
header p {
padding: 0.4rem;
}
#navbar-tools {
display: flex;
}
#info-button {
position: absolute;
text-align: center;
top: 1rem;
left: 1.5rem;
}
/* main section style */
main {
display: flex;
flex-direction: column;
width: 100%;
margin-top: 4rem;
align-items: center;
position: relative;
top: 15rem;
z-index: 8;
}
.audio-controls {
display: flex;
width: 80%;
/* styling for animated visuals */
.visuals {
display: inline-flex;
flex-direction: row;
justify-content: space-between;
height: 18rem;
}
.voice {
display: inline-block;
background-color: rgb(5, 5, 75);
width: 20vw;
height: 100%;
margin: 0 1rem;
}
#visuals-border {
display: block;
width: 85vw;
height: 1rem;
background-color: white;
margin: 3rem 0;
border-radius: 15rem;
}
/* styling for synth control panel */
.control-row {
display: inline-flex;
flex-direction: row;
align-items: center;
margin: 1rem 0;
}
.button-row button {
margin: 0 0.4rem;
}
.controls {
display: inline-flex;
flex-direction: column;
border: 1px solid black;
align-items: center;
justify-content: center;
border: 1px solid white;
width: 18vw;
height: 25vh;
margin: 1rem;
}
.controls h3 {
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 0.2em;
}
.controls label {
display: inline-flex;
flex-direction: row;
}
.controls label, input {
margin: 0.2rem 0;
}
#synth-button {

70
app.js Normal file
View File

@@ -0,0 +1,70 @@
import { sopranoTones, altoTones, tenorTones, bassTones, evaluateHarmony } 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();
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);
evaluateHarmony(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();
}

View File

@@ -6,92 +6,121 @@
<body>
<header>
<h1>Procedural Drone, No. 1</h1>
<h1>Procedural Drones, No. 1</h1>
<h2>Design and engineering by Mikayla Dobson</h2>
<button class="show-on-start" id="info-button">Show more info...</button>
<div class="show-on-start" id="navbar-tools">
<button id="info-button">Show more info...</button>
<!-- <button id="how-to-use">How to use</button> -->
</div>
<div class="hide-on-start more-info">
<p>Musical widget designed using Tone.js and the Web Audio API.</p>
<p>Music-focused widget designed using Tone.js and the Web Audio API.</p>
<p>Sliders control output parameters of four independent oscillators modulated by four independent low frequency oscillators (LFOs).</p>
<p>JavaScript passes your inputs to the browser and procedurally constructs harmonies based on developer-defined logic based on tenets of music theory.</p>
</div>
<button class-name="destroy-on-start" id="start-tone">Click here to begin.</button>
</header>
<main>
<div class="show-on-start">
<div>
<button id="synth-button">Test Audio</button>
<button id="transport-start">Start Transport</button>
<button id="start-tone">Click here to begin.</button>
<main class="show-on-start">
<div class="visuals">
<div class="voice" id="soprano"></div>
<div class="voice" id="alto"></div>
<div class="voice" id="tenor"></div>
<div class="voice" id="bass"></div>
</div>
<div id="visuals-border"></div>
<div class="control-row button-row">
<button id="synth-button">Test Audio</button>
<button id="transport-start">Start Transport</button>
</div>
<form class="audio-controls control-row">
<!-- soprano -->
<div class="controls">
<h3>Soprano</h3>
<label for="soprano-vol">
<p>Soprano Volume:&nbsp;</p>
<p id="soprano-vol-target">-</p>
</label>
<input id="soprano-vol" type="range" min="5" max="20">
<label for="sop-lfo">
<p>Soprano LFO:&nbsp;</p>
<p id="sop-lfo-target">-</p>
</label>
<input id="sop-lfo" type="range" min="1" max="20" step="0.2">
</div>
<div class="visuals">
<div class="voice" id="soprano"></div>
<div class="voice" id="alto"></div>
<div class="voice" id="tenor"></div>
<div class="voice" id="bass"></div>
<!-- alto -->
<div class="controls">
<h3>Alto</h3>
<label for="alto-vol">
<p>Alto Volume:&nbsp;</p>
<p id="alto-vol-target">-</p>
</label>
<input id="alto-vol" type="range" min="5" max="20">
<label for="alto-lfo">
<p>Alto LFO:&nbsp;</p>
<p id="alto-lfo-target">-</p>
</label>
<input id="alto-lfo" type="range" min="1" max="20" step="0.2">
</div>
<form class="audio-controls">
<div class="controls">
<h3>Soprano</h3>
<label for="soprano-vol">
<p>Soprano Volume:</p>
<p id="soprano-vol-target">-</p>
</label>
<input id="soprano-vol" type="range" min="5" max="20">
<label for="sop-lfo">
<p>Soprano LFO (Hz.)</p>
<p id="sop-lfo-target">-</p>
</label>
<input id="sop-lfo" type="range" min="1" max="20" step="0.2">
</div>
<div class="controls">
<h3>Alto</h3>
<label for="alto-vol">
<p>Alto Volume:</p>
<p id="alto-vol-target">-</p>
</label>
<input id="alto-vol" type="range" min="5" max="20">
<label for="alto-lfo">
<p>Alto LFO (Hz).</p>
<p id="alto-lfo-target">-</p>
</label>
<input id="alto-lfo" type="range" min="1" max="20" step="0.2">
</div>
<div class="controls">
<h3>Tenor</h3>
<label for="ten-vol">
<p>Tenor Volume:</p>
<p id="ten-vol-target">-</p>
</label>
<input id="ten-vol" type="range" min="5" max="20">
<label for="ten-lfo">
<p>Tenor LFO (Hz.)</p>
<p id="ten-lfo-target">-</p>
</label>
<input id="ten-lfo" type="range" min="1" max="20" step="0.2">
</div>
<div class="controls">
<h3>Bass</h3>
<label for="bass-vol">
<p>Bass Volume:</p>
<p id="bass-vol-target">-</p>
</label>
<input id="bass-vol" type="range" min="5" max="20">
<label for="bass-lfo">
<p>Bass LFO (Hz.)/p>
<p id="bass-lfo-target">-</p>
</label>
<input id="bass-lfo" type="range" min="1" max="20" step="0.2">
</div>
</form>
<!-- tenor -->
<div class="controls">
<h3>Tenor</h3>
<label for="ten-vol">
<p>Tenor Volume:&nbsp;</p>
<p id="ten-vol-target">-</p>
</label>
<input id="ten-vol" type="range" min="5" max="20">
<label for="ten-lfo">
<p>Tenor LFO:&nbsp;</p>
<p id="ten-lfo-target">-</p>
</label>
<input id="ten-lfo" type="range" min="1" max="20" step="0.2">
</div>
<!-- bass -->
<div class="controls">
<h3>Bass</h3>
<label for="bass-vol">
<p>Bass Volume:&nbsp;</p>
<p id="bass-vol-target">-</p>
</label>
<input id="bass-vol" type="range" min="5" max="20">
<label for="bass-lfo">
<p>Bass LFO:&nbsp;</p>
<p id="bass-lfo-target">-</p>
</label>
<input id="bass-lfo" type="range" min="1" max="20" step="0.2">
</div>
</form>
<form class="control-row master-params">
<div class="controls">
<h3>Master</h3>
<label for="probability">Probability</label>
<input id="probability" type="range" min="0" max="1" step="0.05" placeholder="1">
</div>
</form>
<div class="control-row aux-functions button-row">
<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>
</div>
</main>
<script type="module" src="./js/app.js"></script>
<!-- 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 src="https://unpkg.com/tone/build/Tone.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>
</body>
</html>

View File

@@ -1,84 +0,0 @@
// element identifiers
const startButton = document.getElementById("start-tone");
const synthButton = document.getElementById("synth-button");
const showStart = document.getElementsByClassName('show-on-start');
const hideStart = document.getElementsByClassName('hide-on-start');
const showMore = document.getElementById('info-button');
const moreInfo = document.getElementsByClassName('more-info');
let initialized = false;
// style utilities
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...";
}
}
}
// application start, show synth play button
startButton.onclick = async () => {
await Tone.start()
.then(() => {
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";
}
});
}
// initialize four 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
const audioTest = () => {
soprano.triggerAttackRelease("C5", "8n");
alto.triggerAttackRelease("F4", "8n");
tenor.triggerAttackRelease("E4", "8n");
bass.triggerAttackRelease("G3", "8n");
}
synthButton.onclick = audioTest;
// set up transport
let clock = 0;
let slowClock = 0;
const transportStart = document.getElementById('transport-start');
Tone.Transport.schedule((time) => {
clock += 2;
slowClock += 1;
console.log(`clock: ${clock}, slow: ${slowClock}`);
if (clock % 4 === 0) {
console.log('caught');
}
if (slowClock % 4 === 0) {
console.log("slow");
audioTest();
}
}, 1);
transportStart.onclick = Tone.Transport.start();

View File

@@ -1,3 +1,5 @@
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');
@@ -26,7 +28,7 @@ sopranoVol.oninput = (e) => {
}
sopLFO.oninput = (e) => {
sopLFOTarget.innerHTML = e.target.value;
sopLFOTarget.innerHTML = ` ${e.target.value} Hz.`;
}
// A
@@ -35,7 +37,7 @@ altoVol.oninput = (e) => {
}
altoLFO.oninput = (e) => {
altoLFOTarget.innerHTML = e.target.value;
altoLFOTarget.innerHTML = ` ${e.target.value} Hz.`;
}
// T
@@ -44,7 +46,7 @@ tenVol.oninput = (e) => {
}
tenLFO.oninput = (e) => {
tenLFOTarget.innerHTML = e.target.value;
tenLFOTarget.innerHTML = ` ${e.target.value} Hz.`;
}
// B
@@ -53,5 +55,12 @@ bassVol.oninput = (e) => {
}
bassLFO.oninput = (e) => {
bassLFOTarget.innerHTML = e.target.value;
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;

40
js/styleUtils.js Normal file
View File

@@ -0,0 +1,40 @@
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 = async () => {
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";
}
});
}

View File

@@ -0,0 +1,26 @@
// 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 evaluateHarmony = (pitches) => {
let sorted = [];
for (let each of pitches) {
let matches = each.match(/(\d+)/);
if (matches) {
sorted.push([each, matches]);
}
}
sorted = sorted.sort((a,b) => a[1] < b[1]);
console.log(sorted);
}
// no tritones
// no minor 2nds or major 7ths

View File

@@ -2,7 +2,7 @@
"name": "tone",
"version": "1.0.0",
"description": "",
"main": "index.js",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},