From d5e41bf78ab69166012f78722f48451db5a93dbb Mon Sep 17 00:00:00 2001 From: Ryan Darcey <70971592+ryandarcey@users.noreply.github.com> Date: Wed, 5 Oct 2022 12:37:20 -0400 Subject: [PATCH 1/3] Update README.md --- README.md | 62 +++++-------------------------------------------------- 1 file changed, 5 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 4ac8bbff..9eebe73b 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,9 @@ -Assignment 4 - Creative Coding: Interactive Multimedia Experiences -=== +## Audio Visualizer -Due: October 4th, by 11:59 AM. +[ hosting link ] -For this assignment we will focus on client-side development using popular audio/graphics/visualization technologies. The goal of this assignment is to refine our JavaScript knowledge while exploring the multimedia capabilities of the browser. - -[WebAudio + Canvas Tutorial](https://github.com/cs4241-22a/cs4241-22a.github.io/blob/main/using_webaudio_canvas.md) -[SVG + D3 tutorial](https://github.com/cs4241-21a/cs4241-21a.github.io/blob/main/using_svg_and_d3.md) - -Baseline Requirements ---- - -Your application is required to implement the following functionalities: - -- A server created using Express. This server can be as simple as needed. -- A client-side interactive experience using at least one of the following web technologies frameworks. - - [Three.js](https://threejs.org/): A library for 3D graphics / VR experiences - - [D3.js](https://d3js.org): A library that is primarily used for interactive data visualizations - - [Canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API): A 2D raster drawing API included in all modern browsers - - [SVG](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API): A 2D vector drawing framework that enables shapes to be defined via XML. - - [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API): An API for audio synthesis, analysis, processing, and file playback. -- A user interface for interaction with your project, which must expose at least four parameters for user control. [tweakpane](https://cocopon.github.io/tweakpane/) is highly recommended for this, but you can also use regular HTML `` tags (the `range` type is useful to create sliders). You might also explore interaction by tracking mouse movement via the `window.onmousemove` event handler in tandem with the `event.clientX` and `event.clientY` properties. Consider using the [Pointer Events API](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events) to ensure that that both mouse and touch events will both be supported in your app. -- Your application should display basic documentation for the user interface when the application first loads. - -The interactive experience should possess a reasonable level of complexity. Some examples: -### Three.js -- A generative algorithm creates simple agents that move through a virtual world. Your interface controls the behavior / appearance of these agents. -- A simple 3D game... you really want this to be a simple as possible or it will be outside the scope of this assignment. -- An 3D audio visualization of a song of your choosing. User interaction should control aspects of the visualization. -### Canvas -- Implement a generative algorithm such as [Conway's Game of Life](https://bitstorm.org/gameoflife/) (or 1D cellular automata) and provide interactive controls. Note that the Game of Life has been created by 100s of people using ; we'll be checking to ensure that your implementation is not a copy of these. -- Design a 2D audio visualizer of a song of your choosing. User interaction should control visual aspects of the experience. -### Web Audio API -- Create a screen-based musical instrument using the Web Audio API. You can use projects such as [Interface.js](http://charlie-roberts.com/interface/) or [Nexus UI](https://nexus-js.github.io/ui/api/#Piano) to provide common musical interface elements, or use dat.GUI in combination with mouse/touch events (use the Pointer Events API). Your GUI should enable users to control aspects of sound synthesis. If you want to use higher-level instruments instead of the raw WebAudio API sounds, consider trying the instruments provided by [Tone.js]() or [Gibber](https://github.com/charlieroberts/gibber.audio.lib). -### D3.js -- Create visualizations using the datasets found at [Awesome JSON Datasets](https://github.com/jdorfman/Awesome-JSON-Datasets). Experiment with providing different visualizations of the same data set, and providing users interactive control over visualization parameters and/or data filtering. Alternatively, create a single visualization with using one of the more complicated techniques shown at [d3js.org](d3js.org) and provide meaningful points of interaction for users. - -Deliverables ---- - -Do the following to complete this assignment: - -1. Implement your project with the above requirements. -3. Test your project to make sure that when someone goes to your main page on Glitch/Heroku/etc., it displays correctly. -4. Ensure that your project has the proper naming scheme `a4-firstname-lastname` so we can find it. -5. Fork this repository and modify the README to the specifications below. *NOTE: If you don't use Glitch for hosting (where we can see the files) then you must include all project files that you author in your repo for this assignment*. -6. Create and submit a Pull Request to the original repo. Name the pull request using the following template: `a4-firstname-lastname`. - -Sample Readme (delete the above when you're ready to submit, and modify the below so with your links and descriptions) ---- - -## Your Web Application Title - -your hosting link e.g. http://a4-charlieroberts.glitch.me - -Include a very brief summary of your project here. Images are encouraged when needed, along with concise, high-level text. Be sure to include: +[ Include a very brief summary of your project here. Images are encouraged when needed, along with concise, high-level text. Be sure to include: - the goal of the application -- challenges you faced in realizing the application -- the instructions you present in the website should be clear enough to use the application, but if you feel any need to provide additional instructions please do so here. +This tool visualizes audio represented by sine waves using Tweakpane monitors and the WebAudio API. - challenges you faced in realizing the application +- the instructions you present in the website should be clear enough to use the application, but if you feel any need to provide additional instructions please do so here. ] From d9e60d553d6d0377ce3c1a067f931c06284603e0 Mon Sep 17 00:00:00 2001 From: Ryan Darcey <70971592+ryandarcey@users.noreply.github.com> Date: Wed, 5 Oct 2022 14:10:51 -0400 Subject: [PATCH 2/3] Add files via upload --- package.json | 16 ++ public/client.js | 448 +++++++++++++++++++++++++++++++++++++++++++ public/old_client.js | 171 +++++++++++++++++ server.js | 17 ++ views/index.html | 48 +++++ 5 files changed, 700 insertions(+) create mode 100644 package.json create mode 100644 public/client.js create mode 100644 public/old_client.js create mode 100644 server.js create mode 100644 views/index.html diff --git a/package.json b/package.json new file mode 100644 index 00000000..fd4a9af6 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "a4-ryan-darcey", + "version": "1.0.0", + "description": "", + "main": "server.js", + "dependencies": { + "express": "^4.18.1" + }, + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node server.js" + }, + "author": "", + "license": "ISC" +} diff --git a/public/client.js b/public/client.js new file mode 100644 index 00000000..601c7d57 --- /dev/null +++ b/public/client.js @@ -0,0 +1,448 @@ +// params, mainly for tweakpane +const params = { + overall_sin: 0, + overall_isPlaying: false, + overall_volume: 30, + + freq1_frequency: 220, + freq1_expSlider: 7.7813597135, + freq1_octave: 3, + freq1_note: Math.pow(2, 9/12), + freq1_isActive: true, + freq1_sine: 0, + + freq2_frequency: 440, + freq2_expSlider: 8.7813597135, + freq2_octave: 4, + freq2_note: Math.pow(2, 9/12), + freq2_isActive: true, + freq2_sine: 0, + + freq3_frequency: 880, + freq3_expSlider: 9.7813597135, + freq3_octave: 5, + freq3_note: Math.pow(2, 9/12), + freq3_isActive: true, + freq3_sine: 0, +} + +let audioContext = null +let pane = null + +// global arr of oscillators +const oscArr = [] +const gainArr = [] +const isActiveArr = [] +const isStartedArr = [] + +// roughly the note C1 (if using A4 = 440Hz) +const C1 = 55 / Math.pow(2, 9/12) + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// tweakpane element / on input functions +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +function overallIsPlayingChange(ev) { + console.log(`overall checkbox is checked: ${ev.value}`) + if(ev.value) { + // if checkbox is checked, reconnect ACTIVE oscillators + let i = 0 + oscArr.forEach((osc) => { + if(isActiveArr[i]) { + // only reconnect if oscillator is active + gainArr[i].connect(audioContext.destination) + + if(!isStartedArr[i]) { + // if not started, start oscillator + osc.start() + console.log(`oscillator ${i} started`) + isStartedArr[i] = true + } + } + + i++ + }) + } + else { + // if unchecked, disconnect oscillators + gainArr.forEach((gain) => { + gain.disconnect() + }) + } +} + +// function for frequency num input +function freqNumChange(ev, index) { + const freq = ev.value + const exp = Math.log2(freq) + + oscArr[index].frequency.value = freq + //oscArr[index].frequency.exponentialRampToValueAtTime(freq, 0.1 + audioContext.currentTime) + + switch(index) { + case 0: + params.freq1_frequency = freq + params.freq1_expSlider = exp + break; + case 1: + params.freq2_frequency = freq + params.freq2_expSlider = exp + break; + case 2: + params.freq3_frequency = freq + params.freq3_expSlider = exp + break; + } + + pane.refresh() +} + +// function for exponent slider input +function expSliderChange(ev, index) { + const exp = ev.value + const freq = Math.pow(2, exp) + + oscArr[index].frequency.value = freq + //oscArr[index].frequency.exponentialRampToValueAtTime(freq, 0.1 + audioContext.currentTime) + + + switch(index) { + case 0: + params.freq1_frequency = freq + params.freq1_expSlider = exp + break; + case 1: + params.freq2_frequency = freq + params.freq2_expSlider = exp + break; + case 2: + params.freq3_frequency = freq + params.freq3_expSlider = exp + break; + } + + pane.refresh() +} + +// frequency calculator helper for octave/note choices +function calcFreqOctaveNote(octave, note) { + const C = Math.pow(2, octave-1) * C1 + const freq = C * note + + return freq +} + +// function for octave/note input +function octaveNoteChange(ev, index, octaveOrNote) { + let octave = null + let note = null + let freq = null + + switch(index) { + case 0: + if(octaveOrNote === 'octave') { + octave = ev.value + note = params.freq1_note + } + else { + note = ev.value + octave = params.freq1_octave + } + freq = calcFreqOctaveNote(octave, note) + oscArr[index].frequency.value = freq + params.freq1_frequency = freq + break + case 1: + if(octaveOrNote === 'octave') { + octave = ev.value + note = params.freq2_note + } + else { + note = ev.value + octave = params.freq2_octave + } + freq = calcFreqOctaveNote(octave, note) + oscArr[index].frequency.value = freq + params.freq2_frequency = freq + break + case 2: + if(octaveOrNote === 'octave') { + octave = ev.value + note = params.freq3_note + } + else { + note = ev.value + octave = params.freq3_octave + } + freq = calcFreqOctaveNote(octave, note) + oscArr[index].frequency.value = freq + params.freq3_frequency = freq + break + } + + pane.refresh() +} + +// function for isActiveArr checkbox input +function isActiveCheckChange(ev, index) { + if(ev.value) { + // if checked, set active + isActiveArr[index] = true + + if(params.overall_isPlaying) { + // if checked AND OVERALL PLAYING, reconnect oscillator + gainArr[index].connect(audioContext.destination) + + if(!isStartedArr[index]) { + // if not started, start oscillator + oscArr[index].start() + console.log(`oscillator ${index} started`) + isStartedArr[index] = true + } + } + } + else { + // if unchecked, disconnect oscillators + gainArr[index].disconnect() + isActiveArr[index] = false + } +} + +// function for calculating sin waves +// --> only works properly up to ~4000Hz, +// after that it's not sampling fast enough to display properly, +// and i didn't want the lower frequencies' cycles to be too long +function calculateSinVal(index) { + time = Date.now() + + let i = 0 + let overall_sin_val = 0 + oscArr.forEach(osc => { + let freq_sin = null + + switch(i) { + case 0: + freq_sin = Math.sin(params.freq1_frequency/40000 * time) + params.freq1_sine = freq_sin + break + case 1: + freq_sin = Math.sin(params.freq2_frequency/40000 * time) + params.freq2_sine = freq_sin + break + case 2: + freq_sin = Math.sin(params.freq3_frequency/40000 * time) + params.freq3_sine = freq_sin + break + } + + // only add each freq to overall if it's active + // and if overall is playing + if(isActiveArr[i] && params.overall_isPlaying) { + overall_sin_val += freq_sin + } + + i++ + }) + + params.overall_sin = overall_sin_val / oscArr.length +} + +// everything else +window.onload = function() { + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Initialize audio audioContext, make tweakpane, etc. + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // start calculating sine waves at regular intervals + setInterval(calculateSinVal, 5) + + // make audio audioContext, oscillators, etc. + audioContext = new AudioContext() + + // make i oscillators + for(let i = 0; i < 3; i++) { + const osc = audioContext.createOscillator() + osc.frequency.value = 220 * Math.pow(2, i) + const gain = audioContext.createGain() + gain.gain.value = 0.5 + osc.connect(gain) + gain.connect(audioContext.destination) + + oscArr.push(osc) + gainArr.push(gain) + isActiveArr.push(true) + isStartedArr.push(false) + } + + // create tweakpane + pane = new Tweakpane.Pane({ + //title: 'Frequency Visualizer', + container: document.getElementById('container') + }) + + // ~~~~~~~~~~~~~~~~~~~ + // tweakpane elements + // ~~~~~~~~~~~~~~~~~~~ + + // folders + const f1 = pane.addFolder({ + title: 'Individual Frequencies', + }); + const f2 = pane.addFolder({ + title: 'Overall', + }); + + // first folder tabs, one for each oscillator + const tab = f1.addTab({ + pages: [ + {title: 'Frequency 1'}, + {title: 'Frequency 2'}, + {title: 'Frequency 3'} + ] + }); + + // overall controls/monitor + // ~~~~~~~~~~~~~~~~~~~~~~~~~~ + // monitor + f2.addMonitor(params, 'overall_sin', { + label: 'Combined sine wave', + interval: 10, + view: 'graph', + min: -1, + max: 1 + }) + + // controls whether all tones are playing + const overallCheckbox = f2.addInput(params, 'overall_isPlaying', { + label: 'All tones are playing' + }) + + // controls overall volume + const overallVolumeSlider = f2.addInput(params, 'overall_volume', { + label: 'Overall gain', + min: 0, + max: 100, + step: 5 + }) + + // first folder tabs (individual frequencies) + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // store elements in arrays + const freqSliderArr = [] + const expSliderArr = [] + const octSliderArr = [] + const noteDropArr = [] + const checkboxArr = [] + const monitorArr = [] + + // create tweakpane elements for each tab + for(let i = 0; i < 3; i++) { + // OSCILLATOR #i+1 + // ~~~~~~~~~~~~~~~~~ + // grab tab # i+1 + const tabx = tab.pages[i] + + // frequency (number) input --> slider? (to have min/max) + const freqNum = tabx.addInput(params, `freq${i+1}_frequency`, { + label: `Frequency ${i+1} (Hz)`, + // min: 20, + // max: 20000, + }) + freqSliderArr.push(freqNum) + + // exponential slider + const expSlider = tabx.addInput(params, `freq${i+1}_expSlider`, { + label: `Frequency ${i+1} exponent`, + min: 4.322, + max: 14.287, // rough range of human hearing (2^x Hz) + }) + expSliderArr.push(expSlider) + + // octave slider + const octSlider = tabx.addInput(params, `freq${i+1}_octave`, { + label: `Note octave`, + min: 1, + max: 7, + step: 1, + }) + octSliderArr.push(octSlider) + + // note name dropdown + const noteDrop = tabx.addInput(params, `freq${i+1}_note`, { + label: 'Note name', + options: { + 'C': 1.0, + 'C#/Db': Math.pow(2, 1/12), + 'D': Math.pow(2, 2/12), + 'D#/Eb': Math.pow(2, 3/12), + 'E': Math.pow(2, 4/12), + 'F': Math.pow(2, 5/12), + 'F#/Gb': Math.pow(2, 6/12), + 'G': Math.pow(2, 7/12), + 'G#/Ab': Math.pow(2, 8/12), + 'A': Math.pow(2, 9/12), + 'A#/Bb': Math.pow(2, 10/12), + 'B': Math.pow(2, 11/12), + } + }) + noteDropArr.push(noteDrop) + + // is active checkbox + const activeCheckbox = tabx.addInput(params, `freq${i+1}_isActive`, { + label: `Frequency ${i+1} is active`, + }) + checkboxArr.push(activeCheckbox) + + // monitor + const sineMonitor = tabx.addMonitor(params, `freq${i+1}_sine`, { + label: `Frequency ${i+1} sine wave`, + interval: 10, + view: 'graph', + min: -1, + max: 1, // fixed amplitude + }) + monitorArr.push(sineMonitor) + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // attach tab's element's input functions + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + freqNum.on('change', function(ev) { + freqNumChange(ev, i) + }) + expSlider.on('change', function(ev) { + expSliderChange(ev, i) + }) + octSlider.on('change', function(ev) { + octaveNoteChange(ev, i, 'octave') + }) + noteDrop.on('change', function(ev) { + octaveNoteChange(ev, i, 'note') + }) + activeCheckbox.on('change', function(ev) { + isActiveCheckChange(ev, i) + }) + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // attach overall input functions + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + overallCheckbox.on('change', function(ev) { + overallIsPlayingChange(ev) + }) + + overallVolumeSlider.on('change', function(ev) { + gainArr.forEach(gain => { + let volume = null + if(ev.value === 0) { + volume = 0.001 + } + else { + volume = ev.value/100 + } + gain.gain.exponentialRampToValueAtTime(volume, 0.5 + audioContext.currentTime) + }) + }) +} + diff --git a/public/old_client.js b/public/old_client.js new file mode 100644 index 00000000..49937517 --- /dev/null +++ b/public/old_client.js @@ -0,0 +1,171 @@ + +const values = { + frequency_exp1: 7.7813597135, + frequency_exp2: 8.7813597135, + frequency_exp3: 9.7813597135, + frequency1: 220, + frequency2: 440, + frequency3: 880, + frequency_arr: [], // holds frequencies for calculating sin wave + freq_sin_func: 0, + playing: false +} + +// gives a visual representation of the combined sin waves +function calculateSinVal() { + time = Date.now() + + if(!values.playing) { + values.freq_sin_func = 0 + return + } + + let val = 0 + values.frequency_arr.forEach((freq) => { + // scaled visual + val += Math.sin(freq/40000 * time) + }) + + values.freq_sin_func = val/values.frequency_arr.length + return +} + +// handles slider input change +function sliderChange(ev, index, osc) { + // scales output logarithmically from 20Hz (0) to 20kHz (100) + const freq = Math.pow(2, ev.value) + console.log(freq) + + osc.frequency.value = freq + values.frequency_arr[index] = freq + + switch(index) { + case 0: + values.frequency1 = freq + break + case 1: + values.frequency2 = freq + break + case 2: + values.frequency3 = freq + } +} + +window.onload = function() { + // calculate combined sin every 10ms + setInterval(calculateSinVal, 10) + + // create tweakpane + const pane = new Tweakpane.Pane({ + container: document.getElementById('container') + }) + + // make audio context, node, etc + const audioContext = new AudioContext() + const osc_arr = [] + for(let i = 0; i < 3; i++) { + // make 3 oscillators + const osc = audioContext.createOscillator() + osc.frequency.value = 220 * Math.pow(2, i) + values.frequency_arr[i] = osc.frequency.value + osc_arr.push(osc) + } + let isStarted = false + + // const values = { + // frequency_exp: 44.764, + // frequency: 440, + // freq_sin_func: 0, + // playing: false + // } + + // ~~~~~~~~~~~ + // add inputs + + const pitchSlider1 = pane.addInput( + values, 'frequency_exp1', { + label: 'Frequency 1 Exponent', + min: 4.322, + max: 14.287, + step: 0.001, + // rough range of human hearing + }) + const pitchSlider2 = pane.addInput( + values, 'frequency_exp2', { + label: 'Frequency 2 Exponent', + min: 4.322, + max: 14.287, + step: 0.001, + // rough range of human hearing + }) + const pitchSlider3 = pane.addInput( + values, 'frequency_exp3', { + label: 'Frequency 3 Exponent', + min: 4.322, + max: 14.287, + step: 0.001, + // rough range of human hearing + }) + + const playCheckbox = pane.addInput( + values, 'playing', { + label: 'Tone is Playing' + } + ) + + const freqMonitor = pane.addMonitor(values, 'frequency1', { + label: 'Frequency 1', + interval: 10, + view: 'graph', + min: 20, + max: 20000, + }) + + const freqSinMonitor = pane.addMonitor(values, 'freq_sin_func', { + interval: 10, + view: 'graph', + min: -1, + max: 1, + // fixed amplitude + }) + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~ + // on input change functions + + // pitch sliders + pitchSlider1.on('change', function(ev) { + sliderChange(ev, 0, osc_arr[0]) + }) + pitchSlider2.on('change', function(ev) { + sliderChange(ev, 1, osc_arr[1]) + }) + pitchSlider3.on('change', function(ev) { + sliderChange(ev, 2, osc_arr[2]) + }) + + // play checkbox + playCheckbox.on('change', function(ev) { + if(ev.value) { + // reconnect oscillators + osc_arr.forEach((osc) => { + osc.connect(audioContext.destination) + }) + + if(!isStarted) { + // if not started, start oscillators + osc_arr.forEach((osc) => { + console.log(osc) + osc.start() + }) + isStarted = true + } + } + else { + // if playing, disconnect oscillators + osc_arr.forEach((osc) => { + osc.disconnect() + }) + } + }) +} + diff --git a/server.js b/server.js new file mode 100644 index 00000000..971a6338 --- /dev/null +++ b/server.js @@ -0,0 +1,17 @@ +const express = require('express') + +const app = express() + +app.use( express.static('public') ) + +app.get('/', (req, res) => { + res.sendFile('./views/index.html', { root: __dirname }) +}) + +app.use((req, res) => { + res.status(404).send('

