Frequency to midi usb

XeoteK

Member
Hello friends,

I am attempting to create a code for teensy whereby in simple terms, takes an input fundamental frequency from an audio source and outputs the note to usb midi monophonically.

I have an issue where it correctly detects the fundamental, but if the same note is sustained in the microphone, the teensy outputs midi on and midi off messages for the same midi value rapidly.

My goal is to have the teensy send midi on and stay on as long as the input frequency is sustained. The teensy should only send a midi off for that note if the amplitude of the input falls below a particular threshold or there is a change in detected frequency.

Please see the below code for reference, and I appreciate any input that can be provided to simplify or alter the code to accomplish the goal.

Thanks

#include <Audio.h>
#include <MIDI.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <Wire.h>

const int channel = 0;
const int myInput = AUDIO_INPUT_MIC;

AudioInputI2S audioInput;
AudioAnalyzeNoteFrequency notefreq1; // YIN algorithm object
AudioConnection patchCord1(audioInput, 0, notefreq1, 0);
AudioAnalyzeRMS rms1;
AudioConnection patchCord2(audioInput, 0, rms1, 0);
AudioControlSGTL5000 audioShield;
const int numNotes = 127; // Number of standard musical notes

float musicalNotes[numNotes];
int currentNote = -1;
bool noteSent = false;

void setup() {
AudioMemory(30);
audioShield.enable();
audioShield.inputSelect(myInput);
audioShield.volume(0.2);
notefreq1.begin(0.1); // Initialize the YIN algorithm with a threshold
Serial.begin(9600);
calculateMusicalNotes();
}

void loop() {
float micInputLevel = rms1.read();

if (micInputLevel > 0.05) {
// Determine MIDI velocity based on the RMS value
int midiVelocity = int(map(micInputLevel, 0.0, 1.0, 0, 127));

if (notefreq1.available()) {
// Check the microphone input level (RMS value)
float Frequency = notefreq1.read();
int closestNoteIndex = findClosestNoteIndex(Frequency);
int newMidiNote = hzToMidiNote(musicalNotes[closestNoteIndex]);

if (newMidiNote != currentNote && noteSent) {
sendMIDINoteOff(currentNote);
noteSent = false;
// Go back to the beginning of the loop
return;
}
else if (noteSent) {
return; // Go back to the beginning of the loop
}

sendMIDINoteOn(newMidiNote, midiVelocity);
currentNote = newMidiNote;
noteSent = true;
}
}
else {
if (currentNote != -1) {
// Mic input threshold not met, send MIDI note off and reset
sendMIDINoteOff(currentNote);
currentNote = -1;
noteSent = false;
}
}
}

int findClosestNoteIndex(float detectedFrequency) {
int closestNoteIndex = 0;
float minDistance = abs(detectedFrequency - musicalNotes[0]);

for (int i = 1; i < numNotes; i++) {
float distance = abs(detectedFrequency - musicalNotes);
if (distance < minDistance) {
minDistance = distance;
closestNoteIndex = i;
}
}
return closestNoteIndex;
}

void calculateMusicalNotes() {
float ratio = pow(2.0, 1.0 / 12.0); // 12th root of 2
float currentFrequency = 116.54; // Adjust this to match your range

for (int i = 0; i < numNotes; i++) {
musicalNotes = currentFrequency;
currentFrequency *= ratio;
}
}

int hzToMidiNote(float frequency) {
if (frequency <= 0.0) {
return 0; // Return 0 for an invalid frequency
}

float note = 12.0 * log2(frequency / 440.0) + 69.0;
return int(note + 0.5);
}

void sendMIDINoteOn(int note, int velocity) {
if (note >= 0 && note <= 127) {
usbMIDI.sendNoteOn(note, velocity, channel);
}
}

void sendMIDINoteOff(int note) {
if (note >= 0 && note <= 127) {
usbMIDI.sendNoteOff(note, 0, channel);
}
}
 
There are code tags for quoting code that retain indentation - use </> button for this.

You assume that notefreq1.available() is idempotent - I suspect its not, each successful call followed by a read() perhaps restarts the frequency acquisition process which means it will return false next time round. You probably need to have a wait-state after each successful call to available()? A guess though...
 
