Audio Library for Linear Timecode (LTC)?

I didn’t have to do too much just change this AudioInputUSB to this AudioInputAnalog and add the recommended external hardware. I don’t have one of the PJRC audio boards so I just used the built in DAC to capture the xlr output from my hardware.

The audio leveling code is already in there, but I didn’t do any tests specifically to test it. My hardware let’s me set the gain for the LTC output, but I just had it at the max level while I did my tests. I can do some more tests at different gains to make sure it works as expected though.
 
Ok, I got it running on my teensy 3.1. I used LTCreader on windows to output an LTC stream, then compared the serial port timecode with LTCreader's timecode. I also shot a slowmo video of it, and am seeing the same delay of 1 frame. This is at 23.976fps. I figure I can just subtract 1 frame from the incoming stream and then it should be in sync.

Very cool!

Now I need to get the ADC version working, which sounds like it requires some hardware.

-tom
 
That's basically what I followed just not the stereo version, I didn't have the same capacitor values so I used 100uf instead since that's what I have handy for DC blocking because my mixer uses them all over the place.
 
I go away for a week and this happens? Holy Moly!

Frank, wonderful work! Of course, now I may actually need to start working on some LTC projects that were way down on my backlog list...sigh.

Thank you again, and if (when) you decide to go after the encode, hopefully I'll be more on top of things and can help with the testing!

Josh
 
Frank - did you make any further progress on the frame rate calc? I am interested in that quite a bit. I'm still a bit away from building anything to test with, but I can try to compare with equipment that will show me frame rate as well.
 
Maybe do the encoding next week if nothing comes up. I didn't bother to calculate the frame rate anymore. Maybe just a bug or a weird effect with my setup (wav-audacity-pc-usb-teensy)
 
Ok, for fun I tried an online translator (deepl.com, amazing..) for the following:

I had some free minutes and thought about it a bit. Furthermore I read somewhere that the sync starts at the beginning of the next frame. So it does not seem wise to do the generation in the audio library as well. It is not designed for this kind of synchronization, and there is no sense in processing the data in an audio way before outputting it (a low pass would be advantageous for the square wave LTC signal, though) So I think a simple program that takes an external frame sync signal into account and then generates the square wave (LTC) signal with a timer would be better and simpler. You can adapt this to your own needs.

What do you think?
 
Do you mean outputting the LTC signal as a series of square waves generated via PWM with analog low-pass filtering as described here?
https://www.pjrc.com/teensy/td_pulse.html

Or do you mean to use the tone() function to generate the square waves?

I bought a DS3231 real time clock chip (adafruit DS3231 precision RTC breakout) and was hoping to use that to keep things in sync. I was thinking I'd set the RTC to match the incoming LTC timecode, and then use the RTC to trigger when each LTC frame (group of square waves) is played?

-Tom
 
Last edited:
In my research today I came across this article that explains in some detail about drop frame, non-drop frame, and what to do about 23.976 frame rate that we use the US for HD video.
https://blog.frame.io/2017/07/17/timecode-and-frame-rates/

My main takeaway is that there are situations when the timecode won't match up with the real world clock after an hour or so (like when shooting in 23.98, or shooting 29.97 non-drop frame) so if I use an RTC to keep things in sync, I better be aware of that, otherwise the timecode I'm generating would be wrong.

I'm thinking that I'll decode the framerate from the incoming LTC signal, and then use the accurate square wave output from the RTC to trigger the teensy to output the appropriate 80 bit LTC frame w/ a set of tone() functions on each frame.

On the LTC generate side- any idea how long (in millis?)each of the 80 bits of LTC square wave needs to be?
 
Last edited:
1/(FPS*80) = LTC Clock in Seconds. *1000 = millis..
But I'm not sure if this was your question.. and where exactly you want to use Tone() ?
 
Last edited:
I was thinking we could use the tone() function to generate the square waves that make up the LTC audio signal. If it was called 80 times per frame at the proper frequencies it might work?

I spent some time yesterday reading through the source code of the other open source LTC arduino library, and found a few items, all on this page: https://github.com/x42/libltc/blob/master/src/ltc.h

The ltc.h has some links to very well explained definitions of LTC signal: http://www.philrees.co.uk/articles/timecode.htm
and https://web.archive.org/web/20080704075553/http://www.barney-wol.net/time/timecode.html

In ltc.h
On line 790 they talk about setting the rise time of the square wave output to 40us +/- 10us.
Does a low pass filter change the rise time of a square wave? If so, then they are doing the low pass filtering in software?
"LTC signal should have a rise time of 40us +/- 10 us.
* by default the encoder honors this and low-pass filters
* the output depending on the sample-rate."
Though when I read through Phill Ree's website on the topic (linked at the top of this post) he says it's 25us +/-5us.

Low pass filters- I know very little about this, but came across this page that describes r/c waveforms and filtering. Are they describing a way to use a resistor and capacitor to change the rise time of a square wave?

It also looks like they are creating a buffer of audio of a single frame, then outputting and creating the next frame, and swapping buffers. (I think?) The guts happen in encoder.c where they build up the waveform for each frame and save it in the buffer.

-tom
 
Last edited:
I'd just add a imple RC Lowpass to the pin. Maybe this isn't even needed..


Today, after work, I wrote a short, simple program to generate the signal. It is completely untested, but looks OK on the DSO.
It does not much besides generating the signal. There are some open TODOs, but it shoud show a simple way to do it.

First, we need a frame-sync. Normally, you would use a external signal. To keep the hardware requirements for this simple demo low, a interval-timer generates it. The signal gets generated in genFpsSync().
You can switch easyly to an external frame-sync if you change the pin from OUTPUT to INPUT in setup() (and perhaps omit the now un-needed timer)