This page does not exist

') +}) + +app.listen(3000) +// pretty sure this is all i need for a server + diff --git a/views/index.html b/views/index.html new file mode 100644 index 00000000..058996ae --- /dev/null +++ b/views/index.html @@ -0,0 +1,48 @@ + + + + + + Audio Visualizer + + + + + + + + +
+

Audio Visualizer

+ +

+ This tool visualizes audio represented by sine waves using Tweakpane monitors + and the WebAudio API. +

+

+ There are three different frequency tabs which each control an oscillator. + You can change the frequency by inputting a number directly, using an + exponential slider (the frequency = 2^x Hz), or by selecting + an octave plus a note name. +

+

+ You can also control whether a given oscillator is playing, + as well as turn off/on all active oscillators. +

+

+ Note: There may be some unpleasant distortion with higher gain values, + I would probably keep it at or below 30. +

+

+ Also note: The visual sine waves are 'stretched' by a significant + amount so the visualization can work in real time, but it still does not + give an accurate representation of the wave above ~4kHz. +

+
+ +
+
+
+ + + From c5f23a32e72147dc4722f069d8758d44b3ab43a7 Mon Sep 17 00:00:00 2001 From: Ryan Darcey <70971592+ryandarcey@users.noreply.github.com> Date: Wed, 5 Oct 2022 14:21:33 -0400 Subject: [PATCH 3/3] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9eebe73b..b2fcd0b6 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ ## Audio Visualizer -[ hosting link ] +https://a4-ryan-darcey.glitch.me/ -[ Include a very brief summary of your project here. Images are encouraged when needed, along with concise, high-level text. Be sure to include: +This project is an audio visualizer that visualizes the sine waves of the tones that are playing using Tweakpane and the WebAudioAPI. It allows the user to play up to three different sine waves at once, visually displaying them each separately, as well as the resulting combined sine wave of all the tones playing. -- the goal of the application -This tool visualizes audio represented by sine waves using Tweakpane monitors and the WebAudio API. - challenges you faced in realizing the application -- the instructions you present in the website should be clear enough to use the application, but if you feel any need to provide additional instructions please do so here. ] +The user can adjust the frequency of each tone in several ways as well as control whether each is active or not, turn on/off all active tones, and adjust the volume/gain of the tones. + +The main challenge came from using the Tweakpane monitors to visualize the sine waves rather than using the WebAudioAPI itself or some other tool better suited for that.