Last edited:
There are code tags for quoting code that retain indentation - use </> button for this.

You assume that notefreq1.available() is idempotent - I suspect its not, each successful call followed by a read() perhaps restarts the frequency acquisition process which means it will return false next time round. You probably need to have a wait-state after each successful call to available()? A guess though...
Hello sir, I can see what you mean.. I have made the following adjustments to the code to add and will test it out within the next couple days. Let me know what you think.

//edit: I have uploaded and tested the following code with the same result (midi on-off messages when there is a a sustained input frequency)

//edit2: your guess was correct. I had the delay on the wrong part of the code. I added delay(70); on the line directly after the .available() function and it works great. I had to play around with the delay times but 70 was the lowest I could use to achieve a sustained note. Let me know if you have any other idea to simplify calculations or processing overhead as well! Thanks again!

Code:
#include <Audio.h>
#include <MIDI.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <Wire.h>

const int channel = 0;
const int myInput = AUDIO_INPUT_MIC;

AudioInputI2S audioInput;
AudioAnalyzeNoteFrequency notefreq1; // YIN algorithm object
AudioConnection patchCord1(audioInput, 0, notefreq1, 0);
AudioAnalyzeRMS rms1;
AudioConnection patchCord2(audioInput, 0, rms1, 0);
AudioControlSGTL5000 audioShield;
const int numNotes = 127; // Number of standard musical notes

float musicalNotes[numNotes];
int currentNote = -1;
bool noteSent = false;

void setup() {
  AudioMemory(30);
  audioShield.enable();
  audioShield.inputSelect(myInput);
  audioShield.volume(0.2);
  notefreq1.begin(0.1); // Initialize the YIN algorithm with a threshold
  Serial.begin(9600);
  calculateMusicalNotes();
}

void loop() {
  float micInputLevel = rms1.read();

  if (micInputLevel > 0.05) {
    // Determine MIDI velocity based on the RMS value
    int midiVelocity = int(map(micInputLevel, 0.0, 1.0, 0, 127));

    if (notefreq1.available()) {
      Delay(70);  // Wait for a short duration to avoid rapid MIDI on and off messages
      float Frequency = notefreq1.read();
      int closestNoteIndex = findClosestNoteIndex(Frequency);
      int newMidiNote = hzToMidiNote(musicalNotes[closestNoteIndex]);

      if (newMidiNote != currentNote && noteSent) {
        sendMIDINoteOff(currentNote);
        noteSent = false;
        // Go back to the beginning of the loop
        return;
      } else if (noteSent) {
        // Go back to the beginning of the loop
        return;
      }

      sendMIDINoteOn(newMidiNote, midiVelocity);
      currentNote = newMidiNote;
      noteSent = true;
    }
  } else {
    if (currentNote != -1) {
      // Mic input threshold not met, send MIDI note off and reset
      sendMIDINoteOff(currentNote);
      currentNote = -1;
      noteSent = false;
    }
  }
}

int findClosestNoteIndex(float detectedFrequency) {
  int closestNoteIndex = 0;
  float minDistance = abs(detectedFrequency - musicalNotes[0]);

  for (int i = 1; i < numNotes; i++) {
    float distance = abs(detectedFrequency - musicalNotes);
    if (distance < minDistance) {
      minDistance = distance;
      closestNoteIndex = i;
    }
  }
  return closestNoteIndex;
}

void calculateMusicalNotes() {
  float ratio = pow(2.0, 1.0 / 12.0); // 12th root of 2
  float currentFrequency = 116.54; // Adjust this to match your range

  for (int i = 0; i < numNotes; i++) {
    musicalNotes = currentFrequency;
    currentFrequency *= ratio;
  }
}

int hzToMidiNote(float frequency) {
  if (frequency <= 0.0) {
    return 0; // Return 0 for an invalid frequency
  }

  float note = 12.0 * log2(frequency / 440.0) + 69.0;
  return int(note + 0.5);
}

void sendMIDINoteOn(int note, int velocity) {
  if (note >= 0 && note <= 127) {
    usbMIDI.sendNoteOn(note, velocity, channel);
  }
}

