Skip to content

Commit

Permalink
Merge pull request #339 from TenghuiZhang/vuMeterLibrary
Browse files Browse the repository at this point in the history
VU-meter library design 2
  • Loading branch information
TenghuiZhang authored Jul 5, 2023
2 parents a3c34fc + 46eed1b commit 031cffa
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 112 deletions.
2 changes: 1 addition & 1 deletion src/_data/build_info.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":"3.1.0","revision":"600d7f6","lastUpdated":"2023-06-29","copyrightYear":2023}
{"version":"3.1.0","revision":"b55ddb9","lastUpdated":"2023-07-05","copyrightYear":2023}
57 changes: 7 additions & 50 deletions src/audio-worklet/migration/spn-recorder/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import createLinkFromAudioBuffer from './exporter.mjs';
import Waveform from '../../../library/Waveform.js';
import VUMeter from '../../../library/VUMeter.js';

// This enum states the current recording state.
const RecorderStates = {
Expand All @@ -19,10 +20,6 @@ const context = new AudioContext();

// Arbitrary buffer size, not specific for a reason.
const BUFFER_SIZE = 256;
// Make the visualization clearer to the users.
const SCALE_FACTOR = 10;
// Make the visualization of vu meter more clear to the users.
const MAX_GAIN = 1;

let recordingLength = 0;
let recordBuffer = [[], []];
Expand Down Expand Up @@ -71,6 +68,7 @@ async function initializeAudio() {
const analyserNode = new AnalyserNode(context);

const waveform = new Waveform('#recording-canvas', analyserNode, 32);
const vuMeter = new VUMeter('#vu-meter', -40, analyserNode, 32, 6);

// Prepare max recording buffer for recording.
const recordingProperties = {
Expand All @@ -81,7 +79,7 @@ async function initializeAudio() {


// Obtain samples passthrough function for visualizers.
const passSampleToVisualizers = setupVisualizers(waveform);
const passSampleToVisualizers = setupVisualizers(waveform, vuMeter);
const spNode =
setupScriptProcessor(recordingProperties, passSampleToVisualizers);

Expand Down Expand Up @@ -213,10 +211,12 @@ async function prepareClip(finalRecordBuffer) {
* Set up and handles calculations and rendering for all visualizers.
* @param {Waveform} waveform An instance of the Waveform object for
* visualization.
* @param {VUMeter} vuMeter An instance of the Waveform object for
* visualization.
* @return {function} Function to set current input samples for
* visualization.
*/
function setupVisualizers(waveform) {
function setupVisualizers(waveform, vuMeter) {
let currentSamples = [];
let firstSamplesReceived = false;

Expand All @@ -231,21 +231,8 @@ function setupVisualizers(waveform) {

function draw() {
if (currentSamples) {
// Calculate the average gain of collected samples for the
// visualization. This needs to be done once per frame.
let currentSampleGain = 0;

for (let i = 0; i < currentSamples.length; i++) {
for (let j = 0; j < currentSamples[i].length; j++) {
currentSampleGain += currentSamples[i][j];
}
}

currentSampleGain /= (currentSamples.length * currentSamples[0].length);

if (recordingState === RecorderStates.RECORDING) {
const recordGain = currentSampleGain;
drawVUMeter(recordGain);
vuMeter.draw();
waveform.draw();
}
}
Expand Down Expand Up @@ -280,33 +267,3 @@ const createFinalRecordBuffer = (recordingProperties) => {
}
return contextRecordBuffer;
};

function drawVUMeter(volume) {
var canvas = document.getElementById('vu-meter');
var ctx = canvas.getContext('2d');

ctx.clearRect(0, 0, canvas.width, canvas.height);

ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);

var meterHeight = canvas.height *
(volume / MAX_GAIN) * SCALE_FACTOR;

ctx.fillStyle = '#f00';
ctx.fillRect(0, canvas.height - meterHeight, canvas.width, meterHeight);

ctx.strokeStyle = '#fff';
ctx.lineWidth = 1;
ctx.globalAlpha = 0.3;
ctx.beginPath();
ctx.moveTo(0, canvas.height * 0.2);
ctx.lineTo(canvas.width, canvas.height * 0.2);
ctx.moveTo(0, canvas.height * 0.4);
ctx.lineTo(canvas.width, canvas.height * 0.4);
ctx.moveTo(0, canvas.height * 0.6);
ctx.lineTo(canvas.width, canvas.height * 0.6);
ctx.moveTo(0, canvas.height * 0.8);
ctx.lineTo(canvas.width, canvas.height * 0.8);
ctx.stroke();
}
53 changes: 7 additions & 46 deletions src/audio-worklet/migration/worklet-recorder/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import createLinkFromAudioBuffer from './exporter.mjs';
import Waveform from '../../../library/Waveform.js';
import VUMeter from '../../../library/VUMeter.js';

// This enum states the current recording state.
const RecorderStates = {
Expand All @@ -17,10 +18,6 @@ const RecorderStates = {

const context = new AudioContext();

// Make the visualization clearer to the users.
const SCALE_FACTOR = 10;
// Make the visualization of vu meter more clear to the users.
const MAX_GAIN = 1;
let recordingState = RecorderStates.UNINITIALIZED;

let recordButton = document.querySelector('#record');
Expand Down Expand Up @@ -72,10 +69,11 @@ async function initializeAudio() {
const recordingNode = await setupRecordingWorkletNode(recordingProperties);

const waveform = new Waveform('#recording-canvas', analyserNode, 32);
const vuMeter = new VUMeter('#vu-meter', -40, analyserNode, 32, 6);

// We can pass this port across the app and let components handle
// their relevant messages.
const visualizerCallback = setupVisualizers(waveform);
const visualizerCallback = setupVisualizers(waveform, vuMeter);
const recordingCallback = handleRecording(
recordingNode.port, recordingProperties);

Expand All @@ -99,9 +97,6 @@ async function initializeAudio() {
/**
* Create and set up a WorkletNode to record audio from a microphone.
* @param {object} recordingProperties
* @param {number} properties.numberOfChannels
* @param {number} properties.sampleRate
* @param {number} properties.maxFrameCount
* @return {AudioWorkletNode} Recording node related components for
* the app.
*/
Expand All @@ -125,9 +120,6 @@ async function setupRecordingWorkletNode(recordingProperties) {
* state events to.
* @param {object} recordingProperties Microphone channel count, for
* accurate recording length calculations.
* @param {number} properties.numberOfChannels
* @param {number} properties.sampleRate
* @param {number} properties.maxFrameCount
* @return {function} Callback for recording-related events.
*/
function handleRecording(processorPort, recordingProperties) {
Expand Down Expand Up @@ -195,18 +187,18 @@ function changeButtonStatus() {
* Set up and handles calculations and rendering for all visualizers.
* @param {Waveform} waveform An instance of the Waveform object for
* visualization.
* @param {VUMeter} vuMeter An instance of the Waveform object for
* visualization.
* @return {function} Callback for visualizer events from the
* processor.
*/
function setupVisualizers(waveform) {
function setupVisualizers(waveform, vuMeter) {
let initialized = false;
let gain = 0;

// Wait for processor to start sending messages before beginning to
// render.
const visualizerEventCallback = async (event) => {
if (event.data.message === 'UPDATE_VISUALIZERS') {
gain = event.data.gain;

if (!initialized) {
initialized = true;
Expand All @@ -217,8 +209,7 @@ function setupVisualizers(waveform) {

function draw() {
if (recordingState === RecorderStates.RECORDING) {
const recordGain = gain;
drawVUMeter(recordGain);
vuMeter.draw();
waveform.draw();
}

Expand Down Expand Up @@ -258,33 +249,3 @@ const createRecord = (recordingProperties, recordingLength, sampleRate,
`recording-${new Date().getMilliseconds().toString()}.wav`;
downloadButton.disabled = false;
};

function drawVUMeter(volume) {
var canvas = document.getElementById('vu-meter');
var ctx = canvas.getContext('2d');

ctx.clearRect(0, 0, canvas.width, canvas.height);

ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);

var meterHeight = canvas.height *
(volume / MAX_GAIN) * SCALE_FACTOR;

ctx.fillStyle = '#f00';
ctx.fillRect(0, canvas.height - meterHeight, canvas.width, meterHeight);

ctx.strokeStyle = '#fff';
ctx.lineWidth = 1;
ctx.globalAlpha = 0.3;
ctx.beginPath();
ctx.moveTo(0, canvas.height * 0.2);
ctx.lineTo(canvas.width, canvas.height * 0.2);
ctx.moveTo(0, canvas.height * 0.4);
ctx.lineTo(canvas.width, canvas.height * 0.4);
ctx.moveTo(0, canvas.height * 0.6);
ctx.lineTo(canvas.width, canvas.height * 0.6);
ctx.moveTo(0, canvas.height * 0.8);
ctx.lineTo(canvas.width, canvas.height * 0.8);
ctx.stroke();
}
67 changes: 67 additions & 0 deletions src/library/FIFO.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* @classdesc A FIFO (First-In-First-Out) class that is specifically
* designed for the VUMeter class in the library directory. This class
* does not have a method to get or drop a head value, but it includes
* one for getting a minimum value form the queue.
*/
class FIFO {

/**
* @constructor
* @param {Number} size The size of the FIFO.
*/
constructor(size) {
/** @private @const {!size} The maximum number of elements that
* can be stored in the FIFO */
this.size_ = size;
/** @private @const {!data} The actual data array for the FIFO. */
this.data_ = new Float32Array(size);
/** @private {!head} The index of the first element in the FIFO */
this.head_ = 0;
/** @private {!tail} The index of the last element in the FIFO */
this.tail_ = 0;
/** @private {!count} The actual number of elements in the FIFO */
this.count_ = 0;
}

/**
* Push a new value to the tail index of the queue. If the FIFO is
* full, it removes the element at the head index (the oldest) and
* insert the new element at the tail index (the newest).
* @param {Number} value The new value to be added to the FIFO.
*/
push(value) {
if (this.count_ === this.size_) {
this.head_ = (this.head_ + 1) % this.size_;
this.count_--;
}
this.data_[this.tail_] = value;
this.tail_ = (this.tail_ + 1) % this.size_;
this.count_++;
}

/**
* Helper function for debugging.
* @returns {Array} A newly created array contains the elements of
* the FIFO in the order in which they were added.
*/
toArray() {
const result = [];
let index = this.head_;
for (let i = 0; i < this.count_; i++) {
result.push(this.data_[index]);
index = (index + 1) % this.size_;
}
return result;
}

/**
* Find and return the minimum element in the FIFO.
* @returns {Number}
*/
getMinValue() {
return Math.min(...this.data_);
}
}

export default FIFO;
Loading

0 comments on commit 031cffa

Please sign in to comment.