Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support remaining "non-standard" types described in the standard #24

Merged
merged 5 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 144 additions & 1 deletion lib/osc-utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ const parseOscArg = (
const { value, rest } = splitOscString(buffer);
return { value: { type: "string", value }, rest };
}
case "S": {
const { value, rest } = splitOscString(buffer);
return { value: { type: "symbol", value }, rest };
}
case "i": {
const { value, rest } = splitInteger(buffer);
return { value: { type: "integer", value }, rest };
Expand Down Expand Up @@ -204,6 +208,21 @@ const parseOscArg = (
rest: sliceDataView(data, length + padding),
};
}
case "r": {
const view = toView(buffer);
return {
value: {
type: "color",
value: {
red: view.getUint8(0),
green: view.getUint8(1),
blue: view.getUint8(2),
alpha: view.getUint8(3),
},
},
rest: sliceDataView(view, 4),
};
}
case "T":
return {
value: { type: "true", value: true },
Expand All @@ -224,6 +243,33 @@ const parseOscArg = (
value: { type: "bang", value: "bang" },
rest: toView(buffer),
};
case "c": {
const view = toView(buffer);
const codepoint = view.getUint32(0, false);
return {
value: { type: "character", value: String.fromCodePoint(codepoint) },
rest: sliceDataView(view, 4),
};
}
case "h": {
const view = toView(buffer);
const bigint = view.getBigInt64(0, false);
return {
value: { type: "bigint", value: bigint },
rest: sliceDataView(view, 8),
};
}
case "m": {
const view = toView(buffer);
if (view.byteLength < 4) {
throw new OSCError("buffer is not big enough to contain a midi packet");
}
const array = new Uint8Array(view.buffer, view.byteOffset, 4);
return {
value: { type: "midi", value: [...array] as OscMidiPacket },
rest: sliceDataView(view, 4),
};
}
}
return undefined;
};
Expand All @@ -232,15 +278,34 @@ const toOscArgument = (arg: OscArgWithType): ArrayBuffer => {
switch (arg.type) {
case "string":
return toOscString(arg.value);
case "symbol":
return toOscString(arg.value);
case "integer":
return toIntegerBuffer(arg.value);
case "timetag":
return toTimetagBuffer(arg.value);
case "character": {
const chars = [...arg.value];

if (chars.length !== 1) {
throw new OSCError("Can only send a single character");
}

const ret = new DataView(new ArrayBuffer(4));
// ! is safe here because we checked length === 1 above
ret.setUint32(0, chars[0]!.codePointAt(0) ?? 0, false);
return ret.buffer;
}
case "float": {
const ret = new DataView(new ArrayBuffer(4));
ret.setFloat32(0, arg.value, false);
return ret.buffer;
}
case "bigint": {
const ret = new DataView(new ArrayBuffer(8));
ret.setBigInt64(0, arg.value, false);
return ret.buffer;
}
case "double": {
const ret = new DataView(new ArrayBuffer(8));
ret.setFloat64(0, arg.value, false);
Expand All @@ -261,6 +326,17 @@ const toOscArgument = (arg: OscArgWithType): ArrayBuffer => {
);
return ret.buffer;
}
case "color": {
const ret = new DataView(new ArrayBuffer(4 * 4));
ret.setUint8(0, arg.value.red);
ret.setUint8(1, arg.value.green);
ret.setUint8(2, arg.value.blue);
ret.setUint8(3, arg.value.alpha);
return ret.buffer;
}
case "midi": {
return new Uint8Array(arg.value).buffer;
}
case "true":
return new ArrayBuffer(0);
case "false":
Expand All @@ -282,7 +358,12 @@ export type OscTypeCode =
| "T"
| "F"
| "N"
| "I";
| "I"
| "S"
| "c"
| "r"
| "m"
| "h";

const RepresentationToTypeCode: {
[key in OscArgWithType["type"]]: OscTypeCode;
Expand All @@ -297,17 +378,32 @@ const RepresentationToTypeCode: {
false: "F",
null: "N",
bang: "I",
symbol: "S",
character: "c",
color: "r",
midi: "m",
bigint: "h",
};

export type OscMidiPacket = [number, number, number, number];

export type OscArgOutput =
| {
type: "string";
value: string;
}
| {
type: "symbol";
value: string;
}
| {
type: "integer";
value: number;
}
| {
type: "bigint";
value: bigint;
}
| {
type: "timetag";
value: TimeTag;
Expand All @@ -324,6 +420,14 @@ export type OscArgOutput =
type: "blob";
value: DataView;
}
| {
type: "midi";
value: OscMidiPacket;
}
| {
type: "color";
value: OscColor;
}
| {
type: "true";
value: true;
Expand All @@ -339,21 +443,40 @@ export type OscArgOutput =
| {
type: "bang";
value: "bang";
}
| {
type: "character";
value: string;
};

export type OscArgOutputOrArray =
| OscArgOutput
| { type: "array"; value: OscArgOutputOrArray[] };

export type OscColor = {
red: number;
green: number;
blue: number;
alpha: number;
};

export type OscArgWithType =
| {
type: "string";
value: string;
}
| {
type: "symbol";
value: string;
}
| {
type: "integer";
value: number;
}
| {
type: "bigint";
value: bigint;
}
| {
type: "timetag";
value: TimeTag | Date;
Expand All @@ -370,6 +493,18 @@ export type OscArgWithType =
type: "blob";
value: ArrayBuffer | TypedBufferLike;
}
| {
type: "character";
value: string;
}
| {
type: "color";
value: OscColor;
}
| {
type: "midi";
value: OscMidiPacket;
}
| {
type: "true";
}
Expand All @@ -387,7 +522,9 @@ export type OscArgInput =
| OscArgWithType
| string
| number
| bigint
| Date
| OscColor
| ArrayBuffer
| TypedBufferLike
| true
Expand Down Expand Up @@ -425,6 +562,12 @@ const toOscArgWithType = (arg: OscArgInput): OscArgWithType => {
if (typeof arg === "object" && "buffer" in arg) {
return { type: "blob", value: arg };
}
if (typeof arg === "object" && "red" in arg) {
return { type: "color", value: arg };
}
if (typeof arg === "bigint") {
return { type: "bigint", value: arg };
}
if (arg === true) {
return { type: "true" };
}
Expand Down
6 changes: 6 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ Where the `type` is one of the following:
- `string` - string value
- `float` - numeric value
- `integer` - numeric value
- `color` - JS object containing `red`, `green`, `blue`, `alpha` in range 0-255
- `midi` - four-element array of numbers representing a midi packet of data
- `symbol` - string value
- `character` - a single-character string
- `double` - numeric value
- `bigint` - 64-bit `bigint` value (watch out, this will be truncated to 64 bits!)
- `blob` - `ArrayBuffer`, `DataView`, `TypedArray` or node.js `Buffer`
- `true` - value is boolean true
- `false` - value is boolean false
Expand Down
70 changes: 70 additions & 0 deletions test/osc-utilities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1157,6 +1157,76 @@ test("toOscMessage just a string works", function () {
assert.strictEqual(message.args.length, 0);
});

test("roundtrip symbol works", () => {
roundTripMessage([
{
type: "symbol",
value: "bleh",
},
]);
});

test("roundtrip character works", () => {
roundTripMessage([
{
type: "character",
value: "b",
},
]);
});

test("roundtrip emoji works", () => {
// Note this isn't actually standard but feels like it should work
roundTripMessage([
{
type: "character",
value: "🎛",
},
]);
});

test("Trying to send multiple characters fails", () => {
assert.throws(() => {
osc.toOscMessage({
address: "/addr",
args: { type: "character", value: "ab" },
});
});
});

test("Trying to send zero characters fails", () => {
assert.throws(() => {
osc.toOscMessage({
address: "/addr",
args: { type: "character", value: "" },
});
});
});

test("Roundtrip color works", () => {
roundTripMessage([
{
red: 255,
green: 0,
blue: 175,
alpha: 255,
},
]);
});

test("Roundtrip bigint works", () => {
roundTripMessage([1234567891234n]);
});

test("Roundtrip midi works", () => {
roundTripMessage([
{
type: "midi",
value: [1, 2, 3, 4],
},
]);
});

test("toOscMessage with multiple args works", function () {
roundTripMessage(["str", 7, new ArrayBuffer(30), 6]);
});
Expand Down