Midi Time Code Reader/Generator with Arduino

This is a piece of code for Arduino to decode MidiTimeCode.

It can be usefull for some people that wants to deal with MTC.

/*


Midi Time Code Reader.

For information about MTC : http://home.roadrunner.com/~jgglatt/tech/mtc.htm


*/

#include <stdio.h>    // sprintf belongs to this lib

enum { F24 = 0, F25 = 2, F30DF = 4, F30 = 6 }; // Frames type

byte h, m, s, f;      // hour, minutes, secondes, frames time code
byte frameType;       // speed of MTC : 24fps / 25fps / 30 drop frame / 30 fps

byte buf[64];         // Serial Midi Buffer
byte tc[20];          // array to old timecode

void setup()
{
  Serial3.begin(31250);    // I use Arduino Mega TX3/RX3 serial / Modify for other arduino hardware
  Serial.begin(115200);    // To trace and display timeocde
}

void loop()
{
  int bytestoRead;

  bytestoRead = Serial3.available();

  if (bytestoRead>0)
  {
    for (int i=0; i<bytestoRead; i++)
    {
      buf[i]=Serial3.read();

      if (buf[i]!=0xF1)
      {
        int indice;

        indice = (buf[i] & 0xf0)>>4;

        if (indice>7)
          continue;

        tc[indice]= buf[i] & 0x0f;

        if (indice==7)
        {
          char toDisp[30];
          char typeStr[10];
          byte frameType;
          
          frameType = tc[7] & 0x06;

          h = (tc[7] & 0x01)*16 + tc[6];
          m = tc[5]*16+tc[4];
          s = tc[3]*16+tc[2];
          f = tc[1]*16+tc[0];

          if (h>23)  h = 23;
          if (m>59)  m = 59;
          if (s>59)  s = 59;
          if (f>30)  f = 30;

          switch(frameType)
          {
            case F24:
              strcpy(typeStr, "24 fps"); break;
            case F25:
              strcpy(typeStr, "25 fps"); break;
            case F30DF:
              strcpy(typeStr, "30df fps"); break;
            case F30:
              strcpy(typeStr, "30 fps"); break;  
            default:
              strcpy(typeStr, "Error"); break;
          }

          sprintf(toDisp, "TC %02d:%02d:%02d.%02d / %s", h, m, s, f, typeStr);
          Serial.println(toDisp);
        }
      }  

    }
  }
  
  delay(50);  // Save time for other things. Be carefull, every 2 frames as MTC works is around 8/100 of seconde ! 
  
}

Hope it can help few of you.

Grag

And now, MTC generator.

I used a timing library : Arduino Playground - TimedAction Library

Easy code to modify, or adapt to other case. Accuracy is'nt good (timing stability along hours, must be implemented in other way with interrupts or else...)

#include <PCD8544.h>
#include <stdio.h>

#include <TimedAction.h>

volatile byte h, m, s, f;

//this initializes a TimedAction class that will send MTC quarter frame every 10 milliseconds so 80 milliseconde for 
// an 'entire' timecode.

TimedAction timedAction = TimedAction(10,GenMtc);

//pin / state variables
const byte ledPin = 13;
boolean ledState = false;
static PCD8544 lcd;     // 5110 Nokia Lcd
char toDisp[14*6];      // 6 lines of 14 characters.


void setup()
{
  lcd.begin(84,48);
  pinMode(ledPin,OUTPUT);
  digitalWrite(ledPin,ledState);
  Serial3.begin(31250);               // use of TX3/RX3 of Arduino Mega
  h = m = s = f = 0;
}

void loop()
{
  timedAction.check();
}

