npm package: @mindfield/capacitor-osc
Description: Capacitor 7 plugin for Open Sound Control (OSC) — Send and receive OSC messages via UDP
Author: Mindfield Biosystems Ltd.
Latest version: 1.0.0

# @mindfield/capacitor-osc

[![npm version](https://img.shields.io/npm/v/@mindfield/capacitor-osc.svg)](https://www.npmjs.com/package/@mindfield/capacitor-osc)
[![license](https://img.shields.io/npm/l/@mindfield/capacitor-osc.svg)](LICENSE)

Capacitor 7 plugin for Open Sound Control (OSC) -- Send and receive OSC messages via UDP on Android and iOS.

## Features

- **UDP send and receive** -- Full bidirectional OSC communication over UDP
- **Complete OSC type system** -- Int32, Float32, Float64, Int64, String, Blob, True, False, Nil
- **Auto-detection** -- Numbers are automatically typed as Int32 or Float32; explicit type tags available when you need control
- **TypeScript-first** -- Full type definitions with JSDoc documentation
- **Promise-based API** -- Modern async/await interface, no callbacks
- **Event-driven receive** -- Capacitor listener pattern for incoming messages
- **Zero configuration (Android)** -- INTERNET permission is auto-added
- **Lightweight** -- Android uses JavaOSC 0.9 (Maven Central), iOS uses Apple's Network framework (no external dependencies)

## Supported Platforms

| Platform | Support | Minimum Version |
|----------|---------|-----------------|
| Android  | Yes     | API 23 (Android 6.0) |
| iOS      | Yes     | iOS 14.0 |
| Web      | No      | UDP is not available in browsers |

## Installation

```bash
npm install @mindfield/capacitor-osc
npx cap sync
```

## Platform Configuration

### Android

No extra configuration needed. The `INTERNET` permission is automatically added by the plugin.

### iOS

Add the local network usage description to your `Info.plist`:

```xml
<key>NSLocalNetworkUsageDescription</key>
<string>This app uses the local network to send and receive OSC messages.</string>
```

## Quick Start

```typescript
import { OSC } from '@mindfield/capacitor-osc';

// Send a message
await OSC.send({
  host: '192.168.1.100',
  port: 9000,
  message: { address: '/sensor/eda', args: [3.14, 'hello'] }
});

// Listen for messages
await OSC.startListening({ port: 8000 });
const handle = await OSC.addListener('message', (event) => {
  console.log(`${event.address}:`, event.args);
});

// Clean up
handle.remove();
await OSC.stopListening();
```

## API Reference

### startListening(options)

Start listening for incoming OSC messages on the given UDP port.

```typescript
startListening(options: StartListeningOptions): Promise<void>
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `options.port` | `number` | UDP port to listen on |

Throws an error with code `BIND_FAILED` if the port is already in use.

```typescript
await OSC.startListening({ port: 8000 });
```

### stopListening()

Stop listening for incoming OSC messages. No-op if not currently listening.

```typescript
stopListening(): Promise<void>
```

```typescript
await OSC.stopListening();
```

### send(options)

Send an OSC message to the specified host and port.

```typescript
send(options: SendOptions): Promise<void>
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `options.host` | `string` | Target host IP address or hostname |
| `options.port` | `number` | Target UDP port |
| `options.message` | `OSCMessage` | The OSC message to send |

Throws an error with code `SEND_FAILED` on network errors.

```typescript
await OSC.send({
  host: '192.168.1.100',
  port: 9000,
  message: {
    address: '/sensor/temperature',
    args: [36.5, 'celsius']
  }
});
```

### getStatus()

Get the current status of the OSC plugin.

```typescript
getStatus(): Promise<OSCStatusResult>
```

Returns an `OSCStatusResult` with `isListening` (boolean) and optionally `listeningPort` (number).

```typescript
const status = await OSC.getStatus();
console.log(status.isListening, status.listeningPort);
```

### addListener('message', ...)

Listen for incoming OSC messages. Requires `startListening()` to be called first.

```typescript
addListener(
  eventName: 'message',
  listener: (event: OSCMessageEvent) => void
): Promise<PluginListenerHandle>
```

The `OSCMessageEvent` contains:

| Field | Type | Description |
|-------|------|-------------|
| `address` | `string` | OSC address of the received message |
| `args` | `OSCArgumentValue[]` | Decoded arguments |
| `host` | `string` | Sender IP address (always `""` on Android — JavaOSC limitation) |
| `port` | `number` | Sender port (always `0` on Android — JavaOSC limitation) |
| `timestamp` | `number` | Receive timestamp (ms since epoch) |

```typescript
const handle = await OSC.addListener('message', (event) => {
  if (event.address === '/sensor/eda') {
    console.log('EDA value:', event.args[0]);
  }
});

// Remove this listener
handle.remove();
```

### addListener('error', ...)

Listen for OSC errors (bind failures, send errors, parse errors).

```typescript
addListener(
  eventName: 'error',
  listener: (event: OSCErrorEvent) => void
): Promise<PluginListenerHandle>
```

The `OSCErrorEvent` contains:

| Field | Type | Description |
|-------|------|-------------|
| `message` | `string` | Human-readable error description |
| `code` | `string` | Error code: `BIND_FAILED`, `SEND_FAILED`, or `PARSE_ERROR` |

```typescript
await OSC.addListener('error', (event) => {
  console.error(`OSC error [${event.code}]: ${event.message}`);
});
```

### removeAllListeners()

Remove all listeners for this plugin.

```typescript
removeAllListeners(): Promise<void>
```

```typescript
await OSC.removeAllListeners();
```

## Type Reference

### OSCTypedArg

Explicit type tag for OSC arguments. Use when auto-detection is insufficient.

```typescript
interface OSCTypedArg {
  type: 'i' | 'f' | 'd' | 'h' | 's' | 'b' | 'T' | 'F' | 'N';
  value?: number | string | boolean | null;
}
```

### OSCArgumentValue

A union type representing any OSC argument:

```typescript
type OSCArgumentValue = number | string | boolean | null | OSCTypedArg;
```

### OSCMessage

```typescript
interface OSCMessage {
  address: string;           // OSC address pattern, e.g. "/sensor/eda"
  args?: OSCArgumentValue[]; // Arguments to include
}
```

### OSCMessageEvent

```typescript
interface OSCMessageEvent {
  address: string;           // OSC address of the received message
  args: OSCArgumentValue[];  // Decoded arguments
  host: string;              // Sender IP address
  port: number;              // Sender port
  timestamp: number;         // Receive timestamp (ms since epoch)
}
```

### OSCErrorEvent

```typescript
interface OSCErrorEvent {
  message: string;           // Human-readable error description
  code: 'BIND_FAILED' | 'SEND_FAILED' | 'PARSE_ERROR';
}
```

### StartListeningOptions

```typescript
interface StartListeningOptions {
  port: number;              // UDP port to listen on
}
```

### SendOptions

```typescript
interface SendOptions {
  host: string;              // Target host IP or hostname
  port: number;              // Target UDP port
  message: OSCMessage;       // The OSC message to send
}
```

### OSCStatusResult

```typescript
interface OSCStatusResult {
  isListening: boolean;      // Whether currently listening
  listeningPort?: number;    // Port being listened on, if listening
}
```

## OSC Type System

The plugin supports the full OSC 1.0 type system. Primitive JS values are auto-detected; use `OSCTypedArg` for explicit control.

| JS Value | OSC Type | Tag | Notes |
|----------|----------|-----|-------|
| `42` (integer) | Int32 | `i` | Auto-detected via `Number.isInteger()` |
| `3.14` (float) | Float32 | `f` | Auto-detected (non-integer number) |
| `'hello'` | String | `s` | |
| `true` | True | `T` | |
| `false` | False | `F` | |
| `null` | Nil | `N` | |
| `{ type: 'i', value: 42 }` | Int32 | `i` | Explicit |
| `{ type: 'f', value: 1.0 }` | Float32 | `f` | Explicit -- forces float even for integer values |
| `{ type: 'd', value: 3.14159265358979 }` | Float64 | `d` | Explicit double precision |
| `{ type: 'h', value: 9007199254740992 }` | Int64 | `h` | Explicit 64-bit integer |
| `{ type: 's', value: 'text' }` | String | `s` | Explicit |
| `{ type: 'b', value: 'SGVsbG8=' }` | Blob | `b` | Base64-encoded binary data |
| `{ type: 'T' }` | True | `T` | Explicit (value ignored) |
| `{ type: 'F' }` | False | `F` | Explicit (value ignored) |
| `{ type: 'N' }` | Nil | `N` | Explicit (value ignored) |

### When to Use Explicit Types

Auto-detection works well for most cases. Use explicit `OSCTypedArg` when:

- You need `Float32` for an integer value (e.g., `{ type: 'f', value: 1 }` sends `1.0` as float)
- You need `Float64` double precision (not available via auto-detection)
- You need `Int64` for large integers (not available via auto-detection)
- You need to send binary `Blob` data

## Migration from cordova-plugin-osc

| cordova-plugin-osc | @mindfield/capacitor-osc | Notes |
|---------------------|--------------------------|-------|
| `new OSC()` | `import { OSC }` | Singleton, no instantiation needed |
| `osc.startListening(port, cb)` | `await OSC.startListening({ port })` | Promise-based, no callback |
| `osc.addListener(addr, cb)` | `await OSC.addListener('message', cb)` | Global listener; filter by address in JS |
| `osc.send({...})` | `await OSC.send({...})` | Similar structure, Promise return |
| `osc.stopListening()` | `await OSC.stopListening()` | |
| `osc.close()` | `await OSC.stopListening()` | Combined into one method |
| Callback error handling | `OSC.addListener('error', cb)` | Dedicated error event |

## Use Cases

- **Biofeedback** -- Stream eSense sensor data (EDA, temperature) to Max/MSP, Pure Data, or custom visualizers
- **VJing and Visuals** -- Control TouchDesigner, Resolume, or Processing sketches from a mobile device
- **Music Production** -- Send control data to Ableton Live, SuperCollider, Reaktor, or any DAW with OSC support
- **Interactive Installations** -- Bridge mobile sensors to desktop applications for real-time audience interaction
- **Research** -- Forward physiological measurements to lab software (LabVIEW, OpenViBE, custom tools)

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and contribution guidelines.

## License

MIT -- [Mindfield Biosystems Ltd.](https://mindfield.de)