Files
old-svelte-site/src/routes/projects/tuner.svelte
wompmacho 683381ccaa minor css updates, moving all videos to django site
instances cant load mp4s for some reason, refactored to pul straight from other site
2020-11-10 09:03:00 -05:00

264 lines
7.7 KiB
Svelte

<svelte:head>
<title>JavaScript Guitar Tuner</title>
</svelte:head>
<div id="tuner_sketch" class="content"></div>
<hr>
<h1>JavaScript Guitar Tuner</h1>
<p>
JavaScript web app that uses a microphone as input and
displays frequency, note and a helper line to assist in
tuning accuracy. Everything is drawn on a canvas including
the background guitar image.
</p>
<p>
Application utilizes P5.js library as
well as ML5.js for a pitch detection assistance.
Additionally notes and frequencies are fetched via a JSON
library hosted on Github.
</p>
<hr>
<h5>Click on page to enable permissions and start the tuner</h5>
<style>
p{
margin: 1em;
border-radius: 5px;
padding: 1em;
background-color: #333333;
}
#tuner_sketch{
height: 50vh;
width: calc(50vh / 1.5);
margin: auto;
}
hr{
background-color: red;
max-width: 20vw;
}
h1{
font-size: x-large;
margin: 1em;
}
h5{
font-size: smaller;
max-width: 40vw;
margin: auto;
text-align: center;
}
</style>
<script>
import { onMount } from "svelte";
import { onDestroy } from 'svelte'
let mic, myp5;
onMount(async () => {
const container = document.getElementById('tuner_sketch');
const p5Module = await import("p5");
window.p5 = p5Module.default;
await import("p5/lib/addons/p5.sound");
const ml5 = await import("ml5");
window.ml5 = ml5.default;
let pitch;
let freq = 0;
let threshold = 1;
let canvas;
let audioContext;
let WIDTH = container.clientWidth;
let HEIGHT = container.clientHeight;
let baseFreq = 440;
let test = 0;
let notes = [];
let img;
// ml5 pitchdetection-crepe modle can only handle from C#1 - D6 Reliably
const model_url = "https://cdn.jsdelivr.net/gh/ml5js/ml5-data-and-models/models/pitch-detection/crepe/";
const note_frequencies = "https://gist.githubusercontent.com/quimbs/fe52bc25dca44cd3fcb8/raw/f9fe9c6bb68f343a4b81a17445efc200702d45dd/notes.json";
const sketch = (sketch) => {
sketch.preload = () => {
// load image
img = sketch.loadImage("img/notMyguitar.png");
}
sketch.setup = () => {
// Resize for init
WIDTH = document.getElementById("tuner_sketch").clientWidth;
HEIGHT = document.getElementById("tuner_sketch").clientHeight;
// Create Canvas
sketch.createCanvas(WIDTH, HEIGHT);
// request access to mic and starting mic
audioContext = sketch.getAudioContext();
mic = new p5.AudioIn();
mic.start(startPitch);
console.log('Mic Started');
// Get notes/freq json
getNotes();
};
// fetches notes/freq JSON and overwrites empty note array
async function getNotes() {
const response = await fetch(note_frequencies);
const data = await response.json();
notes = data[baseFreq];
}
sketch.draw = () => {
WIDTH = container.clientWidth;
HEIGHT = container.clientHeight;
sketch.clear();
resetSketch();
}
function resetSketch(){
sketch.image(img, WIDTH / 2 / 2, 0, WIDTH / 2, HEIGHT);
sketch.textAlign(sketch.CENTER, sketch.CENTER);
sketch.fill(105, 105, 105);
sketch.strokeWeight(3);
let closestNote = 0;
let difference = Infinity;
for (let i = 0; i < notes.length; i++) {
let diff = freq - notes[i].frequency;
if (Math.abs(diff) < Math.abs(difference)) {
closestNote = notes[i];
difference = diff;
}
}
// change color based on prox to threshold
if (Math.abs(difference) < threshold) {
sketch.strokeWeight(6);
sketch.stroke(0, 255, 0);
sketch.fill(0, 0, 0);
} else if (Math.abs(difference) < threshold + 4) {
sketch.strokeWeight(4);
sketch.stroke(255, 165, 0);
} else if (Math.abs(difference) < threshold + 13) {
sketch.strokeWeight(3);
sketch.stroke(255, 165, 0);
}
// freq text
let freq_message = freq.toFixed(2) + " hz";
let note_message = closestNote.note;
// test if pitch detected
if ((sketch.i = parseInt(freq_message) < 1)) {
freq_message = "no audio detected";
sketch.stroke(27, 51, 71);
sketch.strokeWeight(3);
note_message = "🔇";
}
// draw text
function drawText(freq_message, note_message) {
sketch.textSize(WIDTH / 16);
sketch.text(freq_message, WIDTH / 2, (HEIGHT / 2) * 1.8);
sketch.textSize(WIDTH / 6);
sketch.text(note_message, WIDTH / 2, (HEIGHT / 2) * 1.5);
}
drawText(freq_message, note_message);
// Canvas radius
let radius = WIDTH / 2;
// Default for p5 is RADIANS
sketch.angleMode(sketch.DEGREES);
// set angle to 90 relative to origin
let angle = -90 + difference;
// set origin to center
let x1 = WIDTH / 2;
let y1 = HEIGHT / 2;
// create circles
let x2 = Math.cos(angle) * radius;
let y2 = Math.sin(angle) * radius;
// Draw line
sketch.line(x1, y1, x2 + radius, y2 + radius);
console.log(
"x1=" +
x1 +
"|" +
"y1=" +
y1 +
"|" +
"x2=" +
x2 +
"|" +
"y2=" +
y2 +
"|" +
"radius=" +
radius +
"|" +
"angle=" +
angle +
"|"
);
sketch.stroke(0);
}
sketch.windowResized = () => {
sketch.resizeCanvas(width, height);
resetSketch();
}
// Create pitch from ml5 lib
function startPitch() {
pitch = ml5.pitchDetection(
model_url,
audioContext,
mic.stream,
modelLoaded
);
}
// Loads pitch model
function modelLoaded() {
console.log("Model Loaded");
getPitch();
}
// recursively keeps getting pitch
function getPitch() {
pitch.getPitch(function (err, frequency) {
if (frequency) {
freq = frequency;
} else {
freq = -100;
}
getPitch();
});
}
};
myp5 = new p5(sketch, container);
});
onDestroy(() => {
mic.stop();
myp5.remove();
console.log('Mic Stopped');
});
</script>