-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add framework to deal with audio analyses
- Loading branch information
Showing
16 changed files
with
502 additions
and
91 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.choice { | ||
display: flex; | ||
justify-content: center; | ||
margin: 12px auto 12px auto; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
function changeMidiDevice() { | ||
const deviceSelector = document.getElementById('device'); | ||
const deviceSelection = deviceSelector.options[deviceSelector.selectedIndex].value; | ||
if (deviceSelector.selectedIndex > 0) | ||
window.audioAPI.connectMidiDeviceToTrack('defaultTrack', deviceSelection).then(() => { | ||
document.getElementById("status").textContent = 'Connected'; | ||
document.getElementById("status").style.color = 'black'; | ||
console.log('Connected to MIDI device!'); | ||
}); | ||
} | ||
|
||
function changeAudioInputDevice() { | ||
const deviceSelector = document.getElementById('input'); | ||
const deviceSelection = deviceSelector.options[deviceSelector.selectedIndex].value; | ||
if (deviceSelector.selectedIndex > 0) | ||
window.audioAPI.connectAudioInputDeviceToTrack('defaultTrack', deviceSelection).then(() => { | ||
document.getElementById("status").textContent = 'Connected'; | ||
document.getElementById("status").style.color = 'black'; | ||
console.log('Connected to audio input device!'); | ||
}); | ||
else | ||
window.audioAPI.disconnectAudioInputDeviceFromTrack('defaultTrack'); | ||
} | ||
|
||
function changeAudioOutputDevice() { | ||
const deviceSelector = document.getElementById('output'); | ||
const deviceSelection = deviceSelector.options[deviceSelector.selectedIndex].value; | ||
window.audioAPI.selectAudioOutputDevice(deviceSelection).then(() => { | ||
document.getElementById("status").textContent = 'Connected'; | ||
document.getElementById("status").style.color = 'black'; | ||
console.log('Connected to audio output device!'); | ||
}); | ||
} | ||
|
||
function changeInstrument() { | ||
const instrumentSelector = document.getElementById('instrument'); | ||
const instrumentSelection = instrumentSelector.options[instrumentSelector.selectedIndex].value; | ||
if (instrumentSelector.selectedIndex > 0) { | ||
window.audioAPI.start(); | ||
document.getElementById("status").textContent = 'Loading...'; | ||
document.getElementById("status").style.color = 'black'; | ||
window.audioAPI.updateInstrument('defaultTrack', instrumentSelection).then(() => { | ||
document.getElementById("status").textContent = 'Ready'; | ||
document.getElementById("status").style.color = 'black'; | ||
console.log('Instrument loading complete!'); | ||
}); | ||
} | ||
} | ||
|
||
function startRecordMidi() { | ||
document.getElementById('startMidiButton').setAttribute('disabled', 'disabled'); | ||
document.getElementById('stopMidiButton').removeAttribute('disabled'); | ||
document.getElementById('playMidiButton').setAttribute('disabled', 'disabled'); | ||
document.getElementById('exportMidiButton').setAttribute('disabled', 'disabled'); | ||
try { | ||
if (document.getElementById('midiDuration').value > 0) { | ||
window.midiClip = window.audioAPI.recordMidiClip('defaultTrack', window.audioAPI.getCurrentTime() + Number(document.getElementById('midiStartOffset').value), document.getElementById('midiDuration').value); | ||
window.midiClip.notifyWhenComplete(stopRecordMidi); | ||
} | ||
else | ||
window.midiClip = window.audioAPI.recordMidiClip('defaultTrack', window.audioAPI.getCurrentTime() + Number(document.getElementById('midiStartOffset').value)); | ||
} | ||
catch (err) { | ||
document.getElementById('startMidiButton').removeAttribute('disabled'); | ||
document.getElementById('stopMidiButton').setAttribute('disabled', 'disabled'); | ||
document.getElementById('playMidiButton').setAttribute('disabled', 'disabled'); | ||
document.getElementById('exportMidiButton').setAttribute('disabled', 'disabled'); | ||
document.getElementById("status").textContent = err.message; | ||
document.getElementById("status").style.color = 'red'; | ||
} | ||
} | ||
|
||
function stopRecordMidi() { | ||
document.getElementById('startMidiButton').removeAttribute('disabled'); | ||
document.getElementById('stopMidiButton').setAttribute('disabled', 'disabled'); | ||
document.getElementById('playMidiButton').removeAttribute('disabled'); | ||
document.getElementById('exportMidiButton').removeAttribute('disabled'); | ||
if (window.midiClip) | ||
window.midiClip.finalize(); | ||
} | ||
|
||
function playMidi() { | ||
if (window.midiClip) | ||
window.audioAPI.playClip('defaultTrack', window.midiClip, window.audioAPI.getCurrentTime()) | ||
} | ||
|
||
async function exportMidi() { | ||
const encodingTypes = window.audioAPI.getAvailableEncoders(); | ||
if (window.midiClip) { | ||
const encodedBlob = await window.midiClip.getEncodedData(encodingTypes.WAV); | ||
const link = document.createElement('a'); | ||
link.download = 'RecordedMidiClip.wav'; | ||
link.href = URL.createObjectURL(encodedBlob); | ||
link.click(); | ||
URL.revokeObjectURL(link.href); | ||
} | ||
} | ||
|
||
function startRecordAudio() { | ||
document.getElementById('startAudioButton').setAttribute('disabled', 'disabled'); | ||
document.getElementById('stopAudioButton').removeAttribute('disabled'); | ||
document.getElementById('playAudioButton').setAttribute('disabled', 'disabled'); | ||
document.getElementById('exportAudioButton').setAttribute('disabled', 'disabled'); | ||
try { | ||
if (document.getElementById('audioDuration').value > 0) { | ||
window.audioClip = window.audioAPI.recordAudioClip('defaultTrack', window.audioAPI.getCurrentTime() + Number(document.getElementById('audioStartOffset').value), document.getElementById('audioDuration').value); | ||
window.audioClip.notifyWhenComplete(stopRecordAudio); | ||
} | ||
else | ||
window.audioClip = window.audioAPI.recordAudioClip('defaultTrack', window.audioAPI.getCurrentTime() + Number(document.getElementById('audioStartOffset').value)); | ||
} | ||
catch (err) { | ||
document.getElementById('startAudioButton').removeAttribute('disabled'); | ||
document.getElementById('stopAudioButton').setAttribute('disabled', 'disabled'); | ||
document.getElementById('playAudioButton').setAttribute('disabled', 'disabled'); | ||
document.getElementById('exportAudioButton').setAttribute('disabled', 'disabled'); | ||
document.getElementById("status").textContent = err.message; | ||
document.getElementById("status").style.color = 'red'; | ||
} | ||
} | ||
|
||
function stopRecordAudio() { | ||
document.getElementById('startAudioButton').removeAttribute('disabled'); | ||
document.getElementById('stopAudioButton').setAttribute('disabled', 'disabled'); | ||
document.getElementById('playAudioButton').removeAttribute('disabled'); | ||
document.getElementById('exportAudioButton').removeAttribute('disabled'); | ||
if (window.audioClip) | ||
window.audioClip.finalize(); | ||
} | ||
|
||
function playAudio() { | ||
if (window.audioClip) | ||
window.audioAPI.playClip('defaultTrack', window.audioClip, window.audioAPI.getCurrentTime()) | ||
} | ||
|
||
async function exportAudio() { | ||
const encodingTypes = window.audioAPI.getAvailableEncoders(); | ||
if (window.audioClip) { | ||
const encodedBlob = await window.audioClip.getEncodedData(encodingTypes.WAV); | ||
const link = document.createElement('a'); | ||
link.download = 'RecordedAudioClip.wav'; | ||
link.href = URL.createObjectURL(encodedBlob); | ||
link.click(); | ||
URL.revokeObjectURL(link.href); | ||
} | ||
} | ||
|
||
window.onload = () => { | ||
window.midiClip = window.audioClip = null; | ||
window.audioAPI = new WebAudioAPI(); | ||
window.audioAPI.createTrack('defaultTrack'); | ||
const midiDeviceSelector = document.getElementById('device'); | ||
midiDeviceSelector.add(new Option('Choose a MIDI device')); | ||
const instrumentSelector = document.getElementById('instrument'); | ||
instrumentSelector.add(new Option('Choose an instrument')); | ||
const inputSelector = document.getElementById('input'); | ||
inputSelector.add(new Option('Choose an audio input device')); | ||
const outputSelector = document.getElementById('output'); | ||
window.audioAPI.getAvailableInstruments('../instruments').then(instruments => instruments.forEach(instrument => instrumentSelector.add(new Option(instrument)))); | ||
window.audioAPI.getAvailableMidiDevices().then(devices => devices.forEach(device => midiDeviceSelector.add(new Option(device)))); | ||
window.audioAPI.getAvailableAudioInputDevices().then(devices => devices.forEach(device => inputSelector.add(new Option(device)))); | ||
window.audioAPI.getAvailableAudioOutputDevices().then(devices => devices.forEach(device => outputSelector.add(new Option(device)))); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
|
||
<head> | ||
<meta charset="utf-8"> | ||
<title>Live Audio Analysis Demo</title> | ||
<link rel="stylesheet" href="analysisdemo.css"> | ||
<script type="module" src="js/webAudioAPI.js"></script> | ||
<script src="js/analysisdemo.js"></script> | ||
</head> | ||
|
||
<body> | ||
|
||
<div class="choice"> | ||
Status: <span id="status">Not Ready</span> | ||
</div> | ||
|
||
<div class="choice"> | ||
<label for="device">MIDI Device: </label> | ||
<select name="device" id="device" onchange="changeMidiDevice()"></select> | ||
</div> | ||
|
||
<div class="choice"> | ||
<label for="instrument">Instrument: </label> | ||
<select name="instrument" id="instrument" onchange="changeInstrument()"></select> | ||
</div> | ||
|
||
<div class="choice"> | ||
<label for="input">Audio Input Device: </label> | ||
<select name="input" id="input" onchange="changeAudioInputDevice()"></select> | ||
</div> | ||
|
||
<div class="choice"> | ||
<label for="output">Audio Output Device: </label> | ||
<select name="output" id="output" onchange="changeAudioOutputDevice()"></select> | ||
</div> | ||
|
||
<p> </p> | ||
|
||
<div class="choice"> | ||
<label for="midiStartOffset">Record MIDI Clip: Start Time Offset: </label> | ||
<input type="number" name="midiStartOffset" id="midiStartOffset" min="0" max="100" value="0"> | ||
<label for="midiDuration"> Duration: </label> | ||
<select name="midiDuration" id="midiDuration"> | ||
<option value="0">Until Stopped</option> | ||
<option value="1">1 second</option> | ||
<option value="2">2 seconds</option> | ||
<option value="3">3 seconds</option> | ||
</select> | ||
| ||
<button type="button" id="startMidiButton" onclick="startRecordMidi()">Start Recording</button> | ||
<button type="button" id="stopMidiButton" onclick="stopRecordMidi()" disabled="true">Stop Recording</button> | ||
<button type="button" id="playMidiButton" onclick="playMidi()" disabled="true">Play Back</button> | ||
<button type="button" id="exportMidiButton" onclick="exportMidi()" disabled="true">Export</button> | ||
</div> | ||
|
||
<div class="choice"> | ||
<label for="audioStartOffset">Record Audio Clip: Start Time Offset: </label> | ||
<input type="number" name="audioStartOffset" id="audioStartOffset" min="0" max="100" value="0"> | ||
<label for="audioDuration"> Duration: </label> | ||
<select name="audioDuration" id="audioDuration"> | ||
<option value="0">Until Stopped</option> | ||
<option value="1">1 second</option> | ||
<option value="2">2 seconds</option> | ||
<option value="3">3 seconds</option> | ||
</select> | ||
| ||
<button type="button" id="startAudioButton" onclick="startRecordAudio()">Start Recording</button> | ||
<button type="button" id="stopAudioButton" onclick="stopRecordAudio()" disabled="true">Stop Recording</button> | ||
<button type="button" id="playAudioButton" onclick="playAudio()" disabled="true">Play Back</button> | ||
<button type="button" id="exportAudioButton" onclick="exportAudio()" disabled="true">Export</button> | ||
</div> | ||
|
||
</body> | ||
|
||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/** Class representing all base-level {@link WebAudioAPI} audio analysis functions */ | ||
export class AnalysisBase { | ||
|
||
/** | ||
* Called by a concrete analysis instance to initialize the inherited {@link AnalysisBase} data | ||
* structure. | ||
*/ | ||
constructor() { /* Empty constructor */ } | ||
|
||
/** | ||
* Performs a spectral analysis corresponding to an underlying concrete class on the passed-in | ||
* buffer containing audio frequency content. | ||
* | ||
* @param {Uint8Array} frequencyContent - {@link https://developer.mozilla.org/en-US/docs/Web/API/Uint8Array Uint8Array} containing audio frequency data | ||
* @returns {Object} Object containing the results of the specified acoustic analysis | ||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Uint8Array Uint8Array} | ||
*/ | ||
static analyze(frequencyContent) { return undefined; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { AnalysisBase } from './AnalysisBase.mjs'; | ||
|
||
/** | ||
* Class representing an acoustic "power spectrum" analysis algorithm. | ||
* | ||
* A Power Spectrum is an array in which each bin contains the power attributed to a discrete | ||
* range of frequencies within an audio signal. | ||
* | ||
* @extends AnalysisBase | ||
*/ | ||
export class PowerSpectrum extends AnalysisBase { | ||
|
||
/** | ||
* Constructs a new {@link PowerSpectrum} analysis object. | ||
*/ | ||
constructor() { | ||
super(); | ||
} | ||
|
||
/** | ||
* Performs a power spectrum analysis on the passed-in buffer containing audio | ||
* frequency content. | ||
* | ||
* @param {Uint8Array} frequencyContent - {@link https://developer.mozilla.org/en-US/docs/Web/API/Uint8Array Uint8Array} containing audio frequency data | ||
* @returns {Float32Array} Array containing the power spectrum corresponding to the specified frequency data | ||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Float32Array Float32Array} | ||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Uint8Array Uint8Array} | ||
*/ | ||
static analyze(frequencyContent) { | ||
return undefined; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { AnalysisBase } from './AnalysisBase.mjs'; | ||
|
||
/** | ||
* Class representing an acoustic "total power" analysis algorithm. | ||
* | ||
* Total power analysis determines the total cumulative spectral power present across all | ||
* frequencies within an audio signal. | ||
* | ||
* @extends AnalysisBase | ||
*/ | ||
export class TotalPower extends AnalysisBase { | ||
|
||
/** | ||
* Constructs a new {@link TotalPower} analysis object. | ||
*/ | ||
constructor() { | ||
super(); | ||
} | ||
|
||
/** | ||
* Performs a total power spectral analysis on the passed-in buffer containing audio | ||
* frequency content. | ||
* | ||
* @param {Uint8Array} frequencyContent - {@link https://developer.mozilla.org/en-US/docs/Web/API/Uint8Array Uint8Array} containing audio frequency data | ||
* @returns {number} Total power content across all frequencies in the specified frequency data | ||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Uint8Array Uint8Array} | ||
*/ | ||
static analyze(frequencyContent) { | ||
return undefined; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/** | ||
* Module containing functionality to create and utilize {@link WebAudioAPI} audio analyzers. | ||
* @module Analysis | ||
*/ | ||
|
||
import { AnalysisType } from './Constants.mjs'; | ||
import { PowerSpectrum } from '../analyses/PowerSpectrum.mjs'; | ||
import { TotalPower } from '../analyses/TotalPower.mjs'; | ||
|
||
const AnalysisClasses = { | ||
[AnalysisType.PowerSpectrum]: PowerSpectrum, | ||
[AnalysisType.TotalPower]: TotalPower | ||
}; | ||
|
||
/** | ||
* Returns a concrete analyzer implementation for the specified analysis type. The value passed | ||
* to the `analysisType` parameter must be the **numeric value** associated with a certain | ||
* {@link module:Constants.AnalysisType AnalysisType}, not a string-based key. | ||
* | ||
* @param {number} analysisType - Numeric value corresponding to the desired {@link module:Constants.AnalysisType AnalysisType} | ||
* @returns {AnalysisBase} Concrete analyzer implementation for the specified {@link module:Constants.AnalysisType AnalysisType} | ||
* @see {@link module:Constants.AnalysisType AnalysisType} | ||
* @see {@link AnalysisBase} | ||
*/ | ||
export function getAnalyzerFor(analysisType) { | ||
return new AnalysisClasses[analysisType]; | ||
} |
Oops, something went wrong.