|
/* |
|
JJY.js: JJY time signal emulation/transmission library |
|
|
|
Copyright (c) @plugnburn (831337.xyz) 2017 |
|
@license ISC |
|
*/ |
|
|
|
function JJY(distorter) { //JJY constructor |
|
distorter = parseFloat(distorter) |
|
|
|
//create pre-filled constant array for efficient copying |
|
var codeProto = '200000000200000000020000000002000000000200000000020000000002'.split('').map(Number) |
|
|
|
function toBCD(val) { |
|
return val%10 + (((val/10)%10)<<4) + ((val/100)<<8) |
|
} |
|
|
|
function calcParity(val){ |
|
var i = 0; |
|
while(val) { |
|
i ^= val & 1 |
|
val >>>= 1 |
|
} |
|
return i |
|
} |
|
|
|
function jjyTimecode(timeObj) { //generate a JJY timecode from any given Date object |
|
var ts = { |
|
min: toBCD(timeObj.getUTCMinutes()), |
|
hour: toBCD(timeObj.getUTCHours()), |
|
day: toBCD(Math.floor((timeObj - new Date(timeObj.getUTCFullYear(),0,0,0,0))/864e5)), |
|
year: toBCD(timeObj.getUTCFullYear() % 100), |
|
weekDay: timeObj.getUTCDay() |
|
}, timeCode = codeProto.slice(); |
|
|
|
//populate minute |
|
timeCode[1] = (ts.min>>6)&1 |
|
timeCode[2] = (ts.min>>5)&1 |
|
timeCode[3] = (ts.min>>4)&1 |
|
timeCode[5] = (ts.min>>3)&1 |
|
timeCode[6] = (ts.min>>2)&1 |
|
timeCode[7] = (ts.min>>1)&1 |
|
timeCode[8] = ts.min&1 |
|
//populate hour |
|
timeCode[12] = (ts.hour>>5)&1 |
|
timeCode[13] = (ts.hour>>4)&1 |
|
timeCode[15] = (ts.hour>>3)&1 |
|
timeCode[16] = (ts.hour>>2)&1 |
|
timeCode[17] = (ts.hour>>1)&1 |
|
timeCode[18] = ts.hour&1 |
|
//populate day number |
|
timeCode[22] = (ts.day>>9)&1 |
|
timeCode[23] = (ts.day>>8)&1 |
|
timeCode[25] = (ts.day>>7)&1 |
|
timeCode[26] = (ts.day>>6)&1 |
|
timeCode[27] = (ts.day>>5)&1 |
|
timeCode[28] = (ts.day>>4)&1 |
|
timeCode[30] = (ts.day>>3)&1 |
|
timeCode[31] = (ts.day>>2)&1 |
|
timeCode[32] = (ts.day>>1)&1 |
|
timeCode[33] = ts.day&1 |
|
//populate parity bits |
|
timeCode[36] = calcParity(ts.hour) |
|
timeCode[37] = calcParity(ts.min) |
|
//populate year |
|
timeCode[41] = (ts.year>>7)&1 |
|
timeCode[42] = (ts.year>>6)&1 |
|
timeCode[43] = (ts.year>>5)&1 |
|
timeCode[44] = (ts.year>>4)&1 |
|
timeCode[45] = (ts.year>>3)&1 |
|
timeCode[46] = (ts.year>>2)&1 |
|
timeCode[47] = (ts.year>>1)&1 |
|
timeCode[48] = ts.year&1 |
|
//populate day of the week |
|
timeCode[50] = ts.weekDay>>2 |
|
timeCode[51] = (ts.weekDay>>1)&1 |
|
timeCode[52] = ts.weekDay&1 |
|
return timeCode |
|
} |
|
|
|
function getJJYTimeCode(timeObj) { |
|
var timeRep = timeObj || new Date(Date.now() + 324e5); |
|
return { |
|
bitCode: jjyTimecode(timeRep), |
|
cs: timeRep.getUTCSeconds() |
|
} |
|
} |
|
|
|
return { |
|
getTimeCode: getJJYTimeCode, //exposed for getting timecode information in non-browser environments |
|
run: function(tObj) { //browser-only function, requires Web Audio API support |
|
var ctx = new (window.AudioContext || window.webkitAudioContext)(), |
|
sr = ctx.sampleRate, |
|
opFreq = 40000/3, |
|
rp = opFreq/sr, bufSet = [], pwm = [.8, .5, .2]; |
|
if(isNaN(distorter) || distorter < 2) |
|
distorter = sr/3 |
|
|
|
ctx.createBuffer = ctx.createBuffer || ctx.webkitCreateBuffer; |
|
ctx.createBufferSource = ctx.createBufferSource || ctx.webkitCreateBufferSource; |
|
performance.now = performance.now || performance.webkitNow || function(){return (new Date).getTime()} |
|
|
|
secondTick = (function(tm){ |
|
var px = 0, py = 0, dx = 0; |
|
return function(cb){ |
|
px = performance.now() |
|
setTimeout(function(){ |
|
dx = (py = performance.now()) - px - tm |
|
px = py |
|
cb() |
|
}, tm - dx/2) |
|
} |
|
})(1000) |
|
|
|
// pre-populate the buffers |
|
for(var i=0;i<3;i++) { |
|
var sLen = sr*pwm[i]; |
|
bufSet[i] = ctx.createBuffer(1, sLen, sr); |
|
var cData = bufSet[i].getChannelData(0); |
|
for(var k=0;k<sLen;k++) |
|
cData[k] = Math.floor(distorter*Math.sin(2 * Math.PI * k * rp))/distorter |
|
} |
|
// play back the timecode |
|
var renderMinute = function() { |
|
var sigInfo = getJJYTimeCode(tObj), currentIndex = sigInfo.cs, |
|
renderSecond = function() { |
|
secondTick(currentIndex<59 ? renderSecond : renderMinute) |
|
var bs = ctx.createBufferSource() |
|
bs.buffer = bufSet[sigInfo.bitCode[currentIndex]] |
|
bs.connect(ctx.destination) |
|
bs.start() |
|
currentIndex++ |
|
} |
|
renderSecond() |
|
} |
|
renderMinute() |
|
} |
|
} |
|
} |
|
|
|
if(typeof module !== 'undefined' && module.exports && this.module !== module) |
|
module.exports = JJY() |
I'm using a local copy of this script to broadcast a JJY signal from my desktop computer to an AirPort Express that sits on my WiFi network with a loop antenna connected to it. That works, and I can leave a watch in the loop antenna overnight and it will sync automatically. The one issue I'm having is that because of the transmission delay between the JJY emulation being generated on my desktop and the antenna receiving it, my watch syncs to a time 0.5 to 1.0 seconds behind the actual time. Can you please tell me what I would have to change in this script to add half a second or a whole second to the time it broadcasts? I don't want to have to learn enough JavaScript just to be able to make this one tiny change to one script. Thanks.