├── docs └── ltc.png ├── keywords.txt ├── LICENSE ├── README.md ├── examples ├── ltc_usb │ └── ltc_usb.ino └── ltc_gen │ └── ltc_gen.ino ├── analyze_ltc.cpp └── analyze_ltc.h /docs/ltc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrankBoesing/LinearTimecode-Decoder/HEAD/docs/ltc.png -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ltcframe_t KEYWORD1 2 | AudioAnalyzeLTC KEYWORD1 3 | available KEYWORD2 4 | read KEYWORD2 5 | hour KEYWORD2 6 | minute KEYWORD2 7 | second KEYWORD2 8 | frame KEYWORD2 9 | bit10 KEYWORD2 10 | bit11 KEYWORD2 11 | bit27 KEYWORD2 12 | bit43 KEYWORD2 13 | bit58 KEYWORD2 14 | bit59 KEYWORD2 15 | userdata KEYWORD2 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Frank Bösing 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LinearTimecode-Decoder 2 | LTC decoder for Teensy Audio Library 3 | 4 | 5 | see: https://forum.pjrc.com/threads/41584-Audio-Library-for-Linear-Timecode-(LTC) 6 | 7 | Linear (or Longitudinal) Timecode (LTC) is an encoding of SMPTE timecode data in an audio signal, as defined in SMPTE 12M specification. The audio signal is commonly recorded on a VTR track or other storage media. The bits are encoded using the biphase mark code (also known as FM): a 0 bit has a single transition at the start of the bit period. A 1 bit has two transitions, at the beginning and middle of the period. This encoding is self-clocking. Each frame is terminated by a 'sync word' which has a special predefined sync relationship with any video or film content. 8 | 9 | A special bit in the linear timecode frame, the biphase mark correction bit, ensures that there are an even number of AC transitions in each timecode frame. 10 | 11 | The sound of linear timecode is a jarring and distinctive noise and has been used as a sound-effects shorthand to imply telemetry or computers. 12 | 13 | ![](https://upload.wikimedia.org/wikipedia/commons/thumb/9/90/Manchester_encoding_both_conventions.svg/650px-Manchester_encoding_both_conventions.svg.png) 14 | ![](docs/ltc.png) 15 | from: https://en.wikipedia.org/wiki/Linear_timecode 16 | -------------------------------------------------------------------------------- /examples/ltc_usb/ltc_usb.ino: -------------------------------------------------------------------------------- 1 | /* Linear Timecode for Audio Library for Teensy 3.x / 4.x 2 | 3 | USB-Audio Example 4 | 5 | Copyright (c) 2019, Frank Bösing, f.boesing (at) gmx.de 6 | 7 | Development of this audio library was funded by PJRC.COM, LLC by sales of 8 | Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop 9 | open source software by purchasing Teensy or other PJRC products. 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice, development funding notice, and this permission 19 | notice shall be included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | THE SOFTWARE. 28 | */ 29 | 30 | /* 31 | 32 | https://forum.pjrc.com/threads/41584-Audio-Library-for-Linear-Timecode-(LTC) 33 | 34 | LTC example audio at: https://www.youtube.com/watch?v=uzje8fDyrgg 35 | 36 | Howto: Set USB-Type to USB-Audio, on PC: Play LTC Audio and set autio-output to use Teensy as audio-device. 37 | 38 | */ 39 | 40 | #include 41 | #include 42 | 43 | // GUItool: begin automatically generated code 44 | AudioInputUSB usb1; //xy=386,450 45 | AudioAnalyzeLTC ltc1; //xy=556,396 46 | AudioOutputI2S i2s1; //xy=556,469 47 | AudioConnection patchCord1(usb1, 0, i2s1, 0); 48 | AudioConnection patchCord2(usb1, 0, ltc1, 0); 49 | AudioConnection patchCord3(usb1, 1, i2s1, 1); 50 | // GUItool: end automatically generated code 51 | 52 | ltcframe_t ltcframe; 53 | 54 | void setup() { 55 | AudioMemory(12); 56 | } 57 | 58 | void loop() { 59 | if (ltc1.available()) { 60 | ltcframe = ltc1.read(); 61 | Serial.printf("%02d:%02d:%02d.%02d\n", ltc1.hour(<cframe), ltc1.minute(<cframe), ltc1.second(<cframe), ltc1.frame(<cframe)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /analyze_ltc.cpp: -------------------------------------------------------------------------------- 1 | /* Audio Library for Teensy 3.X 2 | Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com 3 | 4 | This Code: 5 | Copyright (c) 2019, Frank Bösing, f.boesing (at) gmx.de 6 | 7 | Please support PJRC's efforts to develop open source software by purchasing 8 | Teensy or other PJRC products. 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice, development funding notice, and this permission 18 | notice shall be included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | THE SOFTWARE. 27 | */ 28 | 29 | // https://forum.pjrc.com/threads/41584-Audio-Library-for-Linear-Timecode-(LTC) 30 | 31 | #include 32 | #include "analyze_ltc.h" 33 | 34 | inline void AudioAnalyzeLTC::decodeBitstream(unsigned newbit) { 35 | static ltcframe_t ltc; 36 | static int bitcounter = 0; 37 | static bool forward = true; 38 | static uint32_t lastts = 0; 39 | 40 | if (bitcounter < 64) { 41 | 42 | if (forward) 43 | ltc.data = (ltc.data >> 1) | ((uint64_t) newbit << 63); 44 | else 45 | ltc.data = (ltc.data << 1) | newbit; 46 | 47 | bitcounter++; 48 | 49 | } else { 50 | 51 | ltc.sync = (ltc.sync << 1) | newbit; 52 | 53 | if (ltc.sync == 0x3FFD) { 54 | ltc.timestampfirstedge = lastts; 55 | lastts = micros(); 56 | ltcframe = ltc; 57 | new_output = true; 58 | bitcounter = 0; 59 | forward = true; 60 | } else if (ltc.sync == 0xBFFC) { 61 | ltc.timestampfirstedge = lastts; 62 | lastts = micros(); 63 | ltcframe = ltc; 64 | new_output = true; 65 | bitcounter = 0; 66 | forward = false; 67 | } 68 | 69 | } 70 | } 71 | 72 | void AudioAnalyzeLTC::update(void) 73 | { 74 | audio_block_t *block; 75 | const int16_t *p, *end; 76 | 77 | block = receiveReadOnly(); 78 | if (!block) { 79 | return; 80 | } 81 | p = block->data; 82 | end = p + AUDIO_BLOCK_SAMPLES; 83 | 84 | static int clkcnt = 0, avclk = 0, newbit = 0; 85 | static bool clkstate = false, clkstatelast = false; 86 | int minsample = 32767, maxsample = -32768; 87 | 88 | do { 89 | int16_t sample = *p++; 90 | 91 | if (sample < minsample) minsample = sample; 92 | else if (sample > maxsample) maxsample = sample; 93 | 94 | clkstatelast = clkstate; 95 | clkstate = (sample > avsample); 96 | 97 | if (clkstate == clkstatelast) 98 | clkcnt++; 99 | else { 100 | 101 | if ( clkcnt < avclk ) { 102 | avclk = clkcnt * 3 / 2; 103 | if (++newbit == 2) { 104 | newbit = 0; 105 | decodeBitstream(1); 106 | } 107 | } else { 108 | 109 | avclk = (clkcnt + clkcnt / 2) / 2; 110 | newbit = 0; 111 | decodeBitstream(0); 112 | } 113 | clkcnt = 0; 114 | } 115 | } while (p < end); 116 | release(block); 117 | avsample = (maxsample + minsample) / 2; 118 | } 119 | -------------------------------------------------------------------------------- /analyze_ltc.h: -------------------------------------------------------------------------------- 1 | /* Audio Library for Teensy 3.X 2 | Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com 3 | 4 | This Code: 5 | Copyright (c) 2019, Frank Bösing, f.boesing (at) gmx.de 6 | 7 | Please support PJRC's efforts to develop open source software by purchasing 8 | Teensy or other PJRC products 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice, development funding notice, and this permission 18 | notice shall be included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | THE SOFTWARE. 27 | */ 28 | 29 | // https://forum.pjrc.com/threads/41584-Audio-Library-for-Linear-Timecode-(LTC) 30 | 31 | 32 | #ifndef analyze_ltc_h_ 33 | #define analyze_ltc_h_ 34 | 35 | #include "Arduino.h" 36 | #include "AudioStream.h" 37 | 38 | struct ltcframe_t { 39 | uint64_t data; 40 | uint16_t sync; 41 | uint32_t timestampfirstedge; 42 | }; 43 | 44 | class AudioAnalyzeLTC : public AudioStream 45 | { 46 | public: 47 | AudioAnalyzeLTC(void) : AudioStream(1, inputQueueArray) { 48 | new_output = false; 49 | } 50 | 51 | bool available(void) { 52 | __disable_irq(); 53 | bool flag = new_output; 54 | __enable_irq(); 55 | return flag; 56 | } 57 | 58 | ltcframe_t read(void) { 59 | __disable_irq(); 60 | ltcframe_t tempframe = ltcframe; 61 | new_output = false; 62 | __enable_irq(); 63 | return tempframe; 64 | } 65 | 66 | 67 | //Auxiliary functions: 68 | 69 | int hour(ltcframe_t * ltc) { 70 | return 10 * ((int) (ltc->data >> 56) & 0x03) + ((int) (ltc->data >> 48) & 0x0f); 71 | } 72 | int minute(ltcframe_t * ltc) { 73 | return 10 * ((int) (ltc->data >> 40) & 0x07) + ((int) (ltc->data >> 32) & 0x0f); 74 | } 75 | int second(ltcframe_t * ltc) { 76 | return 10 * ((int) (ltc->data >> 24) & 0x07) + ((int) (ltc->data >> 16) & 0x0f); 77 | } 78 | int frame(ltcframe_t * ltc) { 79 | return 10 * ((int) (ltc->data >> 8) & 0x03) + ((int) (ltc->data >> 0) & 0x0f); 80 | } 81 | bool bit10(ltcframe_t * ltc) { 82 | return (int) (ltc->data >> 10) & 0x01; 83 | } 84 | bool bit11(ltcframe_t * ltc) { 85 | return (int) (ltc->data >> 11) & 0x01; 86 | } 87 | bool bit27(ltcframe_t * ltc) { 88 | return (int) (ltc->data >> 27) & 0x01; 89 | } 90 | bool bit43(ltcframe_t * ltc) { 91 | return (int) (ltc->data >> 43) & 0x01; 92 | } 93 | bool bit58(ltcframe_t * ltc) { 94 | return (int) (ltc->data >> 58) & 0x01; 95 | } 96 | bool bit59(ltcframe_t * ltc) { 97 | return (int) (ltc->data >> 59) & 0x01; 98 | } 99 | 100 | uint32_t userdata(ltcframe_t * ltc) { 101 | return ((int) (ltc->data >> 4) & 0x0000000fUL) | ((int) (ltc->data >> 8) & 0x000000f0UL) | 102 | ((int) (ltc->data >> 12) & 0x00000f00UL) | ((int) (ltc->data >> 16) & 0x0000f000UL) | 103 | ((int) (ltc->data >> 20) & 0x000f0000UL) | ((int) (ltc->data >> 24) & 0x00f00000UL) | 104 | ((int) (ltc->data >> 28) & 0x0f000000UL) | ((int) (ltc->data >> 32) & 0xf0000000UL); 105 | } 106 | 107 | virtual void update(void); 108 | 109 | private: 110 | audio_block_t *inputQueueArray[1]; 111 | volatile bool new_output; 112 | int avsample; 113 | ltcframe_t ltcframe; 114 | inline void decodeBitstream(unsigned newbit); 115 | }; 116 | 117 | #endif -------------------------------------------------------------------------------- /examples/ltc_gen/ltc_gen.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Linear Timecode generation and decoding combined 3 | 4 | Please modify as needed 5 | 6 | (c) Frank B, 2020 7 | (pls keep copyright-info) 8 | 9 | To enable decoding: 10 | #define USE_LTC_INPUT 11 | and connect pin1 to pinA2 (=pin 16) 12 | 13 | Licence: MIT 14 | */ 15 | 16 | #define USE_LTC_INPUT //comment-out for generation only 17 | float fps = 24; 18 | const int ltcPin = 1; 19 | const int syncPin = 0; 20 | 21 | #include 22 | 23 | 24 | #ifdef USE_LTC_INPUT 25 | /* 26 | Connect ltc 27 | */ 28 | #include 29 | #include 30 | AudioInputAnalog adc1; 31 | AudioAnalyzeLTC ltc1; 32 | AudioConnection patchCord2(adc1, 0, ltc1, 0); 33 | 34 | #else 35 | struct ltcframe_t { 36 | uint64_t data; 37 | uint16_t sync; 38 | }; 39 | #endif 40 | 41 | IntervalTimer ltcTimer; 42 | IntervalTimer fpsTimer; 43 | 44 | volatile int clkCnt = 0; 45 | ltcframe_t ltc; 46 | float ltcTimer_freq; 47 | 48 | time_t getTeensy3Time() 49 | { 50 | return Teensy3Clock.get(); 51 | } 52 | 53 | void genLtc() { 54 | static int state = 0; 55 | 56 | if (clkCnt++ >= 2 * 80) { 57 | //ltcTimer.end(); 58 | clkCnt = 0; 59 | return; 60 | } 61 | 62 | int bitval; 63 | int bitcount = clkCnt / 2; 64 | 65 | ltcframe_t ltct; 66 | ltct = ltc; 67 | 68 | if (bitcount < 64) { 69 | bitval = (int) (ltct.data >> bitcount) & 0x01; 70 | } else { 71 | bitval = (int) (ltct.sync >> (bitcount - 64)) & 0x01; //backward 72 | } 73 | 74 | if ( (bitval == 1) || ( (bitval == 0) && (clkCnt & 0x01) ) == 0) { 75 | state = !state; // toggle state 76 | digitalWriteFast(ltcPin, state); 77 | } 78 | 79 | } 80 | 81 | //https://www.geeksforgeeks.org/program-to-find-parity/ 82 | inline 83 | int getParity(uint64_t n) 84 | { 85 | int parity = 0; 86 | while (n) 87 | { 88 | parity = !parity; 89 | n = n & (n - 1); 90 | } 91 | return parity & 0x01; 92 | } 93 | 94 | 95 | /* 96 | 97 | Gets called by rising edge of syncPin 98 | */ 99 | void startLtc() { 100 | 101 | 102 | ltcTimer.begin(genLtc, ltcTimer_freq); 103 | ltcTimer.priority(0); 104 | 105 | int t, t10; 106 | uint64_t data = ltc.data; 107 | clkCnt = 0; 108 | 109 | //get frame number: 110 | t = ((data >> 8) & 0x03) * 10 + (data & 0x0f); 111 | 112 | //zero out ltc data, leave d+c flags and user-bits untouched: 113 | data &= 0xf0f0f0f0f0f0fcf0ULL; 114 | //data = 0; 115 | //inc frame-number: 116 | //TODO: realize "drop frame numbering" (when D-Flag is set) 117 | t++; 118 | if (t >= (int) fps) t = 0; 119 | //set frame number: 120 | t10 = t / 10; 121 | data |= (t10 & 0x03) << 8; 122 | data |= ((t - t10 * 10)) << 0; 123 | 124 | //set time: 125 | t = second(); 126 | t10 = t / 10; 127 | data |= (t10 & 0x07) << 24; //Seconds tens 128 | data |= ((t - t10 * 10) & 0x0f) << 16; 129 | 130 | t = minute(); 131 | t10 = t / 10; 132 | data |= (uint64_t)(t10 & 0x07) << 40; //minute tens 133 | data |= (uint64_t)((t - t10 * 10) & 0x0f) << 32; 134 | 135 | t = hour(); 136 | t10 = t / 10; 137 | data |= (uint64_t)(t10 & 0x03) << 56; //hour tens 138 | data |= (uint64_t)((t - t10 * 10) & 0x0f) << 48; 139 | 140 | //set parity: 141 | int parity = (!getParity(data)) & 0x01; 142 | if ((int) fps == 25) { 143 | data |= (uint64_t) parity << 59; 144 | } else { 145 | data |= (uint64_t) parity << 27; 146 | } 147 | 148 | ltc.data = data; 149 | 150 | } 151 | 152 | void initLtcData() 153 | { 154 | ltc.sync = 0xBFFC; 155 | ltc.data = (uint64_t) 0; //<- place your initial data here 156 | } 157 | 158 | void genFpsSync() { 159 | static int state = 0; 160 | state = !state; // toggle state 161 | digitalWriteFast(syncPin, state); 162 | } 163 | 164 | void loop() { 165 | 166 | while (1) { 167 | #ifdef USE_LTC_INPUT 168 | if (ltc1.available()) { 169 | ltcframe_t ltcframe = ltc1.read(); 170 | Serial.printf("%02d:%02d:%02d.%02d\n", ltc1.hour(<cframe), ltc1.minute(<cframe), ltc1.second(<cframe), ltc1.frame(<cframe)); 171 | } 172 | #else 173 | Serial.printf(" %02d:%02d:%02d\n", hour(), minute(), second()); 174 | delay(1000); 175 | #endif 176 | } 177 | } 178 | 179 | void setup() { 180 | #ifdef USE_LTC_INPUT 181 | AudioMemory(4); 182 | #endif 183 | setSyncProvider(getTeensy3Time); 184 | pinMode(syncPin, OUTPUT); 185 | pinMode(ltcPin, OUTPUT); 186 | Serial.begin(9600); 187 | ltcTimer_freq = (1.0f / (2 * 80 * (fps ))) * 1000000.0f - 0.125f;// -0.125: make it a tiny bit faster than needed to allow syncing 188 | 189 | initLtcData(); 190 | //set frame number to last frame to force roll-over with new second() 191 | ltc.data &= 0xf0f0f0f0f0f0fcf0ULL; //delete all dynamic data in frame 192 | int t, t10; 193 | t = fps; 194 | t10 = t / 10; 195 | ltc.data |= (t10 & 0x03) << 8; 196 | ltc.data |= ((t - t10 * 10)); 197 | //now wait for seconds-change 198 | int secs = second(); 199 | while (secs == second()) {;} 200 | 201 | //start timer and pin-interrupt: 202 | fpsTimer.begin(&genFpsSync, (1.0f / (2 * fps)) * 1000000.0f); 203 | fpsTimer.priority(0); 204 | attachInterrupt(digitalPinToInterrupt(syncPin), &startLtc, RISING); 205 | NVIC_SET_PRIORITY(88, 0); //set GPIO-INT-Priority for Pin 1 206 | } 207 | --------------------------------------------------------------------------------