195 lines
5.5 KiB
Python
195 lines
5.5 KiB
Python
|
#!/usr/bin/env python3
|
|||
|
from timecode import Timecode
|
|||
|
|
|||
|
def bitstring_to_bytes(s, bytecount=1, byteorder='big'):
|
|||
|
return int(s, 2).to_bytes(bytecount, byteorder)
|
|||
|
|
|||
|
# binary big-endian
|
|||
|
def bbe(n, bits=8):
|
|||
|
# terminal condition
|
|||
|
retval = ''
|
|||
|
if n == 0:
|
|||
|
retval = '0'
|
|||
|
else:
|
|||
|
retval = bbe(n//2, None) + str(n%2)
|
|||
|
if bits is None:
|
|||
|
return retval
|
|||
|
else:
|
|||
|
return (('0'*bits) + retval)[-bits:]
|
|||
|
|
|||
|
|
|||
|
# binary, little-endian
|
|||
|
def ble(n, bits=8):
|
|||
|
# terminal condition
|
|||
|
retval = ''
|
|||
|
if n == 0:
|
|||
|
retval = '0'
|
|||
|
else:
|
|||
|
retval = str(n%2) + ble(n//2, None)
|
|||
|
if bits is None:
|
|||
|
return retval
|
|||
|
else:
|
|||
|
return (retval + ('0'*bits))[0:bits]
|
|||
|
|
|||
|
def cint(n, bytecount=2):
|
|||
|
return int(n).to_bytes(bytecount, byteorder='little')
|
|||
|
|
|||
|
def units_tens(n):
|
|||
|
return n % 10, int(n/10)
|
|||
|
|
|||
|
##
|
|||
|
## LTC functions
|
|||
|
##
|
|||
|
# GENERATE BINARY-CODED DATA FOR LTC
|
|||
|
# ACCORDING TO https://en.wikipedia.org/wiki/Linear_timecode
|
|||
|
# everything is encoded little endian
|
|||
|
# so to encode the number 3 with four bits, we have 1100
|
|||
|
def ltc_encode(timecode, as_string=False):
|
|||
|
LTC = ''
|
|||
|
HLP = ''
|
|||
|
hrs, mins, secs, frs = timecode.frames_to_tc(timecode.frames)
|
|||
|
frame_units, frame_tens = units_tens(frs)
|
|||
|
secs_units, secs_tens = units_tens(secs)
|
|||
|
mins_units, mins_tens = units_tens(mins)
|
|||
|
hrs_units, hrs_tens = units_tens(hrs)
|
|||
|
|
|||
|
#frames units / user bits field 1 / frames tens
|
|||
|
LTC += ble(frame_units,4) + '0000' + ble(frame_tens,2)
|
|||
|
HLP += '---{u}____-{t}'.format(u=frame_units, t=frame_tens)
|
|||
|
|
|||
|
#drop frame / color frame / user bits field 2
|
|||
|
LTC += '00'+'0000'
|
|||
|
HLP += '__'+'____'
|
|||
|
|
|||
|
#secs units / user bits field 3 / secs tens
|
|||
|
LTC += ble(secs_units,4) + '0000' + ble(secs_tens,3)
|
|||
|
HLP += '---{u}____--{t}'.format(u=secs_units, t=secs_tens)
|
|||
|
|
|||
|
# bit 27 flag / user bits field 4
|
|||
|
LTC += '0' + '0000'
|
|||
|
HLP += '_' + '____'
|
|||
|
|
|||
|
#mins units / user bits field 5 / mins tens
|
|||
|
LTC += ble(mins_units,4) + '0000' + ble(mins_tens,3)
|
|||
|
HLP += '---{u}____--{t}'.format(u=mins_units, t=mins_tens)
|
|||
|
|
|||
|
# bit 43 flag / user bits field 6
|
|||
|
LTC += '0' + '0000'
|
|||
|
HLP += '_' + '____'
|
|||
|
|
|||
|
#hrs units / user bits field 7 / hrs tens
|
|||
|
LTC += ble(hrs_units,4) + '0000' + ble(hrs_tens,2)
|
|||
|
HLP += '---{u}____--{t}'.format(u=hrs_units, t=hrs_tens)
|
|||
|
|
|||
|
# bit 58 clock flag / bit 59 flag / user bits field 8
|
|||
|
LTC += '0' + '0' + '0000'
|
|||
|
HLP += '_' + '_' + '____'
|
|||
|
|
|||
|
# sync word
|
|||
|
LTC += '0011111111111101'
|
|||
|
HLP += '################'
|
|||
|
if as_string:
|
|||
|
return LTC
|
|||
|
else:
|
|||
|
return bitstring_to_bytes(LTC, bytecount=10)
|
|||
|
|
|||
|
|
|||
|
##
|
|||
|
## MTC functions
|
|||
|
##
|
|||
|
def mtc_encode(timecode, as_string=False):
|
|||
|
# MIDI bytes are little-endian
|
|||
|
# Byte 0
|
|||
|
# 0rrhhhhh: Rate (0–3) and hour (0–23).
|
|||
|
# rr = 000: 24 frames/s
|
|||
|
# rr = 001: 25 frames/s
|
|||
|
# rr = 010: 29.97 frames/s (SMPTE drop-frame timecode)
|
|||
|
# rr = 011: 30 frames/s
|
|||
|
# Byte 1
|
|||
|
# 00mmmmmm: Minute (0–59)
|
|||
|
# Byte 2
|
|||
|
# 00ssssss: Second (0–59)
|
|||
|
# Byte 3
|
|||
|
# 000fffff: Frame (0–29, or less at lower frame rates)
|
|||
|
hrs, mins, secs, frs = timecode.frames_to_tc(timecode.frames)
|
|||
|
framerate = timecode.framerate
|
|||
|
rateflags = {
|
|||
|
'24': 0,
|
|||
|
'25': 1,
|
|||
|
'29.97': 2,
|
|||
|
'30': 3
|
|||
|
}
|
|||
|
rateflag = rateflags[framerate] * 32 # multiply by 32, because the rate flag starts at bit 6
|
|||
|
|
|||
|
# print('{:8} {:8} {:8} {:8}'.format(hrs, mins, secs, frs))
|
|||
|
if as_string:
|
|||
|
b0 = bbe(rateflag + hrs, 8)
|
|||
|
b1 = bbe(mins)
|
|||
|
b2 = bbe(secs)
|
|||
|
b3 = bbe(frs)
|
|||
|
# print('{:8} {:8} {:8} {:8}'.format(b0, b1, b2, b3))
|
|||
|
return b0+b1+b2+b3
|
|||
|
else:
|
|||
|
b = bytearray([rateflag + hrs, mins, secs, frs])
|
|||
|
# debug_string = ' 0x{:02} 0x{:02} 0x{:02} 0x{:02}'
|
|||
|
# debug_array = [ord(b[0]), ord(b[1]), ord(b[2]), ord(b[3])]
|
|||
|
# print(debug_string.format(debug_array))
|
|||
|
return b
|
|||
|
|
|||
|
# convert a bytearray back to timecode
|
|||
|
def mtc_decode(mtc_bytes):
|
|||
|
rhh, mins, secs, frs = mtc_bytes
|
|||
|
rateflag = rhh >> 5
|
|||
|
hrs = rhh & 31
|
|||
|
fps = ['24','25','29.97','30'][rateflag]
|
|||
|
total_frames = int(frs + float(fps) * (secs + mins * 60 + hrs * 60 * 60))
|
|||
|
return Timecode(fps, frames=total_frames)
|
|||
|
|
|||
|
def mtc_full_frame(timecode):
|
|||
|
# if sending this to a MIDI device, remember that MIDI is generally little endian
|
|||
|
# but the full frame timecode bytes are big endian
|
|||
|
mtc_bytes = mtc_encode(timecode)
|
|||
|
# mtc full frame has a special header and ignores the rate flag
|
|||
|
return bytearray([0xf0, 0x7f, 0x7f, 0x01, 0x01]) + mtc_bytes + bytearray([0xf7])
|
|||
|
|
|||
|
def mtc_decode_full_frame(full_frame_bytes):
|
|||
|
mtc_bytes = full_frame_bytes[5:-1]
|
|||
|
return mtc_decode(mtc_bytes)
|
|||
|
|
|||
|
def mtc_quarter_frame(timecode, piece=0):
|
|||
|
# there are 8 different mtc_quarter frame pieces
|
|||
|
# see https://en.wikipedia.org/wiki/MIDI_timecode
|
|||
|
# and https://web.archive.org/web/20120212181214/http://home.roadrunner.com/~jgglatt/tech/mtc.htm
|
|||
|
# these are little-endian bytes
|
|||
|
# piece 0 : 0xF1 0000 ffff frame
|
|||
|
mtc_bytes = mtc_encode(timecode)
|
|||
|
this_byte = mtc_bytes[3 - piece//2] #the order of pieces is the reverse of the mtc_encode
|
|||
|
if piece % 2 == 0:
|
|||
|
# even pieces get the low nibble
|
|||
|
nibble = this_byte & 15
|
|||
|
else:
|
|||
|
# odd pieces get the high nibble
|
|||
|
nibble = this_byte >> 4
|
|||
|
return bytearray([0xf1, piece * 16 + nibble])
|
|||
|
|
|||
|
def mtc_decode_quarter_frames(frame_pieces):
|
|||
|
mtc_bytes = bytearray(4)
|
|||
|
if len(frame_pieces) < 8:
|
|||
|
return None
|
|||
|
for piece in range(8):
|
|||
|
mtc_index = 3 - piece//2 # quarter frame pieces are in reverse order of mtc_encode
|
|||
|
this_frame = frame_pieces[piece]
|
|||
|
if this_frame is bytearray or this_frame is list:
|
|||
|
this_frame = this_frame[1]
|
|||
|
data = this_frame & 15 # ignore the frame_piece marker bits
|
|||
|
if piece % 2 == 0:
|
|||
|
# 'even' pieces came from the low nibble
|
|||
|
# and the first piece is 0, so it's even
|
|||
|
mtc_bytes[mtc_index] += data
|
|||
|
else:
|
|||
|
# 'odd' pieces came from the high nibble
|
|||
|
mtc_bytes[mtc_index] += data * 16
|
|||
|
return mtc_decode(mtc_bytes)
|
|||
|
|
|||
|
|