Skip to content

Commit

Permalink
theremin homework
Browse files Browse the repository at this point in the history
  • Loading branch information
Imimimikimmmi committed Nov 30, 2023
1 parent f15f093 commit 77b688f
Show file tree
Hide file tree
Showing 4 changed files with 294 additions and 0 deletions.
50 changes: 50 additions & 0 deletions theremin/11-auxiliary-functions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"use strict";

let notenames = {
0: "C",
1: "C#",
2: "D",
3: "Eb",
4: "E",
5: "F",
6: "F#",
7: "G",
8: "Ab",
9: "A",
10: "Bb",
11: "B"
}

function interval(frequency, semitones) {
// Assuming equal temperament
return frequency * Math.pow(2, semitones / 12);
}

function midiToFrequency(midinumber, concertA = 440) {
// converts a MIDI note number into its equivalent frequency.
const A4 = 69
if (midinumber === A4) {
return concertA;
}
let semitones = midinumber - A4;
return interval(440, semitones);
}

function frequencyToMidi(frequency){
// converts a frequency into its equivalent MIDI note number.
let midinumber = (( 12 * Math.log(frequency / 220.0) / Math.log(2.0)) + 57.001 );
return midinumber
}

function noteFromFrequency(frequency, withOctave=false) {
// converts a frequency into its closest human-readable note name.
const midinumber = frequencyToMidi(frequency);
const pitchclass = midinumber % 12;
let octave = (midinumber - pitchclass) / 12;
let notename = notenames[Math.round(pitchclass)];
if (withOctave) {
octave--;
notename += octave;
}
return notename;
}
38 changes: 38 additions & 0 deletions theremin/11.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
h4 {
margin-top: 0;
}
*{ touch-action: none; }

#thereminZone {
background: linear-gradient(90deg, rgb(0, 153, 184) 0%, rgb(181, 243, 255) 100%);
height: 200px;
width: 100%;
border-style: none;
margin: 20px 0;
}

#thereminLogo {
font-family: 'Holtwood One SC', serif;
}


#theremin-infos {
border: 1px solid rgb(0, 153, 184);
padding: 10px 20px;
color: rgb(0, 153, 184);
display: inline-block;
width: 35%;
}

#theremin-infos div .title {
display: inline-block;
width: 35%;
}
#theremin-infos div .info {
font-weight: 700;
}

#theremin-options {
display: inline-block;
width: 55%;
}
152 changes: 152 additions & 0 deletions theremin/11.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"use strict";

var oscillator; // We make the oscillator object as global
var reverb; // We make the reverb effect object as global
var autotune = false; // we set autotune to false by default
const userGestureEvents = [
'click',
'contextmenu',
'auxclick',
'dblclick',
'mousedown',
'mouseup',
'pointerup',
'touchend',
'keydown',
'keyup',
];

var userInteraction = false;
const unlockAudio = () => {
userGestureEvents.forEach(eventName => {
document.removeEventListener(eventName, unlockAudio);
userInteraction = true;
});
};


// Turn theremin on
function thereminOn(oscillator) {
oscillator.play();
if( userInteraction == false ) { window.alert("Please click on the page before playing so the browser can enable audio playback."); }
}

// Control the theremin
function thereminControl(e, oscillator, theremin) {
let x = e.offsetX;
let y = e.offsetY;
console.log(x, y);

let minFrequency = 220.0;
let maxFrequency = 880.0;
let freqRange = maxFrequency - minFrequency;
let thereminFreq = minFrequency + (x / theremin.clientWidth) * freqRange;
let thereminVolume = 1.0 - (y / theremin.clientHeight);

if( autotune ) {
var midiNote = Math.floor( frequencyToMidi(thereminFreq) );
thereminFreq = midiToFrequency(midiNote, 440);
}
oscillator.frequency = thereminFreq;
var frequency = document.querySelector('#frequency .info');
frequency.innerHTML = thereminFreq;

oscillator.volume = thereminVolume;
var volumeElement = document.querySelector('#volume .info');
var volumeValue = Math.round( thereminVolume * 10 );
volumeElement.innerHTML = volumeValue;

var noteNameFromFrequency = noteFromFrequency(thereminFreq, true);
var noteName = document.querySelector('#note-name .info');
noteName.innerHTML = noteNameFromFrequency;
}

// Turn theremin off
function thereminOff(oscillator) {
oscillator.stop();
}