void sendMIDINoteOff(int note) {
  if (note >= 0 && note <= 127) {
    usbMIDI.sendNoteOff(note, 0, channel);
  }
}
 
Last edited:
What if instead of using a delay (blocking) you set a threshold with the RMS level to avoid the re triggering of the detection?. If the signal falls below certain value or the note is different, you reset the detection
 
What if instead of using a delay (blocking) you set a threshold with the RMS level to avoid the re triggering of the detection?. If the signal falls below certain value or the note is different, you reset the detection
Hello,
Thanks for the suggestion, I tried replacing the delay with the rms read, but it seemed to add some additional latency to send the midi note in comparison to the delay. The less latency the better. I am trying to modify the code to make it have less lines also..
 
Woah!
I've been working on a MIDI Cello project for the last while and finally switched from using an uncooperative Sonuus G2M v3 to trying to DIY an Audio to MIDI device. I read up a bunch on the Audio library and had a Teensy 3.2 and Audio Shield hanging around from a previous (failed) project years ago.

I basically created the same Yin + Peak ---> Frequency ---> Rounded Frequency code last night, with the exception of:
  • using a botched NoteOff detection debounce
  • manually writing the notes array instead of generating it
  • using Peak instead of RMS
  • no experimentation with quickly changing notes
  • no MIDI stuff yet
I've got a lot of noise coming into the audio shield's mic with both a clip-on piezo pickup and a flat guitar pickup that I used in this 3D printing project.

I lack confidence in my programming skills and it's been a while since I've touched the Arduino IDE (first time using IDE 2.0 lol), but here's what I've come up with:
#include <Audio.h> #include <Wire.h> #include <SPI.h> #include <SerialFlash.h> #include <math.h> // float peakVal = 0; float noteFreq; bool playing = false; // A note is either on or off float peakCutoff = 0.15; //Stays at lower threshold until a played note decays to that threhold where i jumps up briefly to prevent retriggering. Basically has debounce. float roundedNote; int playingCutoffCounter = 0; // AudioInputI2S audioInput; AudioFilterStateVariable filter; AudioAmplifier amp; AudioAnalyzePeak peak; AudioAnalyzeNoteFrequency yin; // AudioConnection patchCord1(audioInput,0, filter,0); AudioConnection patchCord2(filter, 2, amp,0); //highpass, "remove DC and low frequencies" //AudioConnection patchCord3(amp, 0 , fft, 0); AudioConnection patchCord4(amp, 0 , peak, 0); AudioConnection patchCord5(amp, 0, yin, 0); AudioControlSGTL5000 audioShield; // const float notes[] = { ///C C# D Eb E F F# G G# A Bb B 65.41, 69.30, 73.42, 77.78, 82.41, 87.31, 92.50, 98.00, 103.8, 110.0, 116.5, 123.5, 130.8, 138.6, 146.8, 155.6, 164.8, 174.6, 185.0, 196.0, 207.7, 220.0, 233.1, 246.9, 261.6, 277.2, 293.7, 311.1, 329.6, 349.2, 370.0, 392.0, 415.3, 440.0, 466.2, 493.9, 523.3, 554.4, 587.3, 622.3, 659.3, 698.5, 740.0, 784.0, 830.6, 880.0, 932.3, 987.8, 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, }; float noteIndexCalc; int noteIndex; // float round_note(float givenNote) { noteIndexCalc = (12.0 * log(givenNote * 100.0 / 6541.0)) / (log(2)); //Inverse of Freq=BaseFreq*2^(NotesAway/12), Base Frequency is 65.41 [C2] noteIndex = (int) (noteIndexCalc + 0.5); //Round without round() Serial.print("Given "); Serial.print(givenNote); Serial.print("Hz --> place no. "); Serial.print(noteIndex); Serial.println(); return notes[noteIndex]; } //---------------------------------------------------------------------------- void setup() { AudioMemory(30); audioShield.enable(); audioShield.inputSelect(AUDIO_INPUT_MIC); Serial.begin(9600); yin.begin(.15); // yin threhold, the amount of allowed uncertainty amp.gain(1.0); //Keep this for experimenting later. Might produce clipping. filter.frequency(50); //Start at 20, get closer to 60... } //---------------------------------------------------------------------------- void loop() { if(peak.available()){ peakVal = peak.read(); if (peakVal > peakCutoff) { //Idle around noise threshold if (!playing) {Serial.print("!"); Serial.println(peakVal);} if (yin.probability() > 0.80f) { //Faster response with lower probabilty value noteFreq = yin.read(); if (noteFreq > 60 && noteFreq < 2100) { //Cello 65.4 Hz [C2] to 2093 Hz [C7] if (!playing) { roundedNote = round_note(noteFreq); Serial.print("Note: "); Serial.print(noteFreq); Serial.print(" Round: "); Serial.print(roundedNote); Serial.println(); } playing = true; } } } else { if (playing && playingCutoffCounter >= 5) { //An arbirary X 'cycle' delay before the peak value turns off the note and the noise takes over playing = false; playingCutoffCounter = 0; peakCutoff = 0.15f; Serial.println("Note Off"); } else if (playing) { Serial.println("/"); peakCutoff = 0.40f; //temporarily make the trigger threshold slightly higher. Like debounce playingCutoffCounter++; } } } }

