Audio Library for Linear Timecode (LTC)?

3.6
As you probably already noticed, I'm not a high level programmer. Still have to learn a lot.
What is the purpose of pin 0 in your example. ( syncpin) i left it unconnected.
 
I have a working code for syncing the input to the output. (like a tentikal) Pulling pin 2 down to ground will set the sync.
Here is my code. (could be written better I think)

/*
Linear Timecode generation and decoding combined

Please modify as needed

(c) Frank B, 2020
(pls keep copyright-info)

To enable decoding:
#define USE_LTC_INPUT
and connect pin1 to pinA2 (=pin 16) !!!! I see no use to do this ????

PIN 2 : if set low it will sync incoming TC to the output TC . Then you can disconnect input TC and output will continue

Licence: MIT
*/
bool insync=0;
int incominghours=0;
int incomingminutes=0;
int incomingseconds=0;
int incomingframes=0;

#define USE_LTC_INPUT //comment-out for generation only
float fps = 25;
const int ltcPin = 1;
const int syncPin = 0;

#include "TimeLib.h"

#ifdef USE_LTC_INPUT
/*
Connect ltc
*/
#include <Audio.h>
#include <analyze_ltc.h>
AudioInputAnalog adc1;
AudioAnalyzeLTC ltc1;
AudioConnection patchCord2(adc1, 0, ltc1, 0);

#else
struct ltcframe_t {
uint64_t data;
uint16_t sync;
};
#endif

IntervalTimer ltcTimer;
IntervalTimer fpsTimer;

volatile int clkCnt = 0;
ltcframe_t ltc;
float ltcTimer_freq;

time_t getTeensy3Time()
{
return Teensy3Clock.get();
}

void genLtc() {
static int state = 0;

if (clkCnt++ >= 2 * 80) {
//ltcTimer.end();
clkCnt = 0;
return;
}

int bitval;
int bitcount = clkCnt / 2;

ltcframe_t ltct;
ltct = ltc;

if (bitcount < 64) {
bitval = (int) (ltct.data >> bitcount) & 0x01;
} else {
bitval = (int) (ltct.sync >> (bitcount - 64)) & 0x01; //backward
}

if ( (bitval == 1) || ( (bitval == 0) && (clkCnt & 0x01) ) == 0) {
state = !state; // toggle state
digitalWriteFast(ltcPin, state);
}

}

//https://www.geeksforgeeks.org/program-to-find-parity/
inline
int getParity(uint64_t n)
{
int parity = 0;
while (n)
{
parity = !parity;
n = n & (n - 1);
}
return parity & 0x01;
}


/*

Gets called by rising edge of syncPin
*/
void startLtc() {

ltcTimer.begin(genLtc, ltcTimer_freq);
ltcTimer.priority(0);

int t, t10;
uint64_t data = ltc.data;
clkCnt = 0;

//get frame number:
t = ((data >> 8) & 0x03) * 10 + (data & 0x0f);

//zero out ltc data, leave d+c flags and user-bits untouched:
data &= 0xf0f0f0f0f0f0fcf0ULL;
//data = 0;
//inc frame-number:
//TODO: realize "drop frame numbering" (when D-Flag is set)
t++;
if (t >= (int) fps) t = 0;
//set frame number:
t10 = t / 10;
data |= (t10 & 0x03) << 8;
data |= ((t - t10 * 10)) << 0;
printTCout();
Serial.printf("%02d\n", t);

//set time:
t = second();
t10 = t / 10;
data |= (t10 & 0x07) << 24; //Seconds tens
data |= ((t - t10 * 10) & 0x0f) << 16;

t = minute();
t10 = t / 10;
data |= (uint64_t)(t10 & 0x07) << 40; //minute tens
data |= (uint64_t)((t - t10 * 10) & 0x0f) << 32;

t = hour();
t10 = t / 10;
data |= (uint64_t)(t10 & 0x03) << 56; //hour tens
data |= (uint64_t)((t - t10 * 10) & 0x0f) << 48;

//set parity:
int parity = (!getParity(data)) & 0x01;
if ((int) fps == 25) {
data |= (uint64_t) parity << 59;
} else {
data |= (uint64_t) parity << 27;
}

ltc.data = data;

}

