From 36ca031cc4d86d171f2186cb89920737494e560c Mon Sep 17 00:00:00 2001 From: anthonypetersen Date: Wed, 8 Nov 2023 21:57:08 -0600 Subject: [PATCH] cube audio --- src/pages/cubes.js | 128 +++++++++++++++++++++++++++++++ src/pages/letters.js | 177 +++++++++++++++++++++++++++++++++++++++++++ src/shared/audio.js | 15 +++- src/shared/common.js | 22 +++++- 4 files changed, 337 insertions(+), 5 deletions(-) create mode 100644 src/pages/cubes.js create mode 100644 src/pages/letters.js diff --git a/src/pages/cubes.js b/src/pages/cubes.js new file mode 100644 index 0000000..85e820a --- /dev/null +++ b/src/pages/cubes.js @@ -0,0 +1,128 @@ +import { renderNotation } from './../shared/common.js'; +import constants from "../shared/constants.js"; +import { snare } from '../shared/audio.js'; +import { state } from '../shared/common.js'; + +const rollDice = (dice) => { + const roll = Math.floor(Math.random() * dice.length); + return dice[roll]; +} + +const pushBeats = (groove, dice) => { + for(let i = 0; i < dice.length; i++) { + groove.push(dice[i]); + } +} + +export const cube_driver = () => { + + snare(); + console.log("Creating new groove..."); + + let groove = { + kickSnare: [], + hiHat: [], + fill: [] + }; + + let kickSnare = rollDice(constants.KICK_SNARE_PATTERNS); + + for(let i = 0; i < 2; i++) { + pushBeats(groove.kickSnare, kickSnare.pattern); + } + + for (let i = 0; i < groove.kickSnare.length; i += 2) { + groove.kickSnare.splice(i + 1, 0, 0); + } + + let hiHat = rollDice(constants.HIHAT_PATTERNS_SIMPLE); + + for(let i = 0; i < 4; i++) { + pushBeats(groove.hiHat, hiHat.pattern); + } + + let fill1 = rollDice(constants.FILL_PATTERNS); + pushBeats(groove.fill, fill1.pattern); + + let fill2 = rollDice(constants.FILL_PATTERNS); + pushBeats(groove.fill, fill2.pattern); + + let fill3 = rollDice(constants.FILL_PATTERNS); + pushBeats(groove.fill, fill3.pattern); + + let fill4 = rollDice(constants.FILL_PATTERNS); + pushBeats(groove.fill, fill4.pattern); + + console.log(groove); + + renderNotation(groove, 'grooveResults'); +} + +export const cube_player = () => { + + let groove = [ + ...state.getState().cubeGroove[0].tickables, + ...state.getState().cubeFill[0].tickables + ]; + + + play(groove); +} + +const play = (groove) => { + + let audioCtx = state.getState().audioContext; + + const bpm = 90; + const secondsPerBeat = 60 / bpm; + const ticksPerQuarterNote = Vex.Flow.RESOLUTION / 4; // Default is 4096 ticks per quarter note + console.log(Vex.Flow.RESOLUTION); + const secondsPerTick = secondsPerBeat / ticksPerQuarterNote; + + function playBufferAtTime(buffer, time) { + const source = audioCtx.createBufferSource(); + source.buffer = buffer; + source.connect(audioCtx.destination); + source.start(time); + } + function getBufferForNoteName(noteName) { + // Add your logic to return the correct buffer based on the note name + // For example: + switch (noteName) { + case constants.KICK: + return state.getState().kickBuffer; + case constants.SNARE: + return state.getState().snareBuffer; + case constants.HIHAT: + return state.getState().hiHatBuffer; + case constants.RACK: + return state.getState().midTomBuffer; + default: + return null; // Return null or undefined if there's no buffer for the note + } + } + + let currentTime = audioCtx.currentTime; + console.log(currentTime); + + + groove.forEach((note) => { + // Determine the note's duration in seconds + const noteDurationSeconds = note.ticks.value() * secondsPerTick; + + // If it's a chord, note.keys will have multiple notes + note.keys.forEach((key) => { + // Retrieve the buffer for this particular note + const bufferToPlay = getBufferForNoteName(key); + + // If we have a buffer to play, schedule it + if (bufferToPlay) { + playBufferAtTime(bufferToPlay, currentTime); + } + }); + + // Increment the current time by the note's duration + currentTime += noteDurationSeconds; + }); + +} \ No newline at end of file diff --git a/src/pages/letters.js b/src/pages/letters.js new file mode 100644 index 0000000..5e3d840 --- /dev/null +++ b/src/pages/letters.js @@ -0,0 +1,177 @@ +import constants from "../shared/constants.js"; +import { state, drawCircle, drawVerticalLines } from '../shared/common.js'; +import { kick, snare, ghost } from '../shared/audio.js'; + +let kickCircles; +let snareCircles; +let ghostCircles; + +let groove; + +export const letter_driver = () => { + let groove = generate_combo(); + state.updateState({groove: groove}); + visualize_groove(groove); +} + +export const groove_driver = () => { + + const beats_element = document.getElementById('bpm'); + const canvas = document.getElementById("alphabetCanvas"); + const ctx = canvas.getContext("2d"); + + + const bpm = beats_element && beats_element.value; + const beatDuration = 60000 / bpm; + const quarterBeatDuration = beatDuration / 4; + + const delay = 500; + + setTimeout(() => { + + state.getState().groove.forEach((value, index) => { + setTimeout(() => { + + const { ghostCircles, kickCircles, snareCircles } = state.getState(); + // Play the bass drum on every 4th beat + if (index % 4 === 0) { + kickCircles[index / 4].setColor("red"); + kickCircles[index / 4].draw(ctx); + kick(); + } + + if (value === 1) { + snare(); + snareCircles[index].setColor("red"); + snareCircles[index].draw(ctx); + } else if (value === 0) { + ghostCircles[index].setColor("red"); + ghostCircles[index].draw(ctx); + ghost(); + } + }, index * quarterBeatDuration); + }); + }, delay); +} + +const visualize_groove = (groove) => { + + snareCircles = []; + ghostCircles = []; + kickCircles = []; + + const canvas = document.getElementById("alphabetCanvas"); + const ctx = canvas.getContext("2d"); + ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear previous drawings + + const circleRadius = 15; // Adjust as needed + const spacing = canvas.width / (groove.length + 1); + const rowHeight = canvas.height / 4; // Divided by 4 to center 3 rows within the canvas height + + groove.forEach((value, index) => { + let yPos; + + // Top row for groove = 1 + if (value === 1) { + yPos = rowHeight; + let circle = drawCircle(ctx, index * spacing + spacing, yPos, circleRadius); + snareCircles.push(circle); + } + else { + snareCircles.push(null); + } + + // Middle row for groove = 0 + if (value === 0) { + yPos = 2 * rowHeight; + let circle = drawCircle(ctx, index * spacing + spacing, yPos, circleRadius); + ghostCircles.push(circle); + } + else { + ghostCircles.push(null); + } + + if(index % 4 === 0) { + yPos = 3 * rowHeight; + let circle = drawCircle(ctx, index * spacing + spacing, yPos, circleRadius); + kickCircles.push(circle); + } + }); + + drawVerticalLines(ctx, canvas); + + state.updateState({ + ghostCircles: ghostCircles, + kickCircles: kickCircles, + snareCircles: snareCircles + }); +} + +const generate_combo = () => { + + let flag = true; + let results; + let combo; + + while(flag) { + flag = false; + + results = ""; + combo = new Array(); + groove = new Array(); + + for(let i = 0; i < 4; i++) { + combo.push(constants.LETTERS[Math.floor(Math.random() * constants.LETTERS.length)]); + results += combo[i].letter; + } + + for(let index of combo) { + console.log(index); + + for(let num of index.beats) { + groove.push(num); + } + } + + if(groove[0] == 1) { + console.log(groove); + } + else { + flag = true; + } + + let streak = 0; + let longest = 0; + + for(let i = 0; i < groove.length; i++) { + if(groove[i] === 1) streak++; + else streak = 0; + + if(streak > longest) longest = streak; + } + + const combo_element = document.getElementById('maxCombo'); + const maximumCombo = combo_element && !combo_element.disabled && combo_element.value; + + + if(maximumCombo && longest > maximumCombo) { + flag = true; + } + + const hit_element = document.getElementById('minHits'); + const minimumHits = hit_element && !hit_element.disabled && hit_element.value; + + const actualHits = groove.reduce((accumulator, currentValue) => accumulator + currentValue, 0); + + if(minimumHits && actualHits < minimumHits) { + flag = true; + } + + + } + + console.log("FINAL GROOVE"); + console.log(groove); + + return groove; +} \ No newline at end of file diff --git a/src/shared/audio.js b/src/shared/audio.js index 904b1b4..6f0308e 100644 --- a/src/shared/audio.js +++ b/src/shared/audio.js @@ -1,12 +1,15 @@ import { state } from './common.js'; -let kickBuffer, snareBuffer, ghostBuffer; +let kickBuffer, snareBuffer, ghostBuffer, hiHatBuffer, midTomBuffer; export const audio_driver = () => { Promise.all([ loadAudioFile('audio/kick.wav').then(buffer => { kickBuffer = buffer; }), loadAudioFile('audio/snare.wav').then(buffer => { snareBuffer = buffer; }), - loadAudioFile('audio/ghost.wav').then(buffer => { ghostBuffer = buffer; }) + loadAudioFile('audio/ghost.wav').then(buffer => { ghostBuffer = buffer; }), + loadAudioFile('audio/hihat.wav').then(buffer => { hiHatBuffer = buffer; }), + loadAudioFile('audio/midtom.wav').then(buffer => { midTomBuffer = buffer; }) ]).then(() => { + state.updateState({ kickBuffer, snareBuffer, ghostBuffer, hiHatBuffer, midTomBuffer }); console.log('All audio files preloaded'); }); } @@ -39,4 +42,12 @@ export const snare = () => { export const ghost = () => { playBuffer(ghostBuffer); +} + +export const hiHat = () => { + playBuffer(hiHatBuffer); +} + +export const midTom = () => { + playBuffer(midTomBuffer); } \ No newline at end of file diff --git a/src/shared/common.js b/src/shared/common.js index c339260..6fca329 100644 --- a/src/shared/common.js +++ b/src/shared/common.js @@ -60,7 +60,11 @@ export const renderNotation = (notation, element) => { let secondNotes = translation[1]; let thirdNotes = translation[2]; let fourthNotes = translation[3]; + + let notes = [...firstNotes, ...secondNotes, ...thirdNotes, ...fourthNotes]; + + // generate beams let beam1; let beam2; let beam3; @@ -81,9 +85,6 @@ export const renderNotation = (notation, element) => { if (fourthNotes.filter(note => !note.isRest()).length > 1) { beam4 = new Beam(fourthNotes.filter(note => !note.isRest())); } - - - let notes = [...firstNotes, ...secondNotes, ...thirdNotes, ...fourthNotes]; console.log(notation.hiHat); console.log(notation.kickSnare); @@ -96,6 +97,7 @@ export const renderNotation = (notation, element) => { }).addTickables(notes) ]; + state.updateState({ cubeGroove: voices }); // Format and justify the notes to 400 pixels. new Formatter().joinVoices(voices).format(voices, 330); @@ -111,6 +113,11 @@ export const renderNotation = (notation, element) => { if(beam4) beam4.setContext(context).draw(); + + + + + const verticalSpace = 100; const secondStaveYPosition = stave.getYForLine(5) + verticalSpace; @@ -118,6 +125,7 @@ export const renderNotation = (notation, element) => { secondStave.addClef('percussion').addTimeSignature('4/4'); secondStave.setContext(context).draw(); + console.log(notation.fill); // Transcribe the notation for the second measure @@ -166,6 +174,8 @@ export const renderNotation = (notation, element) => { beat_value: 4, }).addTickables(secondMeasureNotes); + state.updateState({ cubeFill: [secondVoice] }); + // You might need to create beams for the second measure too, similar to how you did for the first measure // Format and justify the notes for the second voice to the second stave's width @@ -179,6 +189,7 @@ export const renderNotation = (notation, element) => { if(fillBeam3) fillBeam3.setContext(context).draw(); if(fillBeam4) fillBeam4.setContext(context).draw(); + console.log(); } export const showGroove = (groove) => { @@ -457,4 +468,9 @@ export const transcribe = (notation, type) => { } return translation; +} + +export const extractNotation = (staveNotes) => { + + } \ No newline at end of file