Strangely, this code is producing much lower signals than what's actually being played. Not sure why that is.

I can't help but wonder if using a Teensy 4.0 instead of a 3.2 would reduce the amount of delay.

I don't have much else to advance the convo here, but here are some other resources I've been experimenting with.
Not sure if this is any better but it does make for a Yin alternative:
https://forum.pjrc.com/index.php?th...-interpolator-for-frequency-estimation.36358/
I originally got on the Teensy tangent because of this project:
https://www.pjrc.com/guitar-tuner-using-teensy-audio-library/
This project was where my code started from.


Please let me know if you have any breakthroughs with this project :)
 
What if instead of using a delay (blocking) you set a threshold with the RMS level to avoid the re triggering of the detection?. If the signal falls below certain value or the note is different, you reset the detection
Can you provide example?
 
Woah!
I've been working on a MIDI Cello project for the last while and finally switched from using an uncooperative Sonuus G2M v3 to trying to DIY an Audio to MIDI device. I read up a bunch on the Audio library and had a Teensy 3.2 and Audio Shield hanging around from a previous (failed) project years ago.

I basically created the same Yin + Peak ---> Frequency ---> Rounded Frequency code last night, with the exception of:
  • using a botched NoteOff detection debounce
  • manually writing the notes array instead of generating it
  • using Peak instead of RMS
  • no experimentation with quickly changing notes
  • no MIDI stuff yet
I've got a lot of noise coming into the audio shield's mic with both a clip-on piezo pickup and a flat guitar pickup that I used in this 3D printing project.