A pin-interrupt then starts a new frame. startLtc() gets called with a rising edge of frame-sync.
Here are some TODOs left for you :) Currently, all data including time and frame-number just set to zero. Then, the parity gets updated, and the main interval-timer (calls genLtc() ) which is responsible the generate the bitstream gets startet.
It runs a tiny little bit faster than needed to allow the syncing to frame-sync. Maybe you want to play with the hardcoded value 0.005 and try higher or lower values.

genLTC does the main work to generate the bitstream.

That's it. Short and simple. I hope it works and generates a valid signal (all with '0' at the moment, see above) - but it is completely untested and may have some problems.


It's meant as a start - if anybody adds the missing parts or fixes bug(s), I'll update this post and upload the program to Github to make it better available for others.
My request would be that you keep it simple and create a kind of demo that can be used by others as a basis for their own programs.

Edit: see post #70


pic_2_3.png
The sceenshot show the ltc-sync(blue) before frame-sync (yellow).
 
Last edited:
Oh wow. Thank you for writing that code.

I have many questions, which will show how little I understand about programming and electronics, so my apologies in advance.


1) Can you explain how the ltc.sync and ltc.data are toggling the ltcPin? How do you go from 2 integers to toggling a pin a bunch of times to make a square wave?
Looks like this happens at the end of genLtc()? Are you setting the output pins directly?

2) Is the time of each LTC frame set to 0 inside of initLtcData() (where the comment says //<- place your initial data here)?

3) Since ltc.data is an unsigned 64bit integer, does that mean I should set the time as a long list of numbers? (like 5hours 45mins 10sec 23frames would be 05451023?)
*oh, now that I'm reading it again, I think it needs to be in BCD, or binary coded decimal. So 05hrs would be 00000101, 45mins would be 01000101 and so on?

4) can you explain how you are zeroing out the ltc.data in void_startLtc()? what does f c U and L do in a bitwise operation?
 
Last edited:
Oh.. let's try it..
The uint64 ( ltc.data) represents the bits which are sent to to the pin. Before that, they need to be encoded in biphase mark code. This is, what in genLTC() happens.
It just encodes a 0 to 00 or 11, and a bit '1' to 01 or 10. It does this for all 64 data-bits and the 16 sync-bits. But genLTC encodes only one bit per call - so it needs to be called 80 (EDIT: 160 see "trick") times in a frame. The timing is done with a intervalTimer.
After 80 bits, getnLTC just stops this timer and resets it's internal counter back to zero. The internal counter counts up to 160 (clknum) - that a little trick, because the BCM may toggle twice per bit (when it is a '1') - so it just makes the coding easier.
Bitcount is just Clknum / 2 and represents the number 0..63 (or 0..15 for sync) of the current bit. Bitshifting ( i.e. bitval = (int) (ltct.data >> 63 - bitcount) & 0x01; //forward) then provides the value of the bit.

2) ist's just the data which is used when the progrm starts - it gets called in setup() only.
you have to add some code to the lines maked with " // TODO" to update the data for every frame.

3) oh.. you have to add some bit-shifting magic here.. maybe the others can help. Or maybe I can add it tomorrow.

4) UUL just means unsigned long long and tells the compiler we want 64 bits, (not the usual 32 ).

5) I hope my english is not too hard to understand...
 
I found this page that describes a performance optimized decimal to BCD converter for real time applications on arduino:
https://forum.arduino.cc/index.php?topic=185235.0

Looks like a good way to convert:

Code:
uint8_t dec2bcd4(uint8_t n)
{
  uint16_t a = n;
  byte b = (a*26) >> 8;  // b = (a*13) >> 7  was OK but slower!
  return  n + b*6;
}

it will work correctly till 2068, then it'll break.

Or this one, which is faster, but uses more memory.
Code:
uint8_t dec2bcd(uint8_t n)
{
 byte b = (n * 103) >> 10;
 return (b * 16 + n-(b*10));  
}
 
Faster or not.. don't know. The Teensy has assembler-instructions for division und is a 32 bit system, so i just took "/10".

Since nothing happened and I like to have usable code, I updated the example :eek:
Still untested. We may have to use "volatile" for the ltc frame...
 
Do you think the ltc frame needs to be volatile so it can be updated faster from the interrupt?

Yes, because of the usage in the interrupt - but not to be faster (it's slower)- "volatile" is needed to prevent the compiler from optimizing the variable away - the compiler might have wrong assumptions.
Here is my test-program:
The Teensy decodes its self-generated code. Connect Pin A2 (adc-input) to Pin 1(LTC output) with a wire.

EDIT: Code is here

Of course, this is not a perfect test - it should be tested with a 3rd-party decoder.
I have no otehr decoder, and this was the fastest way :)

This program ist not optimized much - you'll find many things that can be faster/better.
For testing, I used a Teensy 3.6 - but any T3.x will work. T4 at the moment has no AudioInputAnalog (it's WIP).

I'm pausing this now. I have a new project idea that will keep me busy for some time.

Edit: PLEASE post your version ! Thank you!!!
 
Last edited:
It's a fantastic start. I'm going to mess around with your code, test and report back.
I'll also try and get that external TXO to keep more accurate time when generating LTC.

Thanks for all your help, it was way more complicated than I'd imagined!

-Tom
 
I loaded your program to my teensy 3.2 but I'm not seeing data coming back from the serial port.
I tried setting the USB to serial, audio, and audio+midi+serial. It's probably something wrong with my PC configuration, though I have gotten your earlier version to work where it took audio input and sent the decoded timecode out the serial port.

Any ideas or suggestions? I'm running Arduino 1.8.1 on Windows 7. I uploaded the blink sketch and it worked, so I know at least that part is functioning.

-Tom
 
Back
Top