@pulsefield/protocol 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,143 @@
1
+ // DO NOT EDIT.
2
+ // swift-format-ignore-file
3
+ // swiftlint:disable all
4
+ //
5
+ // Generated by the Swift generator plugin for the protocol buffer compiler.
6
+ // Source: pulsefield/protocol/v1/mapper.proto
7
+ //
8
+ // For information on using the generated types, please see the documentation:
9
+ // https://github.com/apple/swift-protobuf/
10
+
11
+ import SwiftProtobuf
12
+
13
+ // If the compiler emits an error on this type, it is because this file
14
+ // was generated by a version of the `protoc` Swift plug-in that is
15
+ // incompatible with the version of SwiftProtobuf to which you are linking.
16
+ // Please ensure that you are building against the same version of the API
17
+ // that was used to generate this file.
18
+ fileprivate nonisolated struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
19
+ struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
20
+ typealias Version = _2
21
+ }
22
+
23
+ public nonisolated struct Pulsefield_Protocol_V1_MapperStreamBeginEvent: Sendable {
24
+ // SwiftProtobuf.Message conformance is added in an extension below. See the
25
+ // `Message` and `Message+*Additions` files in the SwiftProtobuf library for
26
+ // methods supported on all messages.
27
+
28
+ public var tokenContractVersion: UInt32 = 0
29
+
30
+ public var audioLengthMs: UInt32 {
31
+ get {_audioLengthMs ?? 0}
32
+ set {_audioLengthMs = newValue}
33
+ }
34
+ /// Returns true if `audioLengthMs` has been explicitly set.
35
+ public var hasAudioLengthMs: Bool {self._audioLengthMs != nil}
36
+ /// Clears the value of `audioLengthMs`. Subsequent reads from it will return its default value.
37
+ public mutating func clearAudioLengthMs() {self._audioLengthMs = nil}
38
+
39
+ public var unknownFields = SwiftProtobuf.UnknownStorage()
40
+
41
+ public init() {}
42
+
43
+ fileprivate var _audioLengthMs: UInt32? = nil
44
+ }
45
+
46
+ public nonisolated struct Pulsefield_Protocol_V1_HitObjectTokenEvent: Sendable {
47
+ // SwiftProtobuf.Message conformance is added in an extension below. See the
48
+ // `Message` and `Message+*Additions` files in the SwiftProtobuf library for
49
+ // methods supported on all messages.
50
+
51
+ public var tokenID: UInt32 = 0
52
+
53
+ public var msInRefAudio: UInt32 = 0
54
+
55
+ public var tokenIndex: UInt32 = 0
56
+
57
+ public var unknownFields = SwiftProtobuf.UnknownStorage()
58
+
59
+ public init() {}
60
+ }
61
+
62
+ // MARK: - Code below here is support for the SwiftProtobuf runtime.
63
+
64
+ fileprivate nonisolated let _protobuf_package = "pulsefield.protocol.v1"
65
+
66
+ nonisolated extension Pulsefield_Protocol_V1_MapperStreamBeginEvent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
67
+ public static let protoMessageName: String = _protobuf_package + ".MapperStreamBeginEvent"
68
+ public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{4}\u{2}token_contract_version\0\u{3}audio_length_ms\0\u{b}route\0\u{c}\u{1}\u{1}")
69
+
70
+ public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
71
+ while let fieldNumber = try decoder.nextFieldNumber() {
72
+ // The use of inline closures is to circumvent an issue where the compiler
73
+ // allocates stack space for every case branch when no optimizations are
74
+ // enabled. https://github.com/apple/swift-protobuf/issues/1034
75
+ switch fieldNumber {
76
+ case 2: try { try decoder.decodeSingularUInt32Field(value: &self.tokenContractVersion) }()
77
+ case 3: try { try decoder.decodeSingularUInt32Field(value: &self._audioLengthMs) }()
78
+ default: break
79
+ }
80
+ }
81
+ }
82
+
83
+ public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
84
+ // The use of inline closures is to circumvent an issue where the compiler
85
+ // allocates stack space for every if/case branch local when no optimizations
86
+ // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
87
+ // https://github.com/apple/swift-protobuf/issues/1182
88
+ if self.tokenContractVersion != 0 {
89
+ try visitor.visitSingularUInt32Field(value: self.tokenContractVersion, fieldNumber: 2)
90
+ }
91
+ try { if let v = self._audioLengthMs {
92
+ try visitor.visitSingularUInt32Field(value: v, fieldNumber: 3)
93
+ } }()
94
+ try unknownFields.traverse(visitor: &visitor)
95
+ }
96
+
97
+ public static func ==(lhs: Pulsefield_Protocol_V1_MapperStreamBeginEvent, rhs: Pulsefield_Protocol_V1_MapperStreamBeginEvent) -> Bool {
98
+ if lhs.tokenContractVersion != rhs.tokenContractVersion {return false}
99
+ if lhs._audioLengthMs != rhs._audioLengthMs {return false}
100
+ if lhs.unknownFields != rhs.unknownFields {return false}
101
+ return true
102
+ }
103
+ }
104
+
105
+ nonisolated extension Pulsefield_Protocol_V1_HitObjectTokenEvent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
106
+ public static let protoMessageName: String = _protobuf_package + ".HitObjectTokenEvent"
107
+ public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}token_id\0\u{3}ms_in_ref_audio\0\u{3}token_index\0")
108
+
109
+ public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
110
+ while let fieldNumber = try decoder.nextFieldNumber() {
111
+ // The use of inline closures is to circumvent an issue where the compiler
112
+ // allocates stack space for every case branch when no optimizations are
113
+ // enabled. https://github.com/apple/swift-protobuf/issues/1034
114
+ switch fieldNumber {
115
+ case 1: try { try decoder.decodeSingularUInt32Field(value: &self.tokenID) }()
116
+ case 2: try { try decoder.decodeSingularUInt32Field(value: &self.msInRefAudio) }()
117
+ case 3: try { try decoder.decodeSingularUInt32Field(value: &self.tokenIndex) }()
118
+ default: break
119
+ }
120
+ }
121
+ }
122
+
123
+ public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
124
+ if self.tokenID != 0 {
125
+ try visitor.visitSingularUInt32Field(value: self.tokenID, fieldNumber: 1)
126
+ }
127
+ if self.msInRefAudio != 0 {
128
+ try visitor.visitSingularUInt32Field(value: self.msInRefAudio, fieldNumber: 2)
129
+ }
130
+ if self.tokenIndex != 0 {
131
+ try visitor.visitSingularUInt32Field(value: self.tokenIndex, fieldNumber: 3)
132
+ }
133
+ try unknownFields.traverse(visitor: &visitor)
134
+ }
135
+
136
+ public static func ==(lhs: Pulsefield_Protocol_V1_HitObjectTokenEvent, rhs: Pulsefield_Protocol_V1_HitObjectTokenEvent) -> Bool {
137
+ if lhs.tokenID != rhs.tokenID {return false}
138
+ if lhs.msInRefAudio != rhs.msInRefAudio {return false}
139
+ if lhs.tokenIndex != rhs.tokenIndex {return false}
140
+ if lhs.unknownFields != rhs.unknownFields {return false}
141
+ return true
142
+ }
143
+ }
package/package.json CHANGED
@@ -1,13 +1,65 @@
1
1
  {
2
2
  "name": "@pulsefield/protocol",
3
- "version": "0.0.1",
4
- "description": "",
5
- "main": "index.js",
3
+ "version": "0.0.3",
4
+ "description": "Shared Protocol Buffers contracts for Pulsefield clients and model services.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/Pulsefield/protocol.git"
8
+ },
9
+ "type": "module",
10
+ "license": "Apache-2.0",
11
+ "packageManager": "npm@11.12.1",
12
+ "engines": {
13
+ "node": ">=22.0.0",
14
+ "npm": ">=11.0.0"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public",
18
+ "registry": "https://registry.npmjs.org/"
19
+ },
20
+ "exports": {
21
+ "./proto/*": "./proto/*",
22
+ "./gen/swift/*": "./gen/swift/*",
23
+ "./gen/python/*": "./gen/python/*",
24
+ "./docs/*": "./docs/*",
25
+ "./package.json": "./package.json"
26
+ },
6
27
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
28
+ "tools": "node --version && npm --version && buf --version && prettier --version",
29
+ "generate": "npm run proto:generate",
30
+ "proto:format": "buf format -w proto",
31
+ "proto:lint": "buf lint",
32
+ "proto:generate": "buf generate && node tools/ensure-python-typed.mjs",
33
+ "generated:check": "node tools/check-generated.mjs",
34
+ "check": "buf lint && npm run generated:check",
35
+ "bootstrap:check": "npm run check && npm run python:check && npm run swift:build",
36
+ "python:check": "node tools/check-python-package.mjs",
37
+ "python:build": "python3 -m build --sdist --wheel --outdir dist/",
38
+ "swift:build": "swift build",
39
+ "format": "prettier --check .",
40
+ "format:write": "prettier --write ."
8
41
  },