void initLtcData()
{
ltc.sync = 0xBFFC;
ltc.data = (uint64_t) 0; //<- place your initial data here
}

void genFpsSync() {
static int state = 0;
state = !state; // toggle state
digitalWriteFast(syncPin, state);
}


void setup() {

Serial.begin(115200);
delay(200);
#ifdef USE_LTC_INPUT
AudioMemory(4);
#endif
setSyncProvider(getTeensy3Time);
pinMode(syncPin, OUTPUT);
pinMode(ltcPin, OUTPUT);
pinMode(2, INPUT_PULLUP);
ltcTimer_freq = (1.0f / (2 * 80 * (fps ))) * 1000000.0f - 0.125f;// -0.125: make it a tiny bit faster than needed to allow syncing
initLtcData();
//set frame number to last frame to force roll-over with new second()
ltc.data &= 0xf0f0f0f0f0f0fcf0ULL; //delete all dynamic data in frame
int t, t10;
t = fps;
t10 = t / 10;
ltc.data |= (t10 & 0x03) << 8;
ltc.data |= ((t - t10 * 10));
//now wait for seconds-change
int secs = second();
while (secs == second()) {;}

//start timer and pin-interrupt:
fpsTimer.begin(&genFpsSync, (1.0f / (2 * fps)) * 1000000.0f);
fpsTimer.priority(0);
attachInterrupt(digitalPinToInterrupt(syncPin), &startLtc, RISING);
NVIC_SET_PRIORITY(88, 0); //set GPIO-INT-Priority for Pin 1

}

void loop() {
if (ltc1.available()) {
ltcframe_t ltcframe = ltc1.read();
incominghours=ltc1.hour(&ltcframe);
incomingminutes=ltc1.minute(&ltcframe);
incomingseconds=ltc1.second(&ltcframe);
incomingframes=ltc1.frame(&ltcframe);
Serial.printf("TC in : %02d:%02d:%02d.%02d\n\n", ltc1.hour(&ltcframe), ltc1.minute(&ltcframe), ltc1.second(&ltcframe), ltc1.frame(&ltcframe));
}

bool inpin = digitalRead(2);
if (inpin==0 && insync==0){
if (incomingframes==0){
detachInterrupt(digitalPinToInterrupt(syncPin));
fpsTimer.end();
setTime(incominghours,incomingminutes,incomingseconds, 01, 01, 2022);
Teensy3Clock.set(now());
ltcTimer_freq = (1.0f / (2 * 80 * (fps ))) * 1000000.0f - 0.125f;// -0.125: make it a tiny bit faster than needed to allow syncing
initLtcData();
//set frame number to last frame to force roll-over with new second()
ltc.data &= 0xf0f0f0f0f0f0fcf0ULL; //delete all dynamic data in frame
int t, t10;
t = fps;
t10 = t / 10;
ltc.data |= (t10 & 0x03) << 8;
ltc.data |= ((t - t10 * 10));
//now wait for seconds-change
int secs = second();
while (secs == second()) {;}
//start timer and pin-interrupt:
fpsTimer.begin(&genFpsSync, (1.0f / (2 * fps)) * 1000000.0f);
fpsTimer.priority(0);
attachInterrupt(digitalPinToInterrupt(syncPin), &startLtc, RISING);
NVIC_SET_PRIORITY(88, 0); //set GPIO-INT-Priority for Pin 1
insync=1;

}
}
}

void printTCout(){
Serial.printf("TCout : %02d:%02d:%02d:", hour(), minute(), second());
}
 
Code:
/*
Linear Timecode generation and decoding combined

Please modify as needed

(c) Frank B, 2020
(pls keep copyright-info)

To enable decoding:
#define USE_LTC_INPUT
and connect pin1 to pinA2 (=pin 16) !!!! I see no use to do this ????

PIN 2 : if set low it will sync incoming TC to the output TC . Then you can disconnect input TC and output will continue

Licence: MIT
*/
bool insync = 0;
int incominghours = 0;
int incomingminutes = 0;
int incomingseconds = 0;
int incomingframes = 0;

