/****************** Microphone to Piano original Code by Vamoss Original code link: https://www.openprocessing.org/sketch/760560 Author links: http://vamoss.com.br http://twitter.com/vamoss http://github.com/vamoss ******************/ const micSampleRate = 44100; const freqDefinition = 8192; const freqSteps = micSampleRate/freqDefinition; const hitKeyThreshold = 80;//minimum level to hit the piano key const minFreqHz = 130;//C3 const maxFreqHz = 2903;//C7 const minFreq = Math.floor(minFreqHz/2/1.445);//2/1.445 is a magic number to convert Hz to MIC frequency, dont know why... const maxFreq = Math.floor(maxFreqHz/2/1.445);//2/1.445 is a magic number to convert Hz to MIC frequency, dont know why... const scaleIndexToNote = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; let mic, fft, spectrum; let historygram; let piano = []; let pressedKey = -1; let keyHeight; let state, nextState, startTime, duration; const STATES = {CALIBRATING: "CALIBRATING", RECORDING: "RECORDING", PLAYING: "PLAYING"}; const calibrationDuration = 5 * 1000;//milliseconds let calibrateSum = []; let avarageSpectrum;//array to threshold the noise from the microphone const recordDuration = 10 * 1000;//milliseconds let recordedSequence = []; function setup() { createCanvas(windowWidth, windowHeight); //historygram = createGraphics(width/4,maxFreq-minFreq); historygram = createGraphics(width/2,height); mic = new p5.AudioIn(); mic.start(); fft = new p5.FFT(0.0, freqDefinition); mic.connect(fft); //setup the Piano array let totalKeys = freqToMidi(maxFreqHz) - freqToMidi(minFreqHz); let keyX = width / 2; let keyWidth = width / 2 / (totalKeys/12*7); keyHeight = width / 1920 * 100; for(let midi = freqToMidi(minFreqHz); midi < freqToMidi(maxFreqHz); midi++){ let index = midi % 12; //black key indices //c c# d d# e f f# g g# a a# b if(index == 1 || index == 3 || index == 6 || index == 8 || index == 10){ createKey(midi+1, "WHITE", keyX, keyWidth); createKey(midi, "BLACK", keyX, keyWidth); midi++; }else{ createKey(midi, "WHITE", keyX, keyWidth); } keyX += keyWidth; } avarageSpectrum = new Array(maxFreq-minFreq); avarageSpectrum.fill(0); textAlign(CENTER, CENTER); textSize(24); setState(STATES.CALIBRATING); } function draw() { background(255); spectrum = fft.analyze(); //CALCULATE AVARAGE NOISE FROM MICROPHONE if(state == STATES.CALIBRATING){ calibrateSum.push(spectrum.slice()); for (let i = minFreq; i < maxFreq; i++) { let index = i - minFreq; let sum = 0; for (let j = 0; j < calibrateSum.length; j++) { sum += calibrateSum[j][i] * 1.1; } avarageSpectrum[index] = sum / calibrateSum.length; } } //DRAW MIC SPECTRUM stroke(0); fill(255,255,0, 150); beginShape(); vertex(width*3/4,height); for (let i = minFreq; i < maxFreq; i++) { let x = (1 - spectrum[i] / 255) * width*1/4; let y = (i - minFreq) / (maxFreq - minFreq - 1) * height; vertex(width-x, height-y); } vertex(width*3/4,0); endShape(CLOSE); //DRAW AVARAGE SPECTRUM stroke(0); fill(0, 150); beginShape(); vertex(width*3/4,height); for (let i = minFreq; i < maxFreq; i++) { let index = i - minFreq; let x = (1 - avarageSpectrum[index] / 255) * width/4; let y = index / (maxFreq - minFreq - 1) * height; //let y = index / (height - 1) * width / 2; vertex(width-x,height- y); } vertex(width*3/4,0); endShape(CLOSE); //DRAW HISTORYGRAM historygram.image(historygram, -1,0); for (let i = maxFreq; i >= minFreq; i--) { let index = i - minFreq; let intensity = (spectrum[i] - avarageSpectrum[index]) * 3; historygram.stroke(255-intensity,255-intensity/2,intensity); historygram.point(historygram.width-1,height- index); } fill(255); push(); scale(1,1) image(historygram, 0,0, width* 3/4, height); pop(); //FIND THE HIGHEST LEVEL OF AUDIO FREQUENCY let selectedFreq = -1; let selectedDist = 0; for (let i = minFreq; i < maxFreq; i++) { let index = i - minFreq; if(spectrum[i] - avarageSpectrum[index] > selectedDist){ selectedDist = spectrum[i] - avarageSpectrum[index]; selectedFreq = i; } } //DRAW HIGHEST LEVEL const magicNumber = 1.091;//I had to multiply to this magic number, to be able to get the right Hz, I dont know why... let freq = selectedFreq * freqSteps/2 * magicNumber; let tempPressedKey = freqToMidi(freq); if(selectedDist > hitKeyThreshold){ let xSelected = (selectedFreq-minFreq) / (maxFreq-minFreq) *height; stroke(255, 0, 255); line(width*3/4,height- xSelected,width,height-xSelected); noStroke(); fill(255, 0,255); text(freq.toFixed(2) + "Hz",width*7/8, height- xSelected-20); let note = scaleIndexToNote[tempPressedKey % 12] + (floor(tempPressedKey/12)-1); text(note, width*7/8, height- xSelected+30); } if(state == STATES.RECORDING){ //RECORD if(selectedDist > hitKeyThreshold && piano[tempPressedKey]){ if(pressedKey != tempPressedKey) { //PRESS KEY pressedKey = tempPressedKey; recordedSequence.push({ key: pressedKey, start: new Date().getTime() - startTime, duration: 100 }); }else{ //still pressing the same key let lastIndex = recordedSequence.length-1; recordedSequence[lastIndex].duration = new Date().getTime() - startTime - recordedSequence[lastIndex].start; } }else{ //RELEASE KEY pressedKey = -1; } //DRAW RECORDING SEQUENCE let scrollY = (new Date().getTime() - startTime)/10; noStroke(); fill(255); for(let i = recordedSequence.length-1; i >= 0; i--){ let record = recordedSequence[i]; let pianoKey = piano[record.key]; let x = pianoKey.x; let y = keyHeight + scrollY - record.start/10; let w = pianoKey.w; let h = -record.duration/10; rect(x, y, w, h); } } //DRAW PLAYING SEQUENCE if(state == STATES.PLAYING){ let time = (new Date().getTime() - startTime); let scrollY = time/10; pressedKey = -1; noStroke(); fill(255); for(let i = recordedSequence.length-1; i >= 0; i--){ let record = recordedSequence[i]; let pianoKey = piano[record.key]; let x = pianoKey.x; let y = keyHeight - scrollY + record.start/10; let w = pianoKey.w; let h = record.duration/10; rect(x, y, w, h); //PLAY SOUND if(time > record.start && time < record.start + record.duration){ pressedKey = record.key; if(!record.played){ record.played = true; pianoKey.osc.start(); pianoKey.envelope.play(pianoKey.osc, 0, record.duration/1000); } }else{ record.played = false; } } } //DRAW PIANO noStroke(); for(let midi = freqToMidi(minFreqHz); midi < freqToMidi(maxFreqHz); midi++){ if(piano[midi].type == "BLACK"){ //first draw the next white key if(pressedKey == midi+1){ fill(255, 255, 0); }else{ fill(255); } rect(piano[midi+1].x, 0, piano[midi+1].w, piano[midi+1].h); //then draw the black key if(pressedKey == midi){ fill(255, 255, 0); }else{ fill(0); } rect(piano[midi].x, 0, piano[midi].w, piano[midi].h); midi++; }else{ //white key if(pressedKey == midi){ fill(255, 255, 0); }else{ fill(255); } rect(piano[midi].x, 0, piano[midi].w, piano[midi].h); } } //CHECK NEXT STATE let time = new Date().getTime(); let remaining = floor((duration - (time - startTime))/1000); fill(255, 0, 255); text(state + ": " + remaining + "s", width / 4, height / 2 - 30); if(time - startTime > duration){ setState(nextState); } } function setState(s){ state = s; startTime = new Date().getTime(); switch (state) { case STATES.CALIBRATING: duration = calibrationDuration; nextState = STATES.RECORDING; break; case STATES.RECORDING: duration = recordDuration; nextState = STATES.PLAYING; break; case STATES.PLAYING: break; } } function createKey(midi, type, x, w){ let h = keyHeight; if(type == "BLACK"){ x -= w*0.3; w /= 1.5; h /= 2; } let envelope = new p5.Envelope(); envelope.setRange(0.07, 0);//attackLevel, releaseLevel envelope.setADSR(0.001, 0.8, 0.1, 0.5);//attackTime, decayTime, sustainRatio, releaseTime let osc = new p5.SinOsc(); osc.freq(midiToFreq(midi)); piano[midi] = { envelope: envelope, osc: osc, midi: midi, freq: midiToFreq(midi), x: x, w: w, h: h, type: type, played: false }; }