Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #13 from russellmcc/feature/time-tags
Feature/time tags
  • Loading branch information
russellmcc committed Jan 7, 2016
2 parents b03f085 + 6a22eec commit 8b14bdb
Show file tree
Hide file tree
Showing 8 changed files with 301 additions and 87 deletions.
34 changes: 13 additions & 21 deletions COPYING
@@ -1,23 +1,15 @@
Boost Software License - Version 1.0 - August 17th, 2003
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.

Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgement in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
9 changes: 6 additions & 3 deletions examples/oscbundle_heartbeat.coffee
Expand Up @@ -10,9 +10,12 @@ if process.argv[2]?
else
outport = 41234

# Get the unix timestamp in seconds
now = -> (new Date()).getTime() / 1000;

sendHeartbeat = () ->
buf = osc.toBuffer(
timetag : 12345
timetag : now() + 0.05 # 0.05 seconds from now
elements : [
{
address : "/p1"
Expand All @@ -23,7 +26,7 @@ sendHeartbeat = () ->
args : "string"
}
{
timetag: 34567
timetag: now() + 1 # 1 second from now
elements : [
{
address : "/p3"
Expand All @@ -38,4 +41,4 @@ sendHeartbeat = () ->

setInterval sendHeartbeat, 2000

console.log "sending heartbeat messages to http://localhost:" + outport
console.log "sending heartbeat messages to http://localhost:" + outport
50 changes: 43 additions & 7 deletions lib/index.js
@@ -1,7 +1,7 @@
(function() {

//~readme.out~
//[![build status](https://secure.travis-ci.org/russellmcc/node-osc-min.png)](http://travis-ci.org/russellmcc/node-osc-min)
//[![build status](https://secure.travis-ci.org/russellmcc/node-osc-min.png)](http://travis-ci.org/russellmcc/node-osc-min) [![Coverage Status](https://coveralls.io/repos/russellmcc/node-osc-min/badge.png?branch=master)](https://coveralls.io/r/russellmcc/node-osc-min?branch=master) [![dependencies](https://david-dm.org/russellmcc/node-osc-min.png)](https://david-dm.org/russellmcc/node-osc-min)
//# osc-min
//
// _simple utilities for open sound control in node.js_
Expand All @@ -28,6 +28,9 @@
//~api~
//----
//~representation~
//----
//## License
// Licensed under the terms found in COPYING (zlib license)

//~representation~
//## Javascript representations of the OSC types.
Expand Down Expand Up @@ -76,20 +79,32 @@
// `string`, `float`, `array` or `blob`, depending on its javascript type
// (String, Number, Array, Buffer, respectively)
//
// + An _OSC Bundle_ is represented as a javascript object with the following layout
// + An _OSC Bundle_ is represented as a javascript object with the following fields:
//
// {
// oscType : "bundle"
// timetag : 7
// elements : [element1, element]
// }
//
// Where the timetag is a javascript-native numeric value of the timetag,
// and elements is an array of either an _OSC Bundle_ or an _OSC Message_
// The `oscType` field is optional, but is always returned by api functions.
// `oscType` "bundle"
//
// `timetag` is one of:
// - `null` - meaning now, the current time.
// By the time the bundle is received it will too late and depending
// on the receiver may be discarded or you may be scolded for being late.
// - `number` - relative seconds from now with millisecond accuracy.
// - `Date` - a JavaScript Date object in your local time zone.
// OSC timetags use UTC timezone, so do not try to adjust for timezones,
// this is not needed.
// - `Array` - `[numberOfSecondsSince1900, fractionalSeconds]`
// Both values are `number`s. This gives full timing accuracy of 1/(2^32) seconds.
//
// `elements` is an `Array` of either _OSC Message_ or _OSC Bundle_
//
//
// [spec]: http://opensoundcontrol.org/spec-1_0

var utils, coffee;
utils = require("./osc-utilities");
// ~api~
Expand All @@ -116,9 +131,11 @@
// takes a _OSC packet_ javascript representation as defined below and returns
// a node.js Buffer, or throws if the representation is ill-formed.
//
// See "JavaScript representations of the OSC types" below.
//
//----
//### .toBuffer(address, args[], [strict])
// alternative syntax for above. Assumes this is an _OSC Message_ as defined below,
// alternative syntax for above. Assumes this is an _OSC Message_ as defined below,
// and `args` is an array of _OSC Arguments_ or single _OSC Argument_
exports.toBuffer = function(object, strict, opt) {
if(typeof object === "string")
Expand Down Expand Up @@ -166,4 +183,23 @@
return utils.applyTransform(buffer, utils.messageTransform(transform));
};


//~api~
//----
//### .timetagToDate(ntpTimeTag)
// Convert a timetag array to a JavaScript Date object in your local timezone.
//
// Received OSC bundles converted with `fromBuffer` will have a timetag array:
// [secondsSince1970, fractionalSeconds]
// This utility is useful for logging. Accuracy is reduced to milliseconds.
exports.timetagToDate = utils.timetagToDate;

//~api~
//----
//### .dateToTimetag(date)
// Convert a JavaScript Date to a NTP timetag array [secondsSince1970, fractionalSeconds].
//
// `toBuffer` already accepts Dates for timetags so you might not need this function. If you need to schedule bundles with finer than millisecond accuracy then you could use this to help assemble the NTP array.
exports.dateToTimetag = utils.dateToTimetag;

}).call(this);
8 changes: 8 additions & 0 deletions lib/install.md
Expand Up @@ -20,4 +20,12 @@ tests with
```
npm test
npm run-script coverage
```

### For the browser
If you want to use this library in a browser, you can build a browserified file (`build/osc-min.js`) with

```
npm install --dev
npm run-script browserify
```
105 changes: 96 additions & 9 deletions lib/osc-utilities.coffee
Expand Up @@ -129,6 +129,94 @@ exports.splitInteger = (buffer, type) ->

return {integer : value, rest : rest}

# Split off an OSC timetag from buffer
# returning {timetag: [seconds, fractionalSeconds], rest: restOfBuffer}
exports.splitTimetag = (buffer) ->
type = "Int32"
bytes = (binpack["pack" + type] 0).length

if buffer.length < (bytes * 2)
throw new Error "buffer is not big enough to contain a timetag"

# integers are stored in big endian format.
a = 0
b = bytes
seconds = binpack["unpack" + type] buffer[a...b], "big"
c = bytes
d = bytes + bytes
fractional = binpack["unpack" + type] buffer[c...d], "big"
rest = buffer[d...(buffer.length)]

return {timetag: [seconds, fractional], rest: rest}

UNIX_EPOCH = 2208988800
TWO_POW_32 = 4294967296

# Convert a JavaScript Date to a NTP timetag array.
# Time zone of the Date object is respected, as the NTP
# timetag uses UTC.
exports.dateToTimetag = (date) ->
return exports.timestampToTimetag(date.getTime() / 1000)

# Convert a unix timestamp (seconds since jan 1 1970 UTC)
# to NTP timestamp array
exports.timestampToTimetag = (secs) ->
wholeSecs = Math.floor(secs)
fracSeconds = secs - wholeSecs
return makeTimetag(wholeSecs, fracSeconds)

makeTimetag = (unixseconds, fracSeconds) ->
# NTP epoch is 1900, JavaScript Date is unix 1970
ntpSecs = unixseconds + UNIX_EPOCH
ntpFracs = Math.round(TWO_POW_32 * fracSeconds)
return [ntpSecs, ntpFracs]

# Convert NTP timestamp array to a JavaScript Date
# in your systems local time zone.
exports.timetagToDate = (timetag) ->
[seconds, fractional] = timetag
seconds = seconds - UNIX_EPOCH
fracs = exports.ntpToFractionalSeconds(fractional)
date = new Date()
# Sets date to UTC/GMT
date.setTime((seconds * 1000) + (fracs * 1000))
# Create a local timezone date
dd = new Date()
dd.setUTCFullYear(date.getUTCFullYear())
dd.setUTCMonth(date.getUTCMonth())
dd.setUTCDate(date.getUTCDate())
dd.setUTCHours(date.getUTCHours())
dd.setUTCMinutes(date.getUTCMinutes())
dd.setUTCSeconds(date.getUTCSeconds())
dd.setUTCMilliseconds(fracs * 1000)
return dd

# Make NTP timestamp array for relative future: now + seconds
# Accuracy of 'now' limited to milliseconds but 'seconds' may be a full 32 bit float
exports.deltaTimetag = (seconds, now) ->
n = (now ? new Date()) / 1000
return exports.timestampToTimetag(n + seconds)

# Convert 32 bit int for NTP fractional seconds
# to a 32 bit float
exports.ntpToFractionalSeconds = (fracSeconds) ->
return parseFloat(fracSeconds) / TWO_POW_32

# Encodes a timetag of type null|Number|Array|Date
# as a Buffer for adding to an OSC bundle.
exports.toTimetagBuffer = (timetag) ->
if typeof timetag is "number"
timetag = exports.timestampToTimetag(timetag)
else if typeof timetag is "object" and ("getTime" of timetag)
# quacks like a Date
timetag = exports.dateToTimetag(timetag)
else if timetag.length != 2
throw new Error("Invalid timetag" + timetag)
type = "Int32"
high = binpack["pack" + type] timetag[0], "big"
low = binpack["pack" + type] timetag[1], "big"
return exports.concat([high, low])

exports.toIntegerBuffer = (number, type) ->
type = "Int32" if not type?
if typeof number isnt "number"
Expand Down Expand Up @@ -164,11 +252,10 @@ oscTypeCodes =
t : {
representation : "timetag"
split : (buffer, strict) ->
split = exports.splitInteger buffer, "UInt64"
{value : split.integer, rest : split.rest}
split = exports.splitTimetag buffer
{value: split.timetag, rest: split.rest}
toArg : (value, strict) ->
throw new Error "expected number" if typeof value isnt "number"
exports.toIntegerBuffer value, "UInt64"
exports.toTimetagBuffer value
}
f : {
representation : "float"
Expand Down Expand Up @@ -388,8 +475,8 @@ exports.fromOscBundle = (buffer, strict) ->
if bundleTag isnt "\#bundle"
throw new Error "osc-bundles must begin with \#bundle"

# grab the 8 - bit timetag
{ integer : timetag, rest : buffer} = exports.splitInteger buffer, "UInt64"
# grab the 8 byte timetag
{timetag: timetag, rest: buffer} = exports.splitTimetag buffer

# convert each element.
convertedElems = mapBundleList buffer, (buffer) ->
Expand Down Expand Up @@ -480,15 +567,15 @@ exports.toOscBundle = (bundle, strict) ->
# the bundle must have timetag and elements.
if strict and not bundle?.timetag?
throw StrictError "bundles must have timetags."
timetag = bundle?.timetag ? 0
timetag = bundle?.timetag ? new Date()
elements = bundle?.elements ? []
if not IsArray elements
elemstr = elements
elements = []
elements.push elemstr

oscBundleTag = exports.toOscString "\#bundle"
oscTimeTag = exports.toIntegerBuffer timetag, "UInt64"
oscTimeTag = exports.toTimetagBuffer timetag

oscElems = []
for elem in elements
Expand Down Expand Up @@ -677,4 +764,4 @@ mapBundleList = (buffer, func) ->
for elem in elems
(nonNullElems.push elem) if elem?

nonNullElems
nonNullElems
5 changes: 3 additions & 2 deletions package.json
@@ -1,6 +1,6 @@
{
"name": "osc-min",
"version": "0.2.0",
"version": "1.0.0",
"main": "lib/index",
"author": {
"name": "Russell McClellan",
Expand All @@ -23,7 +23,7 @@
},
"devDependencies": {
"mocha": "*",
"docket": ">=0.0.3",
"docket": "0.0.5",
"coveralls": "*",
"blanket": "*",
"mocha-lcov-reporter": "*",
Expand All @@ -42,6 +42,7 @@
"lib": "lib",
"examples": "examples"
},
"license": "Zlib",
"engines": {
"node": ">=0.10.0"
},
Expand Down

0 comments on commit 8b14bdb

Please sign in to comment.