#define USE_LTC_INPUT //comment-out for generation only
float fps = 25;
const int ltcPin = 1;
const int syncPin = 0;

#include "TimeLib.h"

#ifdef USE_LTC_INPUT
/*
Connect ltc
*/
#include <Audio.h>
#include <analyze_ltc.h>
AudioInputAnalog adc1;
AudioAnalyzeLTC ltc1;
AudioConnection patchCord2(adc1, 0, ltc1, 0);

#else
struct ltcframe_t {
	uint64_t data;
	uint16_t sync;
};
#endif

IntervalTimer ltcTimer;
IntervalTimer fpsTimer;

volatile int clkCnt = 0;
ltcframe_t ltc;
float ltcTimer_freq;

time_t getTeensy3Time()
{
	return Teensy3Clock.get();
}

void genLtc() {
	static int state = 0;

	if (clkCnt++ >= 2 * 80) {
		//ltcTimer.end();
		clkCnt = 0;
		return;
	}

	int bitval;
	int bitcount = clkCnt / 2;

	ltcframe_t ltct;
	ltct = ltc;

	if (bitcount < 64) {
		bitval = (int)(ltct.data >> bitcount) & 0x01;
	}
	else {
		bitval = (int)(ltct.sync >> (bitcount - 64)) & 0x01; //backward
	}

	if ((bitval == 1) || ((bitval == 0) && (clkCnt & 0x01)) == 0) {
		state = !state; // toggle state
		digitalWriteFast(ltcPin, state);
	}

}

//https://www.geeksforgeeks.org/program-to-find-parity/
inline
int getParity(uint64_t n)
{
	int parity = 0;
	while (n)
	{
		parity = !parity;
		n = n & (n - 1);
	}
	return parity & 0x01;
}


/*

Gets called by rising edge of syncPin
*/
void startLtc() {

	ltcTimer.begin(genLtc, ltcTimer_freq);
	ltcTimer.priority(0);

	int t, t10;
	uint64_t data = ltc.data;
	clkCnt = 0;

	//get frame number:
	t = ((data >> 8) & 0x03) * 10 + (data & 0x0f);

	//zero out ltc data, leave d+c flags and user-bits untouched:
	data &= 0xf0f0f0f0f0f0fcf0ULL;
	//data = 0;
	//inc frame-number:
	//TODO: realize "drop frame numbering" (when D-Flag is set)
	t++;
	if (t >= (int)fps) t = 0;
	//set frame number:
	t10 = t / 10;
	data |= (t10 & 0x03) << 8;
	data |= ((t - t10 * 10)) << 0;
	printTCout();
	Serial.printf("%02d\n", t);

	//set time:
	t = second();
	t10 = t / 10;
	data |= (t10 & 0x07) << 24; //Seconds tens
	data |= ((t - t10 * 10) & 0x0f) << 16;

	t = minute();
	t10 = t / 10;
	data |= (uint64_t)(t10 & 0x07) << 40; //minute tens
	data |= (uint64_t)((t - t10 * 10) & 0x0f) << 32;

	t = hour();
	t10 = t / 10;
	data |= (uint64_t)(t10 & 0x03) << 56; //hour tens
	data |= (uint64_t)((t - t10 * 10) & 0x0f) << 48;

	//set parity:
	int parity = (!getParity(data)) & 0x01;
	if ((int)fps == 25) {
		data |= (uint64_t)parity << 59;
	}
	else {
		data |= (uint64_t)parity << 27;
	}

	ltc.data = data;

}

void initLtcData()
{
	ltc.sync = 0xBFFC;
	ltc.data = (uint64_t)0; //<- place your initial data here
}

void genFpsSync() {
	static int state = 0;
	state = !state; // toggle state
	digitalWriteFast(syncPin, state);
}


