-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f15f093
commit 77b688f
Showing
4 changed files
with
294 additions
and
0 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
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; | ||
} |
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,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%; | ||
} |
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,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; |
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,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> | ||
<input type="radio" id="wave-type-square" name="wave-type" value="square"><label for="wave-type-square">square</label> | ||
<input type="radio" id="wave-type-triangle" name="wave-type" value="triangle"><label for="wave-type-triangle">triangle</label> | ||
<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> |