Initial commit: simple synth
This commit is contained in:
		
							
								
								
									
										30
									
								
								index.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								index.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| .whiteKey { | ||||
|     color: white; | ||||
|     border: solid 1px; | ||||
|     border-color: grey; | ||||
|  | ||||
|     height: 67pt; | ||||
| /* | ||||
|     max-height: 60pt; | ||||
|     max-width: 20pt; | ||||
|     */ | ||||
|     position: relative; | ||||
|     left: 7.5pt; | ||||
|     width: 20pt; | ||||
|     display: inline-block; | ||||
| } | ||||
|  | ||||
| .blackKey { | ||||
|     color: black; | ||||
|     background-color: black; | ||||
|  | ||||
|     position: absolute; | ||||
|     z-index: 1; | ||||
|     height: 40pt; | ||||
|     width: 15pt; | ||||
|     /* | ||||
|     max-height: 40pt; | ||||
|     max-width: 15pt; | ||||
|     */ | ||||
|     display: inline-block; | ||||
| } | ||||
							
								
								
									
										18
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
|   <head> | ||||
|     <meta charset="utf-8"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
|     <meta name="color-scheme" content="light dark"> | ||||
|     <link rel="stylesheet" href="pico.min.css"> | ||||
|     <link rel="stylesheet" href="index.css"> | ||||
|     <script src="index.js"></script> | ||||
|     <title>Tooner 0</title> | ||||
|   </head> | ||||
|   <body> | ||||
|     <main class="container"> | ||||
|         <div id="keyboard"> | ||||
|         </div> | ||||
|     </main> | ||||
|   </body> | ||||
| </html> | ||||
							
								
								
									
										92
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| const kNumKeys = 49; | ||||
| const kFirstKey = 'C2'; | ||||
| const kNotes = ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'G#', 'A', 'Bb', 'B']; | ||||
| const kA0Frequency = 27.5; | ||||
| const kSemiToneMultiplier = Math.pow(2, 1/12); | ||||
|  | ||||
| const audioCtx = new AudioContext(); | ||||
|  | ||||
| function playNote(frequency, duration) { | ||||
|     let currentGain = 1.0; | ||||
|  | ||||
|     const bassBoost = f => { | ||||
|         const t = f / 20000; | ||||
|         const a = 4.0; | ||||
|         const b = 0.2; | ||||
|  | ||||
|         return b * t + a * (1 - t); | ||||
|     }; | ||||
|  | ||||
|     while (frequency < 20000) { | ||||
|         const oscillator = new OscillatorNode(audioCtx); | ||||
|         const gain = new GainNode(audioCtx); | ||||
|  | ||||
|         oscillator.type = 'sine'; | ||||
|         oscillator.frequency.value = frequency; // value in hertz | ||||
|         oscillator.connect(gain); | ||||
|         gain.gain.setValueAtTime(0, audioCtx.currentTime); | ||||
|         gain.gain.exponentialRampToValueAtTime(bassBoost(frequency) * currentGain, audioCtx.currentTime + 0.010); | ||||
|         gain.gain.linearRampToValueAtTime(1e-10, audioCtx.currentTime + 0.500); | ||||
|         gain.connect(audioCtx.destination); | ||||
|         oscillator.start(); | ||||
|  | ||||
|         setTimeout(() => {oscillator.stop();}, duration); | ||||
|  | ||||
|         frequency *= 2; | ||||
|         currentGain *= 0.3; | ||||
|     } | ||||
| } | ||||
|  | ||||
| function noteFrequency(key) { | ||||
|     let [note, octave, ] = key.split(/(\d)/); | ||||
|     octave = parseInt(octave); | ||||
|  | ||||
|     const interval = kNotes.indexOf(note) - kNotes.indexOf('A') + octave * 12; | ||||
|  | ||||
|     let freq = kA0Frequency * Math.pow(kSemiToneMultiplier, interval); | ||||
|  | ||||
|     return freq; | ||||
| } | ||||
|  | ||||
| function onKeyClick(event) { | ||||
|     const note = event.srcElement.id; | ||||
|     const freq = noteFrequency(note); | ||||
|     playNote(freq, 500); | ||||
| } | ||||
|  | ||||
| function makeKey(note) { | ||||
|     const key = document.createElement('span'); | ||||
|     if (note.includes('#') || note.includes('b')) { | ||||
|         key.classList.add('blackKey'); | ||||
|     } else { | ||||
|         key.classList.add('whiteKey'); | ||||
|     } | ||||
|     key.id = note; | ||||
|     key.addEventListener('click', onKeyClick); | ||||
|  | ||||
|     return key; | ||||
| } | ||||
|  | ||||
| function setupKeyboard() { | ||||
|     const keyboard = document.getElementById('keyboard'); | ||||
|     keyboard.innerHTML = ''; | ||||
|  | ||||
|     let curIndex = kNotes.indexOf(kFirstKey.charAt(0)); | ||||
|     let octave = parseInt(kFirstKey.charAt(1)); | ||||
|  | ||||
|     for (let i = 0; i < kNumKeys; i++) { | ||||
|         const note = kNotes[curIndex] + octave; | ||||
|         const key = makeKey(note) | ||||
|         keyboard.appendChild(key); | ||||
|  | ||||
|         curIndex += 1; | ||||
|         if (curIndex >= kNotes.length) { | ||||
|             octave += 1; | ||||
|             curIndex = curIndex % kNotes.length; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| window.addEventListener('load', e => { | ||||
|     setupKeyboard(); | ||||
| }); | ||||
							
								
								
									
										4
									
								
								pico.min.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								pico.min.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user