program can randomly generate chords, removing any duplicate members
This commit is contained in:
123
app.css
123
app.css
@@ -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
70
app.js
Normal 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();
|
||||
}
|
||||
|
||||
169
index.html
169
index.html
@@ -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: </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: </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: </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: </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: </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: </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: </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: </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>
|
||||
84
js/app.js
84
js/app.js
@@ -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();
|
||||
@@ -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
40
js/styleUtils.js
Normal 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";
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user