void setup() {

	Serial.begin(115200);
	delay(200);
#ifdef USE_LTC_INPUT
	AudioMemory(4);
#endif
	setSyncProvider(getTeensy3Time);
	pinMode(syncPin, OUTPUT);
	pinMode(ltcPin, OUTPUT);
	pinMode(2, INPUT_PULLUP);
	ltcTimer_freq = (1.0f / (2 * 80 * (fps))) * 1000000.0f - 0.125f;// -0.125: make it a tiny bit faster than needed to allow syncing
	initLtcData();
	//set frame number to last frame to force roll-over with new second()
	ltc.data &= 0xf0f0f0f0f0f0fcf0ULL; //delete all dynamic data in frame
	int t, t10;
	t = fps;
	t10 = t / 10;
	ltc.data |= (t10 & 0x03) << 8;
	ltc.data |= ((t - t10 * 10));
	//now wait for seconds-change
	int secs = second();
	while (secs == second()) { ; }

	//start timer and pin-interrupt:
	fpsTimer.begin(&genFpsSync, (1.0f / (2 * fps)) * 1000000.0f);
	fpsTimer.priority(0);
	attachInterrupt(digitalPinToInterrupt(syncPin), &startLtc, RISING);
	NVIC_SET_PRIORITY(88, 0); //set GPIO-INT-Priority for Pin 1

}

void loop() {
	if (ltc1.available()) {
		ltcframe_t ltcframe = ltc1.read();
		incominghours = ltc1.hour(&ltcframe);
		incomingminutes = ltc1.minute(&ltcframe);
		incomingseconds = ltc1.second(&ltcframe);
		incomingframes = ltc1.frame(&ltcframe);
		Serial.printf("TC in : %02d:%02d:%02d.%02d\n\n", ltc1.hour(&ltcframe), ltc1.minute(&ltcframe), ltc1.second(&ltcframe), ltc1.frame(&ltcframe));
	}

	bool inpin = digitalRead(2);
	if (inpin == 0 && insync == 0) {
		if (incomingframes == 0) {
			detachInterrupt(digitalPinToInterrupt(syncPin));
			fpsTimer.end();
			setTime(incominghours, incomingminutes, incomingseco nds, 01, 01, 2022);
			Teensy3Clock.set(now());
			ltcTimer_freq = (1.0f / (2 * 80 * (fps))) * 1000000.0f - 0.125f;// -0.125: make it a tiny bit faster than needed to allow syncing
			initLtcData();
			//set frame number to last frame to force roll-over with new second()
			ltc.data &= 0xf0f0f0f0f0f0fcf0ULL; //delete all dynamic data in frame
			int t, t10;
			t = fps;
			t10 = t / 10;
			ltc.data |= (t10 & 0x03) << 8;
			ltc.data |= ((t - t10 * 10));
			//now wait for seconds-change
			int secs = second();
			while (secs == second()) { ; }
			//start timer and pin-interrupt:
			fpsTimer.begin(&genFpsSync, (1.0f / (2 * fps)) * 1000000.0f);
			fpsTimer.priority(0);
			attachInterrupt(digitalPinToInterrupt(syncPin), &startLtc, RISING);
			NVIC_SET_PRIORITY(88, 0); //set GPIO-INT-Priority for Pin 1
			insync = 1;

		}
	}
}

void printTCout() {
	Serial.printf("TCout : %02d:%02d:%02d:", hour(), minute(), second());
}
Could you place your code between code tags using the # button.
It makes the code much more readable and therefore more liable to get some help.
 
I'm really new to forums. Could you explain a bit more how to do this? Is it a button somewhere on the forum, or do I add # before I paste the code?
 
New to this? You've been a member since Nov 2018.
When making a post, when you want to add some code, press the # button above.
Type or paste your code between the [ CODE][/CODE ] tags generated.
Alternatively type or paste your code, highlight it and then press the # button and your code will have the tags placed before and after your code.
 
I'm using Chrome on a windows10 machine. It does not show these options. (see screenshot) changed to Explorer, it shows them.
Screenshot.jpg
 
Still trying to figure out on how to get this to work on a Teensy4.0, because I need (S)RTC.
So far I found out that it has something to do with the attachInterrupt on the sync pin. That part won't start until I connect a lose wire to the sync-pin. Strange to start with, A pull up resistor will stop the interrupt. But with the loose wire the TC-FRAME rate is going nuts... It will do more then 20 times 25 frames loops per second.....
 
