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; margin: 0;
font-family: sans-serif; 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 */ /* javascript utilities */
@@ -9,36 +35,115 @@
display: none; display: none;
} }
/* document styles */ /* header styles */
header { header {
display: block; display: block;
position: fixed; position: sticky;
background-color: black;
border-bottom: 1px solid white;
max-height: 25vh;
width: 100%; width: 100%;
text-align: center; text-align: center;
min-height: 12rem;
top: 0;
padding: 2rem; 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 { main {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
margin-top: 4rem;
align-items: center; align-items: center;
position: relative; position: relative;
top: 15rem; z-index: 8;
} }
.audio-controls { /* styling for animated visuals */
display: flex; .visuals {
width: 80%; 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 { .controls {
display: inline-flex; display: inline-flex;
flex-direction: column; flex-direction: column;
border: 1px solid black;
align-items: center; 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 { #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> <body>
<header> <header>
<h1>Procedural Drone, No. 1</h1> <h1>Procedural Drones, No. 1</h1>
<h2>Design and engineering by Mikayla Dobson</h2> <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"> <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>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> <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> </div>
<button class-name="destroy-on-start" id="start-tone">Click here to begin.</button>
</header> </header>
<main> <button id="start-tone">Click here to begin.</button>
<div class="show-on-start">
<div> <main class="show-on-start">
<button id="synth-button">Test Audio</button> <div class="visuals">
<button id="transport-start">Start Transport</button> <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>
<div class="visuals"> <!-- alto -->
<div class="voice" id="soprano"></div> <div class="controls">
<div class="voice" id="alto"></div> <h3>Alto</h3>
<div class="voice" id="tenor"></div> <label for="alto-vol">
<div class="voice" id="bass"></div> <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> </div>
<form class="audio-controls"> <!-- tenor -->
<div class="controls"> <div class="controls">
<h3>Soprano</h3> <h3>Tenor</h3>
<label for="soprano-vol"> <label for="ten-vol">
<p>Soprano Volume:</p> <p>Tenor Volume:&nbsp;</p>
<p id="soprano-vol-target">-</p> <p id="ten-vol-target">-</p>
</label> </label>
<input id="soprano-vol" type="range" min="5" max="20"> <input id="ten-vol" type="range" min="5" max="20">
<label for="sop-lfo"> <label for="ten-lfo">
<p>Soprano LFO (Hz.)</p> <p>Tenor LFO:&nbsp;</p>
<p id="sop-lfo-target">-</p> <p id="ten-lfo-target">-</p>
</label> </label>
<input id="sop-lfo" type="range" min="1" max="20" step="0.2"> <input id="ten-lfo" type="range" min="1" max="20" step="0.2">
</div> </div>
<div class="controls">
<h3>Alto</h3> <!-- bass -->
<label for="alto-vol"> <div class="controls">
<p>Alto Volume:</p> <h3>Bass</h3>
<p id="alto-vol-target">-</p> <label for="bass-vol">
</label> <p>Bass Volume:&nbsp;</p>
<input id="alto-vol" type="range" min="5" max="20"> <p id="bass-vol-target">-</p>
<label for="alto-lfo"> </label>
<p>Alto LFO (Hz).</p> <input id="bass-vol" type="range" min="5" max="20">
<p id="alto-lfo-target">-</p> <label for="bass-lfo">
</label> <p>Bass LFO:&nbsp;</p>
<input id="alto-lfo" type="range" min="1" max="20" step="0.2"> <p id="bass-lfo-target">-</p>
</div> </label>
<div class="controls"> <input id="bass-lfo" type="range" min="1" max="20" step="0.2">
<h3>Tenor</h3> </div>
<label for="ten-vol"> </form>
<p>Tenor Volume:</p>
<p id="ten-vol-target">-</p> <form class="control-row master-params">
</label> <div class="controls">
<input id="ten-vol" type="range" min="5" max="20"> <h3>Master</h3>
<label for="ten-lfo"> <label for="probability">Probability</label>
<p>Tenor LFO (Hz.)</p> <input id="probability" type="range" min="0" max="1" step="0.05" placeholder="1">
<p id="ten-lfo-target">-</p> </div>
</label> </form>
<input id="ten-lfo" type="range" min="1" max="20" step="0.2">
</div> <div class="control-row aux-functions button-row">
<div class="controls"> <button id="mute-all">Mute all oscillators</button>
<h3>Bass</h3> <button id="reset-lfos">Reset LFO values to default</button>
<label for="bass-vol"> <button id="rand-chord">Full random chord</button>
<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>
</div> </div>
</main> </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 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> </body>
</html> </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 // slider variables referring to DOM
export const sopranoVol = document.getElementById('soprano-vol'); export const sopranoVol = document.getElementById('soprano-vol');
const sopranoVolTarget = document.getElementById('soprano-vol-target'); const sopranoVolTarget = document.getElementById('soprano-vol-target');
@@ -26,7 +28,7 @@ sopranoVol.oninput = (e) => {
} }
sopLFO.oninput = (e) => { sopLFO.oninput = (e) => {
sopLFOTarget.innerHTML = e.target.value; sopLFOTarget.innerHTML = ` ${e.target.value} Hz.`;
} }
// A // A
@@ -35,7 +37,7 @@ altoVol.oninput = (e) => {
} }
altoLFO.oninput = (e) => { altoLFO.oninput = (e) => {
altoLFOTarget.innerHTML = e.target.value; altoLFOTarget.innerHTML = ` ${e.target.value} Hz.`;
} }
// T // T
@@ -44,7 +46,7 @@ tenVol.oninput = (e) => {
} }
tenLFO.oninput = (e) => { tenLFO.oninput = (e) => {
tenLFOTarget.innerHTML = e.target.value; tenLFOTarget.innerHTML = ` ${e.target.value} Hz.`;
} }
// B // B
@@ -53,5 +55,12 @@ bassVol.oninput = (e) => {
} }
bassLFO.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", "name": "tone",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "index.js", "main": "app.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },