Sign in

    moonosc

    Bounded Open Sound Control codecs, address dispatch and deterministic timed scheduling

    osc
    open-sound-control
    protocol
    routing
    streaming
    Download zip
    Author
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    8 hours ago
    Downloads
    4

    #MoonOSC

    A portable, bounded Open Sound Control library in MoonBit. It combines wire codecs, address-pattern dispatch, deterministic bundle scheduling, and incremental stream framing. It performs no socket IO and invokes no callbacks.

    #Install

    moon add CYang-dep/moonosc@0.1.0

    In your moon.pkg:

    import { "CYang-dep/moonosc" @osc }

    let packet = @osc.Message({ address: "/mixer/gain", arguments: [@osc.float_argument(0.5)] })
    let wire = @osc.encode(packet)
    let recovered = @osc.decode(wire)

    These functions raise OscError; handle errors at your application boundary. The complete public interface is pkg.generated.mbti.

    #Delivered workflows

    ConsumerInput and processingObservable resultRun
    Audio toolsTyped gain message, codec roundtrip, brace-pattern fanout, exact signature checksTwo accepted destinations; no callbacks before validationmoon run examples/audio
    Interactive mediaAbsolute timed bundle with nested immediate cues, heap queue, stable ticketsNo early output; both cues retain enclosing time and wire ordermoon run examples/media
    Device gatewaysSLIP input arriving one byte at a time, decoding and JSON diagnosticsOne reconstructed packet; exact replay including escaped bytesmoon run examples/device

    All examples are deterministic and assertion-backed. They require no devices, servers, credentials, or audio driver.

    #Capabilities

    • OSC messages and nested bundles; canonical big-endian fields and zero padding; strict full-payload consumption, type tags and bounded lengths/depth/counts.
    • Tags i f s b h d t S c r m T F N I and recursive [...] arrays. Floats are raw IEEE bits: NaN payloads and signed zero are preserved.
    • Non-NUL ASCII OSC strings; character arguments restricted to ASCII 0..127. This intentionally does not implement non-ASCII protocol extensions.
    • Compiled * ? [ranges] [!ranges] {literal,alternatives} patterns. Wildcards do not cross slashes. Brace choices must be nonempty literal segment strings; nested alternatives, recursive //, and generic regex are not supported.
    • Ordered method registry and signature-aware inert dispatch plans; fanout has a shared work budget. Default contract failure is all-or-nothing; partial plans are explicit. This is not an indexed router or executing RPC framework.
    • NTP-era-zero timetags, explicit Unix conversion (1900 through the 2036 rollover), checked tick arithmetic; nested immediate bundles inherit enclosing time.
    • Transactional scheduler admission, stable minheap, cancellation, polling, explicit exclusive late-event cutoff and clearing without ticket reuse.
    • Transactional size-prefix and SLIP feeds with bounded frame/chunk/output counts, split-header/escape support, EOF checks, reset and rollback on failure.
    • Strict hex utilities, validated structural summaries and moonosc/1 lossless JSON tagged arrays. All numeric values use fixed-width hex, never JSON numbers.

    #Validation

    moon info && moon fmt moon fmt --check moon check --deny-warn --target wasm-gc moon test --deny-warn --target wasm-gc moon build --deny-warn --target wasm-gc python scripts/reference_vectors.py --target wasm-gc python scripts/audit.py

    Repeat check/test/build/examples for wasm, js and native. Native execution requires a C toolchain (CI uses Ubuntu). CI installs the official stable toolchain on clean runners and tests all four targets. Local development was initially checked using Moon 0.1.20260920 and then migrated to the canonical Moon 0.1.20261012 syntax after the first real CI run. Use Moon 0.1.20261012 or newer; CI logs record the actual release.

    The test suite includes 256 full-model roundtrips with every shortened message prefix rejected, 240 wildcard pairs against a recursive short-input oracle, 512 queue mutations against a scan model, every split of both stream encodings, and 256 independently encoded Python stdlib message/bundle vectors. These are correctness checks, not performance claims or proof of complete conformance.

    #Boundaries

    This is a reusable library, not a complete OSC server. Users provide transports, clock synchronization, NTP-era choice, authentication, thread synchronization, application contracts, and callback execution. No hard-realtime guarantees, audio synthesis, DSP, discovery, delivery acknowledgement, clock drift model, or automatic competition registration. Malformed packets are rejected, not repaired.

    #License

    Original implementation under Apache-2.0, relying on MoonBit core only. Bounded ecosystem research and rejected alternatives are documented in duplicate-check; it is not a claim of uniqueness.

    OscError

    pub suberror OscError {
    Invalid(String, Int)
    Limit(String)
    } derive(Eq,
    Debug
    )

    OscError::equal

    fn OscError::equal(OscError, OscError) -> Bool

    OscError::not_equal

    fn OscError::not_equal(x : OscError, y : OscError) -> Bool

    OscError::to_repr

    Argument

    pub(all) enum Argument {
    I32(Int)
    F32(UInt)
    Text(String)
    Blob(Bytes)
    I64(Int64)
    F64(UInt64)
    Time(TimeTag)
    Symbol(String)
    Character(Int)
    Rgba(UInt)
    Midi(UInt)
    True
    False
    Nil
    Impulse
    Array(Array[Argument])
    } derive(Eq,
    Debug
    )

    Argument::equal

    fn Argument::equal(Argument, Argument) -> Bool

    Argument::not_equal

    fn Argument::not_equal(x : Argument, y : Argument) -> Bool

    Argument::to_repr

    DispatchPlan

    pub(all) struct DispatchPlan {
    accepted : Array[Route]
    rejected : Array[Rejection]
    } derive(Eq,
    Debug
    )

    DispatchPlan::equal

    DispatchPlan::not_equal

    fn DispatchPlan::not_equal(x : DispatchPlan, y : DispatchPlan) -> Bool

    Dispatcher

    pub struct Dispatcher {
    // private fields
    }

    Dispatcher::add

    fn Dispatcher::add(self : Dispatcher, id : Int, address : String, signature? : String?) -> Unit raise OscError

    Dispatcher::new

    fn Dispatcher::new(capacity? : Int) -> Dispatcher raise OscError

    Dispatcher::plan

    fn Dispatcher::plan(self : Dispatcher, message : Message, all_or_nothing? : Bool, work_limit? : Int, limits? : Limits) -> DispatchPlan raise OscError

    Limits

    pub(all) struct Limits {
    packet_bytes : Int
    string_bytes : Int
    blob_bytes : Int
    arguments : Int
    elements : Int
    depth : Int
    } derive(Eq,
    Debug
    )

    Limits::default

    fn Limits::default() -> Limits

    Limits::equal

    fn Limits::equal(Limits, Limits) -> Bool

    Limits::not_equal

    fn Limits::not_equal(x : Limits, y : Limits) -> Bool

    Limits::to_repr

    Message

    pub(all) struct Message {
    address : String
    arguments : Array[Argument]
    } derive(Eq,
    Debug
    )

    Message::equal

    fn Message::equal(Message, Message) -> Bool

    Message::not_equal

    fn Message::not_equal(x : Message, y : Message) -> Bool

    Message::to_repr

    Message::type_tags

    fn Message::type_tags(self : Message, limits? : Limits) -> String raise OscError

    Packet

    pub(all) enum Packet {
    Message(Message)
    Bundle(TimeTag, Array[Packet])
    } derive(Eq,
    Debug
    )

    Packet::equal

    fn Packet::equal(Packet, Packet) -> Bool

    Packet::not_equal

    fn Packet::not_equal(x : Packet, y : Packet) -> Bool

    Packet::to_repr

    PacketSummary

    pub(all) struct PacketSummary {
    wire_bytes : Int
    messages : Int
    bundles : Int
    arguments : Int
    array_arguments : Int
    blob_bytes : Int
    max_depth : Int
    } derive(Eq,
    Debug
    )

    PacketSummary::equal

    PacketSummary::not_equal

    fn PacketSummary::not_equal(x : PacketSummary, y : PacketSummary) -> Bool

    Pattern

    pub struct Pattern {
    // private fields
    }

    Pattern::matches

    fn Pattern::matches(self : Pattern, path : String, work_limit? : Int) -> Bool raise OscError

    Pattern::source

    fn Pattern::source(self : Pattern) -> String

    Rejection

    pub(all) struct Rejection {
    route : Route
    expected : String
    actual : String
    } derive(Eq,
    Debug
    )

    Rejection::equal

    fn Rejection::equal(Rejection, Rejection) -> Bool

    Rejection::not_equal

    fn Rejection::not_equal(x : Rejection, y : Rejection) -> Bool

    Route

    pub(all) struct Route {
    id : Int
    address : String
    } derive(Eq,
    Debug
    )

    Route::equal

    fn Route::equal(Route, Route) -> Bool

    Route::not_equal

    fn Route::not_equal(x : Route, y : Route) -> Bool

    Route::to_repr

    Router

    pub struct Router {
    // private fields
    }

    Router::add

    fn Router::add(self : Router, id : Int, address : String) -> Unit raise OscError

    Router::len

    fn Router::len(self : Router) -> Int

    Router::new

    fn Router::new(capacity? : Int) -> Router raise OscError

    Router::remove

    fn Router::remove(self : Router, id : Int) -> Bool

    Router::resolve

    fn Router::resolve(self : Router, address : String, work_limit? : Int) -> Array[Route] raise OscError

    The message supplies the pattern; registered destinations are literal paths.

    Router::snapshot

    fn Router::snapshot(self : Router) -> Array[Route]

    Scheduled

    pub(all) struct Scheduled {
    ticket : UInt64
    due : TimeTag
    message : Message
    } derive(Eq,
    Debug
    )

    Scheduled::equal

    fn Scheduled::equal(Scheduled, Scheduled) -> Bool

    Scheduled::not_equal

    fn Scheduled::not_equal(x : Scheduled, y : Scheduled) -> Bool

    Scheduler

    pub struct Scheduler {
    // private fields
    }

    Scheduler::cancel

    fn Scheduler::cancel(self : Scheduler, ticket : UInt64) -> Bool

    Cancel one ticket. Removing a non-root node restores the heap in both directions.

    Scheduler::clear

    fn Scheduler::clear(self : Scheduler) -> Int

    Clear pending messages without recycling ticket identities.

    Scheduler::discard_before

    fn Scheduler::discard_before(self : Scheduler, cutoff : TimeTag) -> Array[Scheduled] raise OscError

    Explicit late-event policy: discard events strictly before cutoff, returning them in stable order. Equality is retained. Validation happens before any mutation.

    Scheduler::enqueue

    fn Scheduler::enqueue(self : Scheduler, packet : Packet, now : TimeTag, limits? : Limits) -> Array[UInt64] raise OscError

    Admission is transactional: flatten and all capacities checked before mutation.

    Scheduler::len

    fn Scheduler::len(self : Scheduler) -> Int

    Scheduler::new

    fn Scheduler::new(capacity? : Int) -> Scheduler raise OscError

    Scheduler::next_due

    fn Scheduler::next_due(self : Scheduler) -> TimeTag?

    Scheduler::poll

    fn Scheduler::poll(self : Scheduler, now : TimeTag, max_count? : Int) -> Array[Scheduled] raise OscError

    SizeFramer

    pub struct SizeFramer {
    // private fields
    }

    Size-prefix framing only: returned frames still require decode().

    SizeFramer::buffered

    fn SizeFramer::buffered(self : SizeFramer) -> Int

    SizeFramer::feed

    fn SizeFramer::feed(self : SizeFramer, chunk : Bytes) -> Array[Bytes] raise OscError

    Entire feed is transactional, including completed frames before a bad header. On failure no input is accepted: retry corrected bytes or explicitly reset().

    SizeFramer::finish

    fn SizeFramer::finish(self : SizeFramer) -> Unit raise OscError

    SizeFramer::new

    fn SizeFramer::new(max_frame? : Int, max_feed? : Int, max_frames? : Int) -> SizeFramer raise OscError

    SizeFramer::reset

    fn SizeFramer::reset(self : SizeFramer) -> Unit

    SlipFramer

    pub struct SlipFramer {
    // private fields
    }

    RFC 1055 SLIP byte-stuffing, constrained to nonempty aligned OSC frames.

    SlipFramer::buffered

    fn SlipFramer::buffered(self : SlipFramer) -> Int

    SlipFramer::feed

    fn SlipFramer::feed(self : SlipFramer, chunk : Bytes) -> Array[Bytes] raise OscError

    Empty ENDs are synchronization markers. Invalid escapes roll back the whole feed.

    SlipFramer::finish

    fn SlipFramer::finish(self : SlipFramer) -> Unit raise OscError

    SlipFramer::new

    fn SlipFramer::new(max_frame? : Int, max_feed? : Int, max_frames? : Int) -> SlipFramer raise OscError

    SlipFramer::reset

    fn SlipFramer::reset(self : SlipFramer) -> Unit

    TimeTag

    pub(all) struct TimeTag {
    seconds : UInt
    fraction : UInt
    } derive(Eq,
    Debug
    )

    TimeTag::add_ticks

    fn TimeTag::add_ticks(self : TimeTag, ticks : UInt64) -> TimeTag raise OscError

    TimeTag::bits

    fn TimeTag::bits(self : TimeTag) -> UInt64

    TimeTag::distance_ticks

    fn TimeTag::distance_ticks(self : TimeTag, later : TimeTag) -> UInt64 raise OscError

    TimeTag::equal

    fn TimeTag::equal(TimeTag, TimeTag) -> Bool

    TimeTag::from_bits

    fn TimeTag::from_bits(bits : UInt64) -> TimeTag

    TimeTag::from_unix

    fn TimeTag::from_unix(seconds : Int64, nanoseconds? : Int) -> TimeTag raise OscError

    Era 0 only, floors nanoseconds to the preceding 2^-32-second tick.

    TimeTag::immediate

    fn TimeTag::immediate() -> TimeTag

    TimeTag::is_immediate

    fn TimeTag::is_immediate(self : TimeTag) -> Bool

    TimeTag::not_equal

    fn TimeTag::not_equal(x : TimeTag, y : TimeTag) -> Bool

    TimeTag::resolve

    fn TimeTag::resolve(self : TimeTag, now : TimeTag) -> TimeTag raise OscError

    TimeTag::to_repr

    TimeTag::to_unix

    fn TimeTag::to_unix(self : TimeTag) -> (Int64, Int) raise OscError

    TimedMessage

    pub(all) struct TimedMessage {
    due : TimeTag
    message : Message
    order : Int
    } derive(Eq,
    Debug
    )

    TimedMessage::equal

    TimedMessage::not_equal

    fn TimedMessage::not_equal(x : TimedMessage, y : TimedMessage) -> Bool

    compile_pattern

    fn compile_pattern(text : String) -> Pattern raise OscError

    decode

    fn decode(bytes : Bytes, limits? : Limits) -> Packet raise OscError

    decode_message

    fn decode_message(bytes : Bytes, limits? : Limits) -> Message raise OscError

    double_argument

    fn double_argument(value : Double) -> Argument

    encode

    fn encode(packet : Packet, limits? : Limits) -> Bytes raise OscError

    encode_message

    fn encode_message(message : Message, limits? : Limits) -> Bytes raise OscError

    flatten

    fn flatten(packet : Packet, now : TimeTag, limits? : Limits) -> Array[TimedMessage] raise OscError

    Returns owned message trees in depth-first wire order, not sorted due order.

    float_argument

    fn float_argument(value : Float) -> Argument

    from_json

    fn from_json(value : Json, limits? : Limits) -> Packet raise OscError

    hex_decode

    fn hex_decode(text : String, max_bytes? : Int) -> Bytes raise OscError

    hex_encode

    fn hex_encode(bytes : Bytes, max_bytes? : Int) -> String raise OscError

    matches

    fn matches(pattern : String, path : String, work_limit? : Int) -> Bool raise OscError

    parse_json

    fn parse_json(text : String, limits? : Limits) -> Packet raise OscError

    Text parsing has both a byte/code-unit cap and parser nesting cap.

    size_frame

    fn size_frame(bytes : Bytes, max_frame? : Int) -> Bytes raise OscError

    slip_frame

    fn slip_frame(bytes : Bytes, max_frame? : Int) -> Bytes raise OscError

    Adds leading and trailing END for synchronization; worst case 2*n+2 bytes.

    summarize

    fn summarize(packet : Packet, limits? : Limits) -> PacketSummary raise OscError

    First validates all budgets; never inspect untrusted models recursively first.

    to_json

    fn to_json(packet : Packet, limits? : Limits) -> Json raise OscError

    Versioned tagged-array schema. Integers and IEEE/NTP bits are fixed-width hex, never JSON numbers: NaN payloads, signed zero and all 64-bit values survive.

    validate_address

    fn validate_address(address : String) -> Unit raise OscError

    validate_method

    fn validate_method(address : String) -> Unit raise OscError

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io