Commenting here rather than starting a new thread as it seems a good place, is it possible to have multiple ltc objects reading from different inputs? For example one reading usb channel 0 and another reading usb channel 1. I've been trying but cannot get it to function. When I connect usb1 channel 0 to an object ltc2 channel 0 neither of the ltc readouts work. This is the audio setup I'ver created, as soon as I uncomment the final line of the setup ltc1 stops reading any timecode

Code:
AudioInputUSB            usb1;           //xy=386,450
AudioAnalyzeLTC          ltc1;         //xy=556,396
AudioAnalyzeLTC          ltc2;         //xy=556,396
AudioOutputI2S           i2s1;           //xy=556,469
AudioConnection          patchCord1(usb1, 0, i2s1, 0);
AudioConnection          patchCord2(usb1, 1, i2s1, 1);
AudioConnection          patchCord3(usb1, 1, ltc1, 0);
AudioConnection          patchCord4(usb1, 0, ltc2, 0);
 
I just want to leave a big thank you for Frank B for adding LTC decoding to the audio library. We just successfully completed a fairly high stakes (for us) corporate event, where we had to trigger some radio modules off of a time code signal. Building on the example sketch it was a breeze to implement the functionality on a T3.2 with audio adapter, and, despite fairly lousy signal quality at the event, everything worked flawlessly.

Thank you!
 
The whole thing can also be implemented well with the RMT device of the esp32, which is actually intended for hardware IR.
Or with cleverly configured timers on the Teensys (and probably flexio on T4). Both RMT or timers would need even less CPU%.

Actually, the audio library is not really the way to go.
I'm Frank B
 
Last edited:
So I'm using the latest and greatest from git, and trying to make a Timecode Clock, as the ones that are out there are stupid expensive, and I've now replaced several and I figure if the LED Matrix Panel goes out it's less than $100 to replace 2 panels, so that's a lot better...
So, I started out with a SmartLED Shield V5 for Teensy 4, and did some testing with fonts and things, I added some Matrix Initialization code, in the Setup() function, so nothing crazy... for some reason I never get into the
Code:
if (ltc1.available()) {
loop. I'm sending LTC from another project that was based on the same Library / Example, with the exception of just sending whatever intial time I had set it to, and Pushing
C-like:
startLtc()
through a button, to turn it on, and then sending.

C-like:
void stopLtc(){ 
  ltcTimer.end();
  ltcTimer.priority(0);

  int t, t10;
  uint64_t data = ltc.data;
  clkCnt = 0;

  //get frame number:
  t = ((data >> 8) & 0x03) * 10 + (data & 0x0f);

  //zero out ltc data, leave  d+c flags and user-bits untouched:
  data &= 0xf0f0f0f0f0f0fcf0ULL;
  ltc.data = data;
}
Once I wanted it to stop, and that "Works" on the Teensy that's sending the TC, and I've verified that it does get read by things that can read LTC... I didn't check much if it would hiccup, but I felt it was jam syncing to the signal and I didn't know how steady it was.

So here we come to the LTC Clock Display...
I have Ground from the sender going to Ground on the receiver, and I have the Data Pin (0) of the outbound Teensy 4 going to Pin (0) on the Clock, and then Pin 1 with a wire connected to pin 16 on the clock.... (I have a pair of headphones in the middle of that so I can hear if I'm outputting signal or not) for some reason, the clock seems to be getting the same frame over and over again...

Code:
Start LTC Setup
Finished LTC Setup
00:00:00.10
21:18:53.14
21:18:53.14
21:18:53.14
21:18:53.14
21:18:53.14
21:18:53.14
21:18:53.14
21:18:53.14
21:18:53.14
21:18:53.14
21:18:53.14
21:18:53.14

And then if I stop and start the TC from the sender it jumps to a new value, and stays there...
I'm not certain if it's an encoding / decoding thing, or if I have somehow wired it wrong or the signal is too hot... I'm wondering if anybody has done something like this, where they were able to decode and get consistent data... Also if there's been any progress on things like 60fps drop or non drop and other more exotic time code formats? I use everything from 24-60drop...

P.S. I do now realize I am getting into the function (Otherwise I wouldn't get frames), but for whatever reason it's not getting updated quick enough, IDK.
 
Back
Top