// Runs a new instance of Oscillator when we change options
function startNewOscillator(waveType) {
// Instantiate a sine wave with pizzicato.js
oscillator = new Pizzicato.Sound({
source: 'wave',
options: {
type: waveType,
frequency: 220
}
});
}

function startEffect(effectType) {
reverb = new Pizzicato.Effects.Reverb({
time: 1,
decay: 0.8,
reverse: true,
mix: 0.5
});
oscillator.addEffect(reverb);
}



function runAfterLoadingPage() {
startNewOscillator('sine');

userGestureEvents.forEach(eventName => {
document.addEventListener(eventName, unlockAudio);
});

// Get the theremin div from the html
const theremin = document.getElementById("thereminZone");


// Theremin plays when the mouse enters the theremin div
theremin.addEventListener("mouseenter", function () {
thereminOn(oscillator);
});
// Theremin is controlled while the mouse is inside the theremin div
theremin.addEventListener("mousemove", function (e) {
thereminControl(e, oscillator, theremin);
});
// Theremin stops when the mouse leaves the theremin div
theremin.addEventListener("mouseleave", function () {
thereminOff(oscillator);
});


// Detects if NOTE checkbox changes
let chromatic = document.getElementById("autotune");
chromatic.addEventListener( "change", () => {
if ( chromatic.checked ) {
autotune = true;
} else {
autotune = false;
}
});


// Detects if wave type radio changes
// If so we restart our Oscillator accordingly
var waves = document.querySelectorAll('input[type=radio][name="wave-type"]');
waves.forEach(radio =>
radio.addEventListener('change', () =>
startNewOscillator(radio.value)
)
);

// Detects if REVERB checkbox changes
let effect = document.getElementById("reverb");
effect.addEventListener( "change", () => {
const selectedWaveType = document.querySelector('input[type=radio][name="wave-type"]:checked').value;

if ( effect.checked ) {
startEffect('reverb')
} else {
oscillator.removeEffect(reverb)
}
});

}

window.onload = runAfterLoadingPage;
54 changes: 54 additions & 0 deletions theremin/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<html>

<head>
<meta charset="utf-8" />
<title>Theremin</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
<link rel="stylesheet" href="11.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/pizzicato/0.6.4/Pizzicato.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Holtwood+One+SC&display=swap" rel="stylesheet">
<script src="11.js" type="text/JavaScript"></script>
<script src="11-auxiliary-functions.js" type="text/JavaScript"></script>
</head>

<body>
<h1>Online <span id="thereminLogo">Theremin</span> instrument</h1>
<h2>Additional features added by Maxime Béchard-Pelletier</h2>
<p>The theremin is an electronic musical instrument invented by <a
href="https://en.wikipedia.org/wiki/Leon_Theremin">Leon Theremin</a> in 1920s.</p>
<p>This demo uses <a href="https://alemangui.github.io/pizzicato/">pizzicato.js</a> to implement a "virtual"
Theremin on your browser.
</p>
<p>The <code>pizzicato.js</code> library (and other similar ones) use the <a
href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API">Web Audio API</a> to play sounds on
the browser.
</p>
<hr/>
<br/>


<div id="theremin-options">
<h4>Sound options</h4>
<input type="checkbox" name="autotune" id="autotune"> <label for="autotune">play chromatic notes</label><br/>
<input type="checkbox" name="reverb" id="reverb"> <label for="reverb">add reverb</label><br/>

<input type="radio" id="wave-type-sine" name="wave-type" value="sine" checked><label for="wave-type-sine">sine</label> &nbsp;&nbsp;&nbsp;
<input type="radio" id="wave-type-square" name="wave-type" value="square"><label for="wave-type-square">square</label> &nbsp;&nbsp;&nbsp;
<input type="radio" id="wave-type-triangle" name="wave-type" value="triangle"><label for="wave-type-triangle">triangle</label> &nbsp;&nbsp;&nbsp;
<input type="radio" id="wave-type-sawtooth" name="wave-type" value="sawtooth"><label for="wave-type-sawtooth">sawtooth</label>
</div>

<div id="theremin-infos">
<h4>Infos</h4>
<div id="frequency"><span class="title">Frequency:</span> <span class="info"></span></div>
<div id="note-name"><span class="title">Note:</span> <span class="info"></span></div>
<div id="volume"><span class="title">Volume:</span> <span class="info"></span></div>
</div>


<div id="thereminZone"></div>
</body>

</html>

0 comments on commit 77b688f

Please sign in to comment.