void GenMtc()
{
  static byte indice=0;
  static byte toSend;

  switch(indice)
  {
  case 0: 
    toSend = ( 0x00 + (f & 0xF));         
    break;

  case 1: 
    toSend = ( 0x10 + ((f & 0xF0)/16));   
    break;

  case 2: 
    toSend = ( 0x20 + (s & 0xF));         
    break;

  case 3: 
    toSend = ( 0x30 + ((s & 0xF0)/16));   
    break;

  case 4: 
    toSend = ( 0x40 + (m & 0xF));         
    break;

  case 5: 
    toSend = ( 0x50 + ((m & 0xF0)/16));   
    break;

  case 6: 
    toSend = ( 0x60 + (h & 0xF));         
    break;

  case 7: 
    toSend = ( 0x72 + ((h & 0xF0)/16));   // 0x70 = 24 fps // 0x72 = 25 fps // 0x74 = 30df fps // 0x76 = 30 fps
    break;
  }
  
  Serial3.write(0xF1);
  Serial3.write(toSend);
  
  if (++indice>7)
  {
    f+=2;
    if (f>24)    // I'm French, so from standard is 25 frames/second
    {
      s++;
      f-=25;
    }
    if (s>59)
    {
      m++;
      s-=60;
    }
    if (m>59)
    {
      h++;
      m-=60;
    }
    ledState = ledState ? false:true;
    digitalWrite(ledPin,ledState);
    sprintf(toDisp, "%02d:%02d:%02d.%02d", h, m, s, f);
    lcd.setCursor(0,0);
    lcd.print(toDisp);
   
    indice=0;
  }
}

Thank you so much! It's a very useful sketch! I work with different kind of time code and any information helps me to explorer this area. I've uploaded "Midi Time Code Reader" sketch. It's working very good! Although I have one question. Why only even frames are detected? Is it possible to detect every frame? I want to crate "MTC to LTC Arduino converter" and I need to convert every incoming MIDI frame.

About the MTC generator.
1st of all a good idea, but far from perfect.
there are 2 mager mistakes in this project.

1st the usage of timedAction is not a solution in this case.
2nd the way to dice the seconds is improper.

Before you bother longer, please let me explain.

TimedAction(10,GenMtc);

The GenMTC() will be called every '10ms'

if (++indice>7)

after if (++indice>7) we have '80ms' by 8 times calling the GenMTC()

f+=2;
if (f>24) // I'm not French I'm German :slight_smile: but the standard is also 25 frames/second
{
s++;
f-=25;
}

this above is a real bugg,
I don't mean the f+=2, this is O.K. and might be helpful for a smoother display reading
What I mean is the way you'r calculating the seconds.

In this case the seconds are a result of counting the 25 frames in 13 steps up.
So we have the 80ms now 13 times and this makes 1040 ms a second. And this is wrong.
But more worse if we're using another fps (frame rate)

doing the example with 30fps, then we have 16 f++ steps, this makes 1280ms a second.

Yuk!

And here comes the solution:

  1. We don't need the TimedAction.
    but before we start the Generator, we must store the microseconds into a variable:
    unsigned long timer = micros();

  2. We must calculate the microseconds per Frame, those of course are different depending on fps
    24fps means that each frame duration = 1000000*(1/24) microseconds
    25fps means that each frame duration = 1000000*(1/25) ...
    and exactly this is the time we have to wate beforefore we increase the f variable.
    We need some variable casting to do this jop as perfect as possible by using:
    unsingned long for the microseconds per frame variable
    and float to casting the fps value
    in other words:
    unsigned long microsPerFrame = (1 / (float)fps) * 1000000; // (fps 24,25,29 or 30)
    and the result is the time we have to wait, before we increment f
    This is because (microsPerFrame * f == one second)

And here comes the way, we wait before we increase the f variable:

while (micros() < (timer + microsPerFrame)); // wait 1/fps seconds or 1000000 * (1/(float)fps) micros()
timer = micros(); // reset the timer variable for the next (f increasing) cycle
f += 1; // and now, after 1/fps seconds, increase f

I hope this little turtorial will help you to get a responsable MTC Generator

cheers Rolf Meurer (the KRAFTWERK electronic engineer)

Grag38:
This is a piece of code for Arduino to decode MidiTimeCode.

  ...

delay(50);  // Save time for other things. Be carefull, every 2 frames as MTC works is around 8/100 of seconde
  ...




No, calling delay() will prevent other things running and may lose MIDI packets too. Don't use delay()