9
- "keywords": [],
42
+ "keywords": [
43
+ "pulsefield",
44
+ "protobuf",
45
+ "protocol"
46
+ ],
10
47
  "author": "",
11
- "license": "ISC",
12
- "type": "commonjs"
48
+ "files": [
49
+ "proto",
50
+ "gen",
51
+ "docs",
52
+ "tools",
53
+ "buf.yaml",
54
+ "buf.gen.yaml",
55
+ "Package.swift",
56
+ "pyproject.toml",
57
+ "README.md",
58
+ "PROTOCOL_GUIDELINES.md",
59
+ "LICENSE"
60
+ ],
61
+ "devDependencies": {
62
+ "@bufbuild/buf": "1.70.0",
63
+ "prettier": "3.8.4"
64
+ }
13
65
  }
@@ -0,0 +1,57 @@
1
+ syntax = "proto3";
2
+
3
+ package pulsefield.protocol.v1;
4
+
5
+ // Capability advertised by a node before domain-specific traffic begins.
6
+ message NodeCapability {
7
+ NodeCapabilityKind kind = 1;
8
+
9
+ // Stable capability identifier, such as "mapper.tuple_tokens" or "hardware.input".
10
+ string name = 2;
11
+
12
+ // Capability-specific contract or implementation version.
13
+ string version = 3;
14
+ NodeCapabilityDirection direction = 4;
15
+ }
16
+
17
+ // Initial topology announcement for apps, services, tools, and hardware bridges.
18
+ message NodeHello {
19
+ // Stable node identifier within a Pulsefield graph or room.
20
+ string node_id = 1;
21
+
22
+ NodeRole role = 2;
23
+
24
+ repeated NodeCapability capabilities = 3;
25
+
26
+ // Protocol package version spoken by this node, such as "0.0.3".
27
+ string protocol_version = 4;
28
+
29
+ // Stable application or service identifier, such as "pulsefield.host".
30
+ string app_id = 5;
31
+ }
32
+
33
+ enum NodeRole {
34
+ NODE_ROLE_UNSPECIFIED = 0;
35
+ NODE_ROLE_HOST = 1;
36
+ NODE_ROLE_PLAYER_CLIENT = 2;
37
+ NODE_ROLE_MODEL_SERVICE = 3;
38
+ NODE_ROLE_HARDWARE = 4;
39
+ NODE_ROLE_DEBUG_TOOL = 5;
40
+ }
41
+
42
+ enum NodeCapabilityKind {
43
+ NODE_CAPABILITY_KIND_UNSPECIFIED = 0;
44
+ NODE_CAPABILITY_KIND_RECOGNITION = 1;
45
+ NODE_CAPABILITY_KIND_TIMING = 2;
46
+ NODE_CAPABILITY_KIND_MAPPER = 3;
47
+ NODE_CAPABILITY_KIND_PLAYER_STREAM = 4;
48
+ NODE_CAPABILITY_KIND_HARDWARE = 5;
49
+ NODE_CAPABILITY_KIND_DEBUG = 6;
50
+ }
51
+
52
+ enum NodeCapabilityDirection {
53
+ NODE_CAPABILITY_DIRECTION_UNSPECIFIED = 0;
54
+ NODE_CAPABILITY_DIRECTION_PRODUCER = 1;
55
+ NODE_CAPABILITY_DIRECTION_CONSUMER = 2;
56
+ NODE_CAPABILITY_DIRECTION_BIDIRECTIONAL = 3;
57
+ }
@@ -0,0 +1,49 @@
1
+ syntax = "proto3";
2
+
3
+ package pulsefield.protocol.v1;
4
+
5
+ import "pulsefield/protocol/v1/core.proto";
6
+ import "pulsefield/protocol/v1/inference.proto";
7
+ import "pulsefield/protocol/v1/mapper.proto";
8
+
9
+ // Transport-agnostic binary envelope shared by Pulsefield nodes.
10
+ message Envelope {
11
+ // Inference/play session identifier. Empty for node-level or graph-level messages.
12
+ string session_id = 1;
13
+
14
+ // Sender-local monotonic sequence number within a session. per source_node_id + session_id
15
+ uint64 sequence = 2;
16
+
17
+ // Sender wall-clock timestamp in Unix milliseconds, when available.
18
+ int64 sent_at_unix_ms = 3;
19
+
20
+ // Node that produced this envelope, when the transport has graph identity.
21
+ string source_node_id = 4;
22
+
23
+ // Intended recipient node. Empty means the session or transport decides routing.
24
+ string target_node_id = 5;
25
+
26
+ // globally unique enough, sender-generated
27
+ string message_id = 6;
28
+
29
+ // Related message or request identifier for response and stream correlation.
30
+ string correlation_id = 7;
31
+
32
+ oneof payload {
33
+ // Graph identity announcement, usually sent before domain-specific traffic.
34
+ NodeHello node_hello = 8;
35
+
36
+ ReadyRequest ready = 10;
37
+ AudioRequest audio = 11;
38
+ ReferenceTimeRequest reference_time = 12;
39
+ StopSessionRequest stop_session = 13;
40
+
41
+ // One mapper token event. The legacy JSON transport calls this hitobject_tokens.
42
+ MapperStreamBeginEvent mapper_stream_begin = 19;
43
+ HitObjectTokenEvent hit_object_token = 20;
44
+
45
+ ErrorEvent error = 21;
46
+ StatusEvent status = 22;
47
+ EndOfStreamEvent end_of_stream = 23;
48
+ }
49
+ }
@@ -0,0 +1,145 @@
1
+ syntax = "proto3";
2
+
3
+ package pulsefield.protocol.v1;
4
+
5
+ // Sent by a client to ask the endpoint to load models and become ready.
6
+ message ReadyRequest {}
7
+
8
+ message AudioAssetRef {
9
+ oneof ref {
10
+ string local_path = 1;
11
+ string uri = 2;
12
+ string asset_id = 3;
13
+ }
14
+
15
+ optional uint32 audio_length_ms = 10;
16
+ optional string sha256_hex = 11;
17
+ }
18
+
19
+ message AudioRequest {
20
+ AudioAssetRef audio = 1;
21
+ SyncSource sync_source = 2;
22
+ optional double difficulty = 3;
23
+ InferenceRoute route = 4;
24
+ }
25
+
26
+ // Sent by a client once ambient sync has produced a reference clock lock.
27
+ message ReferenceTimeRequest {
28
+ // Current playback/reference position in the selected audio, in milliseconds.
29
+ uint32 ref_time_ms = 1;
30
+
31
+ // Client monotonic host-clock timestamp captured when ref_time_ms was sent.
32
+ uint64 local_host_time_send_ms = 2;
33
+
34
+ // Duration check in milliseconds. When present, it must match AudioRequest.
35
+ optional uint32 audio_length_ms = 3;
36
+ }
37
+
38
+ // Sent by a client to stop and reset an active endpoint session.
39
+ message StopSessionRequest {
40
+ // Machine-readable stop reason such as "client_stop" or "peer_disconnect".
41
+ string reason = 1;
42
+ }
43
+
44
+ // Machine-readable endpoint failure plus a human-readable message.
45
+ message ErrorEvent {
46
+ // Stable string code retained for JSON compatibility with prototype clients.
47
+ string code = 1;
48
+
49
+ string message = 2;
50
+
51
+ // Preferred typed error code for new protobuf consumers.
52
+ InferenceErrorCode error_code = 3;
53
+
54
+ // Endpoint phase that failed, such as "prepare_audio" or "stream".
55
+ string phase = 4;
56
+
57
+ // Route active when the failure occurred, when known.
58
+ InferenceRoute route = 5;
59
+
60
+ // Compatibility bucket used by the current JSON endpoint, such as "inference".
61
+ string error_kind = 6;
62
+ }
63
+
64
+ // Endpoint lifecycle transition or current-status notification.
65
+ message StatusEvent {
66
+ // Target status after this notification.
67
+ EndpointStatus status = 1;
68
+
69
+ string message = 2;
70
+
71
+ // Reference-audio position associated with the status, when known.
72
+ optional uint32 ref_time_ms = 3;
73
+
74
+ // Sender-local monotonic host-clock timestamp in milliseconds, when known.
75
+ optional uint64 sender_monotonic_ms = 4;
76
+
77
+ // Previous status for transition logs.
78
+ EndpointStatus from_status = 5;
79
+
80
+ // Machine-readable transition reason such as "audio_path" or "reference_time".
81
+ string reason = 6;
82
+
83
+ // Selected audio duration associated with this transition, when known.
84
+ optional uint32 audio_length_ms = 7;
85
+
86
+ // Host-clock deadline for automatic cleanup after audio end, when scheduled.
87
+ optional uint64 reset_sender_monotonic_ms = 8;
88
+
89
+ // Route active for this status transition, when relevant.
90
+ InferenceRoute route = 9;
91
+
92
+ // User-facing difficulty associated with mapper preparation, when relevant.
93
+ optional double difficulty = 10;
94
+ }
95
+
96
+ // Sent by the model service after all tokens through complete_through_ms were emitted.
97
+ message EndOfStreamEvent {
98
+ // Selected audio duration in milliseconds.
99
+ optional uint32 audio_length_ms = 1;
100
+
101
+ // Reference-audio time through which the stream is complete.
102
+ uint32 complete_through_ms = 2;
103
+ }
104
+
105
+ enum SyncSource {
106
+ SYNC_SOURCE_UNSPECIFIED = 0;
107
+ SYNC_SOURCE_BACKGROUND = 1;
108
+ SYNC_SOURCE_SYSTEM_AUDIO = 2;
109
+ }
110
+
111
+ enum InferenceRoute {
112
+ INFERENCE_ROUTE_UNSPECIFIED = 0;
113
+ INFERENCE_ROUTE_MAPPER = 1;
114
+
115
+ // Timing-only route selected by the legacy JSON is_mock/isMock flag.
116
+ INFERENCE_ROUTE_TIMING_MOCK = 2;
117
+ }
118
+
119
+ enum EndpointStatus {
120
+ ENDPOINT_STATUS_UNSPECIFIED = 0;
121
+ ENDPOINT_STATUS_READY = 1;
122
+ ENDPOINT_STATUS_AUDIO_PREPARING = 2;
123
+ ENDPOINT_STATUS_AUDIO_READY = 3;
124
+ ENDPOINT_STATUS_STREAMING = 4;
125
+ ENDPOINT_STATUS_STOPPED = 5;
126
+ ENDPOINT_STATUS_FAILED = 6;
127
+
128
+ // Endpoint has not completed initial readiness startup.
129
+ ENDPOINT_STATUS_COLD = 7;
130
+
131
+ // No active session exists for the envelope session_id.
132
+ ENDPOINT_STATUS_NO_SESSION = 8;
133
+ }
134
+
135
+ enum InferenceErrorCode {
136
+ INFERENCE_ERROR_CODE_UNSPECIFIED = 0;
137
+ INFERENCE_ERROR_CODE_PROTOCOL_ERROR = 1;
138
+ INFERENCE_ERROR_CODE_INVALID_DEVICE = 2;
139
+ INFERENCE_ERROR_CODE_AUDIO_NOT_FOUND = 3;
140
+ INFERENCE_ERROR_CODE_INFERENCE_FAILED = 4;
141
+ INFERENCE_ERROR_CODE_SESSION_NOT_FOUND = 5;
142
+ INFERENCE_ERROR_CODE_SESSION_OWNERSHIP_CONFLICT = 6;
143
+ INFERENCE_ERROR_CODE_AUDIO_NOT_PREPARED = 7;
144
+ INFERENCE_ERROR_CODE_UNSUPPORTED_ROUTE = 8;
145
+ }
@@ -0,0 +1,17 @@
1
+ syntax = "proto3";
2
+
3
+ package pulsefield.protocol.v1;
4
+
5
+ message MapperStreamBeginEvent {
6
+ reserved 1;
7
+ reserved "route";
8
+
9
+ uint32 token_contract_version = 2;
10
+ optional uint32 audio_length_ms = 3;
11
+ }
12
+
13
+ message HitObjectTokenEvent {
14
+ uint32 token_id = 1;
15
+ uint32 ms_in_ref_audio = 2;
16
+ uint32 token_index = 3;
17
+ }
package/pyproject.toml ADDED
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pulsefield-protocol"
7
+ version = "0.0.3"
8
+ description = "Shared Protocol Buffers contracts for Pulsefield clients and model services."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "Apache-2.0"
12
+ license-files = ["LICENSE"]
13
+ authors = []
14
+ keywords = ["pulsefield", "protobuf", "protocol"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3 :: Only",
20
+ "Topic :: Software Development :: Libraries",
21
+ ]
22
+ dependencies = ["protobuf>=5.26.1,<6"]
23
+
24
+ [project.urls]
25
+ Homepage = "https://github.com/Pulsefield/protocol"
26
+ Repository = "https://github.com/Pulsefield/protocol"
27
+
28
+ [tool.setuptools]
29
+ package-dir = { "" = "gen/python" }
30
+ include-package-data = true
31
+
32
+ [tool.setuptools.packages.find]
33
+ where = ["gen/python"]
34
+ namespaces = true
35
+
36
+ [tool.setuptools.package-data]
37
+ "pulsefield.protocol" = ["py.typed"]
38
+ "pulsefield.protocol.v1" = ["*.pyi"]