I lack confidence in my programming skills and it's been a while since I've touched the Arduino IDE (first time using IDE 2.0 lol), but here's what I've come up with:
#include <Audio.h> #include <Wire.h> #include <SPI.h> #include <SerialFlash.h> #include <math.h> // float peakVal = 0; float noteFreq; bool playing = false; // A note is either on or off float peakCutoff = 0.15; //Stays at lower threshold until a played note decays to that threhold where i jumps up briefly to prevent retriggering. Basically has debounce. float roundedNote; int playingCutoffCounter = 0; // AudioInputI2S audioInput; AudioFilterStateVariable filter; AudioAmplifier amp; AudioAnalyzePeak peak; AudioAnalyzeNoteFrequency yin; // AudioConnection patchCord1(audioInput,0, filter,0); AudioConnection patchCord2(filter, 2, amp,0); //highpass, "remove DC and low frequencies" //AudioConnection patchCord3(amp, 0 , fft, 0); AudioConnection patchCord4(amp, 0 , peak, 0); AudioConnection patchCord5(amp, 0, yin, 0); AudioControlSGTL5000 audioShield; // const float notes[] = { ///C C# D Eb E F F# G G# A Bb B 65.41, 69.30, 73.42, 77.78, 82.41, 87.31, 92.50, 98.00, 103.8, 110.0, 116.5, 123.5, 130.8, 138.6, 146.8, 155.6, 164.8, 174.6, 185.0, 196.0, 207.7, 220.0, 233.1, 246.9, 261.6, 277.2, 293.7, 311.1, 329.6, 349.2, 370.0, 392.0, 415.3, 440.0, 466.2, 493.9, 523.3, 554.4, 587.3, 622.3, 659.3, 698.5, 740.0, 784.0, 830.6, 880.0, 932.3, 987.8, 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, }; float noteIndexCalc; int noteIndex; // float round_note(float givenNote) { noteIndexCalc = (12.0 * log(givenNote * 100.0 / 6541.0)) / (log(2)); //Inverse of Freq=BaseFreq*2^(NotesAway/12), Base Frequency is 65.41 [C2] noteIndex = (int) (noteIndexCalc + 0.5); //Round without round() Serial.print("Given "); Serial.print(givenNote); Serial.print("Hz --> place no. "); Serial.print(noteIndex); Serial.println(); return notes[noteIndex]; } //---------------------------------------------------------------------------- void setup() { AudioMemory(30); audioShield.enable(); audioShield.inputSelect(AUDIO_INPUT_MIC); Serial.begin(9600); yin.begin(.15); // yin threhold, the amount of allowed uncertainty amp.gain(1.0); //Keep this for experimenting later. Might produce clipping. filter.frequency(50); //Start at 20, get closer to 60... } //---------------------------------------------------------------------------- void loop() { if(peak.available()){ peakVal = peak.read(); if (peakVal > peakCutoff) { //Idle around noise threshold if (!playing) {Serial.print("!"); Serial.println(peakVal);} if (yin.probability() > 0.80f) { //Faster response with lower probabilty value noteFreq = yin.read(); if (noteFreq > 60 && noteFreq < 2100) { //Cello 65.4 Hz [C2] to 2093 Hz [C7] if (!playing) { roundedNote = round_note(noteFreq); Serial.print("Note: "); Serial.print(noteFreq); Serial.print(" Round: "); Serial.print(roundedNote); Serial.println(); } playing = true; } } } else { if (playing && playingCutoffCounter >= 5) { //An arbirary X 'cycle' delay before the peak value turns off the note and the noise takes over playing = false; playingCutoffCounter = 0; peakCutoff = 0.15f; Serial.println("Note Off"); } else if (playing) { Serial.println("/"); peakCutoff = 0.40f; //temporarily make the trigger threshold slightly higher. Like debounce playingCutoffCounter++; } } } }

Strangely, this code is producing much lower signals than what's actually being played. Not sure why that is.

I can't help but wonder if using a Teensy 4.0 instead of a 3.2 would reduce the amount of delay.

I don't have much else to advance the convo here, but here are some other resources I've been experimenting with.
Not sure if this is any better but it does make for a Yin alternative:
https://forum.pjrc.com/index.php?th...-interpolator-for-frequency-estimation.36358/
I originally got on the Teensy tangent because of this project:
https://www.pjrc.com/guitar-tuner-using-teensy-audio-library/
This project was where my code started from.


Please let me know if you have any breakthroughs with this project :)
Hello, I am not by any means a Teensy pro or intermediate for that matter, but as far as getting the teensy to detect the fundamental and rms amplitude, convert it to midi value and midi velocity and send the midi note via usb it seems to work ok, but as you mention there is a bit of latency involved. This either has to do with the processing power of the teensy or the amount of lines / efficiency of the code. The latter seems to me as the culprit as the Teensy 4.0 features an ARM Cortex-M7 processor at 600MHz, with a NXP iMXRT1062 chip, which is the fastest microcontroller available today according to some sites.

Thanks.
 
Latency is due to the sample rate and audio block size. 128 samples per block at 44.1kSPS means about 3ms latency irrespective of processor performance. The audio library handles by default blocks of 128 samples everywhere. This can only be changed at compile time - shorter blocksize will mean less latency.
 
Latency is due to the sample rate and audio block size. 128 samples per block at 44.1kSPS means about 3ms latency irrespective of processor performance. The audio library handles by default blocks of 128 samples everywhere. This can only be changed at compile time - shorter blocksize will mean less latency.
Hi Mark,

How many samples does it take for the frequency detect YIN algorithm to determine and output a fundamental? I assume there are a few factors such as the confidence value, or other libraries being used that contribute the most toward latency between input to output?
 
Back
Top