#-------------------------------------------------------------------
# This script uses your soundcard to transmit an MSF timecode
#
# Save it as msf.py somewhere on your PC.
#
# Run it with 'python msf.py'
#
# Tested with Python 2.7.10 and 3.5.2
#
# http://www.burningimage.net/msfsimulator
#
# andy@burningimage.net
#
#-------------------------------------------------------------------
# Set the time/date to transmit below.
#
# The 'minutes' will always start at 0, incrementing up to 59
# before exiting.
#
# Your clock should have set its time by around minute 5.
#
# Note:  Some clocks require that the day of the week correctly
#        matches the date - I'd recommend using my test values below
#        before changing them and trying your own.
#-------------------------------------------------------------------
year = 19
month = 6
dayofmonth = 4
dayofweek = 2       #0 = sunday, 1 = monday etc.
hour = 10

# Set to 1 to transmit proper parity bits
enableparity = 1
#-------------------------------------------------------------------

import pyaudio
import numpy
import math

def sine(frequency, length, rate):
    length = int(length * rate)
    factor = float(frequency) * (math.pi * 2) / rate
    return numpy.sin(numpy.arange(length) * factor)

def play_tone(stream, frequency=440, length=10, rate=48000):
    chunks = []
    
    #overdrive it to a square wave
    chunks.append(1e3*sine(frequency, length, rate))
    chunk = numpy.concatenate(chunks) * 0.25
    stream.write(chunk.astype(numpy.float32).tostring())

def minutemarker():
    play_tone(stream, 0, 0.5)
    play_tone(stream, 20000, 0.5)

def send01():
    play_tone(stream, 0, 0.1)
    play_tone(stream, 20000, 0.1)
    play_tone(stream, 0, 0.1)
    play_tone(stream, 20000, 0.7)

def send11():
    play_tone(stream, 0, 0.3)
    play_tone(stream, 20000, 0.7)

def send00():
    play_tone(stream, 0, 0.1)
    play_tone(stream, 20000, 0.9)

def send10():
    play_tone(stream, 0, 0.2)
    play_tone(stream, 20000, 0.8)

bcdlist= [80, 40, 20, 10, 8, 4, 2, 1]

p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paFloat32, channels=1, rate=48000, output=1)

for minute in range(0,60):
    # Ignore the '0' element in the list as it confuses matters
    # with the timecode
    timecodeA = [0] * 60
    timecodeB = [0] * 60

    # Convert the year to BCD and store in the correct place in the timecode
    bcdindex=0
    temp = year
    sum=0
    for i in range(17,25):
        if temp >= bcdlist[bcdindex]:
            timecodeA[i] = 1
            sum += 1;
            temp -= bcdlist[bcdindex]
        bcdindex += 1

    # Work out the parity bit for 17-24
    if (sum % 2) != 1:
        timecodeB[54] = enableparity

    # Now do the month
    bcdindex=3  #starts at 10
    temp = month
    sum = 0
    for i in range(25,30):
        if temp >= bcdlist[bcdindex]:
            timecodeA[i] = 1
            temp -= bcdlist[bcdindex]
            sum += 1
        bcdindex += 1

    # Do the day of month
    bcdindex=2  #starts at 20
    temp = dayofmonth
    for i in range(30,36):
        if temp >= bcdlist[bcdindex]:
            timecodeA[i] = 1
            temp -= bcdlist[bcdindex]
            sum += 1
        bcdindex += 1

    # Work out the parity bit for 25-35
    if (sum % 2) != 1:
        timecodeB[55] = enableparity

    # Do the day of week
    bcdindex=5  #starts at 4
    temp = dayofweek
    sum = 0
    for i in range(36,39):
        if temp >= bcdlist[bcdindex]:
            timecodeA[i] = 1
            temp -= bcdlist[bcdindex]
            sum += 1
        bcdindex += 1

    # Work out the parity bit for 36-38
    if (sum % 2) != 1:
        timecodeB[56] = enableparity

    # Do the hour
    bcdindex=2  #starts at 20
    temp = hour
    sum = 0
    for i in range(39,45):
        if temp >= bcdlist[bcdindex]:
            timecodeA[i] = 1
            temp -= bcdlist[bcdindex]
            sum += 1
        bcdindex += 1

    # Do the minute
    bcdindex=1  #starts at 40
    temp = minute
    for i in range(45,52):
        if temp >= bcdlist[bcdindex]:
            timecodeA[i] = 1
            temp -= bcdlist[bcdindex]
            sum += 1
        bcdindex += 1

    # Work out the parity bit for 36-38
    if (sum % 2) != 1:
        timecodeB[57] = enableparity

    #Bits 53A - 58A should always be 1
    for i in range(53,59):
        timecodeA[i] = 1

    print(str(year).zfill(2) + "-" + str(month).zfill(2) + "-" + str(dayofmonth).zfill(2) + " " + str(hour).zfill(2) + ":" + str(minute).zfill(2))
    print("A bits: " + str(timecodeA[1:]))
    print("B bits: " + str(timecodeB[1:]))

    # Now play the timecode out
    minutemarker()
    for i in range(1,60):
        if (timecodeA[i] == 1) and (timecodeB[i] == 1):
            send11()
        elif (timecodeA[i] == 0) and (timecodeB[i] == 1):
            send01()
        elif (timecodeA[i] == 0) and (timecodeB[i] == 0):
            send00()
        elif (timecodeA[i] == 1) and (timecodeB[i] == 0):
            send10()

stream.close()
p.terminate()
