@swiftbrowser/native 0.19.0 → 0.23.0

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.
Files changed (66) hide show
  1. package/README.md +34 -11
  2. package/dist/blob.d.ts +85 -11
  3. package/dist/blob.d.ts.map +1 -1
  4. package/dist/blob.js +184 -34
  5. package/dist/blob.js.map +1 -1
  6. package/dist/bridge.d.ts +33 -2
  7. package/dist/bridge.d.ts.map +1 -1
  8. package/dist/bridge.js.map +1 -1
  9. package/dist/fetch.d.ts +19 -11
  10. package/dist/fetch.d.ts.map +1 -1
  11. package/dist/fetch.js +105 -41
  12. package/dist/fetch.js.map +1 -1
  13. package/dist/hostRequests.d.ts +3 -1
  14. package/dist/hostRequests.d.ts.map +1 -1
  15. package/dist/hostRequests.js +47 -6
  16. package/dist/hostRequests.js.map +1 -1
  17. package/dist/index.d.ts +1 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/runtime.js +1 -1
  22. package/dist/runtime.js.map +1 -1
  23. package/dist/webSocket.d.ts.map +1 -1
  24. package/dist/webSocket.js +13 -1
  25. package/dist/webSocket.js.map +1 -1
  26. package/package.json +3 -3
  27. package/src/blob.ts +215 -34
  28. package/src/bridge.ts +27 -2
  29. package/src/fetch.ts +99 -41
  30. package/src/hostRequests.ts +62 -8
  31. package/src/index.ts +1 -2
  32. package/src/runtime.ts +1 -1
  33. package/src/webSocket.ts +10 -1
  34. package/swift/Packages/SwiftBrowserDSP/Sources/SwiftBrowserDSP/ProgramNode.swift +185 -24
  35. package/swift/Packages/SwiftBrowserDSP/Sources/SwiftBrowserDSP/ScopeView.swift +79 -0
  36. package/swift/Packages/SwiftBrowserDSP/Sources/SwiftBrowserDSPCore/Machine.swift +2075 -213
  37. package/swift/Packages/SwiftBrowserDSP/Sources/SwiftBrowserDSPCore/Program.swift +407 -16
  38. package/swift/Packages/SwiftBrowserShader/Package.swift +31 -0
  39. package/swift/Packages/SwiftBrowserShader/README.md +45 -0
  40. package/swift/Packages/SwiftBrowserShader/Sources/SwiftBrowserShader/ShaderRenderer.swift +103 -0
  41. package/swift/Packages/SwiftBrowserShader/Sources/SwiftBrowserShader/ShaderView.swift +168 -0
  42. package/swift/Packages/SwiftBrowserShader/Sources/SwiftBrowserShaderCore/ShaderProps.swift +120 -0
  43. package/swift/Sources/SwiftBrowserNative/Host/BodyStream.swift +166 -0
  44. package/swift/Sources/SwiftBrowserNative/Host/FilesBridge.swift +141 -7
  45. package/swift/Sources/SwiftBrowserNative/Host/HostBridge.swift +47 -1
  46. package/swift/Sources/SwiftBrowserNative/Host/HostRequestBridge.swift +98 -29
  47. package/swift/Sources/SwiftBrowserNative/Runtime/NativeFunctions.swift +1 -1
  48. package/swift/Sources/SwiftBrowserNative/Runtime/ViewRegistry.swift +57 -0
  49. package/swift/Sources/SwiftBrowserNative/Views/ElementView.swift +11 -7
  50. package/swift/Sources/SwiftBrowserNative/Views/Kinds/Controls.swift +28 -6
  51. package/swift/Sources/SwiftBrowserNative/Views/Kinds/Drawing.swift +378 -0
  52. package/swift/Sources/SwiftBrowserNative/Views/Kinds/Navigation.swift +15 -10
  53. package/swift/Sources/SwiftBrowserNative/Views/Kinds/PopTap.swift +31 -28
  54. package/swift/Sources/SwiftBrowserNative/Views/StyleModifier.swift +142 -0
  55. package/swift/Sources/SwiftBrowserProtocol/Drawing.swift +306 -0
  56. package/swift/Sources/SwiftBrowserProtocol/Props.swift +5 -2
  57. package/swift/Sources/SwiftBrowserProtocol/RendererEvent.swift +24 -3
  58. package/swift/Sources/SwiftBrowserProtocol/Style.swift +87 -0
  59. package/swift/Sources/SwiftBrowserProtocol/TextChange.swift +17 -0
  60. package/wasm/SwiftBrowserNative.wasm +2 -2
  61. package/dist/native.d.ts +0 -19
  62. package/dist/native.d.ts.map +0 -1
  63. package/dist/native.js +0 -17
  64. package/dist/native.js.map +0 -1
  65. package/src/native.ts +0 -35
  66. package/swift/Sources/SwiftBrowserNative/Runtime/NativeRegistry.swift +0 -53
@@ -6,6 +6,10 @@ import Foundation
6
6
  /// voice; `block` runs once per block of up to 128 frames and `sample` once
7
7
  /// per frame, each a flat list of instructions: an opcode, a destination
8
8
  /// register, the opcode's operands and, for a stateful builtin, its instance.
9
+ /// A voice may have a right side (`outputRight`), and the `master` stage
10
+ /// runs after the voices (in lane 0). The machine runs `sample` and `master`
11
+ /// a chunk of frames at a time, all but the parts that touch state, which it
12
+ /// schedules to run frame by frame when it loads them (block processing).
9
13
  public struct DSPProgram: Sendable, Equatable {
10
14
  public struct Parameter: Sendable, Equatable {
11
15
  public let name: String
@@ -19,30 +23,146 @@ public struct DSPProgram: Sendable, Equatable {
19
23
  public let name: String
20
24
  public let register: Int
21
25
  public let initial: Float
26
+ /// The master stage's: reset when the program loads and on `reset`, not when a voice starts.
27
+ public var master = false
28
+ }
29
+
30
+ /// A wavetable: its frames, each the amplitudes of its sine harmonics from the fundamental up, and with
31
+ /// `cosines` (one list a frame, each as long as its frame's) their cosine parts.
32
+ public struct Table: Sendable, Equatable {
33
+ public let name: String
34
+ public let frames: [[Float]]
35
+ public var cosines: [[Float]]? = nil
36
+ }
37
+
38
+ /// A drawn curve: values at evenly spaced points from x = 0 to x = 1.
39
+ public struct Shape: Sendable, Equatable {
40
+ public let name: String
41
+ public let points: [Float]
42
+ }
43
+
44
+ /// Audio: mono, `rate` samples a second.
45
+ public struct Sample: Sendable, Equatable {
46
+ public let name: String
47
+ public let rate: Double
48
+ public let data: [Float]
49
+ }
50
+
51
+ /// The master stage: the voices' sum goes into `inputs` (left, right) each frame, `code` runs in lane 0, and
52
+ /// `outputs` is the program's output. Its notes are the newest one's (registers 7 … 10), and its envelopes start
53
+ /// over at every note.
54
+ public struct Master: Sendable, Equatable {
55
+ public let code: [Int32]
56
+ public let inputs: [Int]
57
+ public let outputs: [Int]
58
+ }
59
+
60
+ /// One voice playing the newest held note: `mono` starts its envelopes over at each note, `legato` only when
61
+ /// no other note was held.
62
+ public enum Mode: String, Sendable, Equatable {
63
+ case mono, legato
22
64
  }
23
65
 
24
66
  public let voices: Int
67
+ /// Absent, polyphonic.
68
+ public let mode: Mode?
69
+ /// The voicing as parameters (program.ts `voicing`, their indices), read at each note: how many voices play
70
+ /// (rounded, 1 … `voices`), and the mode (rounded: 0 as many, 1 mono, 2 legato) in place of `mode`.
71
+ public let voicingVoices: Int?
72
+ public let voicingMode: Int?
25
73
  public let registers: Int
26
74
  public let constants: [(register: Int, value: Float)]
27
75
  public let parameters: [Parameter]
28
76
  public let state: [State]
29
77
  public let instances: [DSPBuiltin]
78
+ public let tables: [Table]
79
+ public let shapes: [Shape]
80
+ public let samples: [Sample]
81
+ /// `modsum`'s route lists: a source's register, then its amount's, pair after pair.
82
+ public let routes: [[Int]]
83
+ /// Envelopes with curves (program.ts `curves`): an `adsr` instance and its attack, decay and release exponents.
84
+ public let curves: [Curve]
30
85
  public let block: [Int32]
31
86
  public let sample: [Int32]
87
+ /// The voice's left side, or both with no `outputRight`.
32
88
  public let output: Int
89
+ public let outputRight: Int?
90
+ public let master: Master?
91
+ /// Gated regions (program.ts `groups`): each one's parameter, the region it is inside (-1 none) and its results.
92
+ public let groups: [Gate]
93
+ /// Each instruction's region in `sample` and `master` (-1 none).
94
+ public let sampleGates: [Int32]?
95
+ public let masterGates: [Int32]?
96
+ /// Choices (program.ts `choices`): each a register read as one of its options, the one its parameter names.
97
+ public let choices: [Choice]
98
+
99
+ /// A choice: `register`, which no instruction writes, read as `options[round(parameter's set value × scale)]`
100
+ /// (clamped). What no choice picks, nor anything else needs, the machine does not run.
101
+ public struct Choice: Sendable, Equatable {
102
+ public let parameter: Int
103
+ public let register: Int
104
+ public let options: [Int]
105
+ /// What the parameter's value is multiplied by before it is rounded to an option.
106
+ public var scale: Double = 1
107
+ }
108
+
109
+ /// The most options a choice has.
110
+ public static let choiceOptionsMax = 256
111
+
112
+ /// A region: it runs while `parameter` (its index) is set above 0.5 and the region around it runs.
113
+ public struct Gate: Sendable, Equatable {
114
+ public let parameter: Int
115
+ public let parent: Int
116
+ public let outputs: [Output]
117
+ }
118
+
119
+ /// A region's result: a register no instruction writes, read as `on` while the region runs and `off` while not.
120
+ public struct Output: Sendable, Equatable {
121
+ public let register: Int
122
+ public let on: Int
123
+ public let off: Int
124
+ }
33
125
 
34
126
  public static func == (a: DSPProgram, b: DSPProgram) -> Bool {
35
- a.voices == b.voices && a.registers == b.registers && a.constants.map(\.register) == b.constants.map(\.register)
127
+ a.voices == b.voices && a.mode == b.mode && a.registers == b.registers && a.constants.map(\.register) == b.constants.map(\.register)
36
128
  && a.constants.map(\.value) == b.constants.map(\.value) && a.parameters == b.parameters && a.state == b.state
37
- && a.instances == b.instances && a.block == b.block && a.sample == b.sample && a.output == b.output
129
+ && a.instances == b.instances && a.tables == b.tables && a.shapes == b.shapes && a.samples == b.samples && a.routes == b.routes && a.curves == b.curves
130
+ && a.block == b.block && a.sample == b.sample && a.output == b.output && a.outputRight == b.outputRight && a.master == b.master
38
131
  }
39
132
 
40
133
  public static let format = "swiftbrowser-dsp"
134
+ /// The format's version: the machine runs only its own.
41
135
  public static let version = 1
42
136
  public static let lanes = 16
43
137
  public static let blockFrames = 128
44
- /// The machine's own registers: `sampleRate`, then a voice's `freq`, `gate`, `velocity` and `note`.
45
- public static let machineRegisters = 5
138
+ /// The machine's own registers: `sampleRate`, a voice's `freq`, `gate`, `velocity` and `note`, then the
139
+ /// transport's `tempo` and `beat`.
140
+ public static let machineRegisters = 11
141
+ /// Samples a wavetable frame has, at each of its band-limited levels (program.ts `TABLE_FRAME`, `TABLE_LEVELS`).
142
+ public static let tableFrame = 2048
143
+ public static let tableLevels = 10
144
+ /// The most harmonics a frame gives, and frames a program's tables have between them.
145
+ public static let tableHarmonics = 512
146
+ public static let tableFramesMax = 256
147
+ /// The bounds of an envelope curve's exponents (program.ts `CURVE_MIN`, `CURVE_MAX`).
148
+ public static let curveRange: ClosedRange<Float> = 1.0 / 16 ... 16
149
+
150
+ /// An envelope's curves: the exponents its attack, decay and release are bent by (1 as without).
151
+ public struct Curve: Equatable, Sendable {
152
+ public let instance: Int
153
+ /// The registers its exponents are read from, each frame (held to 1/16 … 16).
154
+ public let attack: Int
155
+ public let decay: Int
156
+ public let release: Int
157
+ }
158
+ /// The longest a delay may be, in seconds.
159
+ public static let delaySecondsMax: Float = 2
160
+ /// Points a shape has, and its shapes between them; samples a program's samples have between them.
161
+ public static let shapePointsMax = 4096
162
+ public static let shapesPointsMax = 65_536
163
+ public static let samplesMax = 8_000_000
164
+ /// The most registers a route list has (128 routes): a machine keeps room for a list this long.
165
+ public static let routesMax = 256
46
166
 
47
167
  /// The program the JSON describes, checked: every register, operand and instance in range.
48
168
  public init(json: String) throws {
@@ -64,8 +184,16 @@ public struct DSPProgram: Sendable, Equatable {
64
184
  public init(object: Any) throws {
65
185
  guard let root = object as? [String: Any] else { throw DSPProgramError("not an object") }
66
186
  guard root["format"] as? String == Self.format else { throw DSPProgramError("format is not \(Self.format)") }
67
- guard Self.int(root["version"]) == Self.version else { throw DSPProgramError("version is not \(Self.version)") }
187
+ guard Self.int(root["version"]) == Self.version else {
188
+ throw DSPProgramError("version \(root["version"].map { "\($0)" } ?? "none") is not \(Self.version)")
189
+ }
68
190
  guard let voices = Self.int(root["voices"]), (1...Self.lanes).contains(voices) else { throw DSPProgramError("voices must be 1 … \(Self.lanes)") }
191
+ var mode: Mode?
192
+ if let value = root["mode"] {
193
+ guard let name = value as? String, let parsed = Mode(rawValue: name) else { throw DSPProgramError("mode is not mono or legato") }
194
+ guard voices == 1 else { throw DSPProgramError("a mono or legato program has 1 voice") }
195
+ mode = parsed
196
+ }
69
197
  guard let registers = Self.int(root["registers"]), (Self.machineRegisters...65_536).contains(registers) else { throw DSPProgramError("registers out of range") }
70
198
  func register(_ value: Any?, _ what: String) throws -> Int {
71
199
  guard let r = Self.int(value), r >= 0, r < registers else { throw DSPProgramError("\(what): register out of range") }
@@ -101,9 +229,120 @@ public struct DSPProgram: Sendable, Equatable {
101
229
  guard let state = root["state"] as? [[String: Any]] else { throw DSPProgramError("state missing") }
102
230
  let parsedState = try state.map { s -> State in
103
231
  let name = s["name"] as? String ?? "state"
104
- return State(name: name, register: try register(s["register"], "state \(name)"), initial: try number(s["initial"], "state \(name)'s initial value"))
232
+ return State(
233
+ name: name, register: try register(s["register"], "state \(name)"), initial: try number(s["initial"], "state \(name)'s initial value"),
234
+ master: s["master"] as? Bool ?? false
235
+ )
105
236
  }
106
237
  let output = try register(root["output"], "output")
238
+ let outputRight = try root["outputRight"].map { try register($0, "outputRight") }
239
+ var masterParts: (inputs: [Int], outputs: [Int])?
240
+ if root["master"] != nil {
241
+ guard let inputs = root["masterInputs"] as? [Any], inputs.count == 2 else { throw DSPProgramError("master needs masterInputs [left, right]") }
242
+ guard let outputs = root["masterOutput"] as? [Any], outputs.count == 2 else { throw DSPProgramError("master needs masterOutput [left, right]") }
243
+ masterParts = (try inputs.map { try register($0, "master") }, try outputs.map { try register($0, "master") })
244
+ }
245
+ var parsedShapes: [Shape] = []
246
+ if let shapes = root["shapes"] {
247
+ guard let list = shapes as? [[String: Any]] else { throw DSPProgramError("shapes must be an array") }
248
+ var points = 0
249
+ for shape in list {
250
+ guard let name = shape["name"] as? String, let values = shape["points"] as? [Any], (2...Self.shapePointsMax).contains(values.count) else {
251
+ throw DSPProgramError("a shape is { name, points }, 2 … \(Self.shapePointsMax) numbers")
252
+ }
253
+ let parsed = try values.map { value in
254
+ let point = try number(value, "shape \(name)'s point")
255
+ guard point.isFinite else { throw DSPProgramError("shape \(name): a point is not finite") }
256
+ return point
257
+ }
258
+ points += parsed.count
259
+ parsedShapes.append(Shape(name: name, points: parsed))
260
+ }
261
+ guard points <= Self.shapesPointsMax else { throw DSPProgramError("the shapes have more than \(Self.shapesPointsMax) points between them") }
262
+ }
263
+ var parsedSamples: [Sample] = []
264
+ if let samples = root["samples"] {
265
+ guard let list = samples as? [[String: Any]] else { throw DSPProgramError("samples must be an array") }
266
+ var total = 0
267
+ for sample in list {
268
+ guard let name = sample["name"] as? String, let text = sample["data"] as? String, let rate = (sample["rate"] as? NSNumber)?.doubleValue,
269
+ rate >= 1000, rate <= 384_000
270
+ else { throw DSPProgramError("a sample is { name, rate, data }") }
271
+ // Base64 of 32-bit little-endian floats; at least two.
272
+ guard let bytes = Data(base64Encoded: text), bytes.count % 4 == 0, bytes.count >= 8 else {
273
+ throw DSPProgramError("sample \(name)'s data is not base64 of at least 2 32-bit floats")
274
+ }
275
+ total += bytes.count / 4
276
+ guard total <= Self.samplesMax else { throw DSPProgramError("the samples have more than \(Self.samplesMax) samples between them") }
277
+ let data = bytes.withUnsafeBytes { raw in
278
+ (0..<(bytes.count / 4)).map { Float(bitPattern: UInt32(littleEndian: raw.loadUnaligned(fromByteOffset: $0 * 4, as: UInt32.self))) }
279
+ }
280
+ parsedSamples.append(Sample(name: name, rate: rate, data: data))
281
+ }
282
+ }
283
+ var parsedTables: [Table] = []
284
+ if let tables = root["tables"] {
285
+ guard let list = tables as? [[String: Any]] else { throw DSPProgramError("tables must be an array") }
286
+ var frameCount = 0
287
+ for table in list {
288
+ guard let name = table["name"] as? String, let frames = table["frames"] as? [[Any]], !frames.isEmpty else { throw DSPProgramError("a table is { name, frames }") }
289
+ let parsedFrames = try frames.map { frame -> [Float] in
290
+ guard !frame.isEmpty, frame.count <= Self.tableHarmonics else { throw DSPProgramError("table \(name): a frame is 1 … \(Self.tableHarmonics) harmonic amplitudes") }
291
+ return try frame.map { value in
292
+ let amplitude = try number(value, "table \(name)'s amplitude")
293
+ guard amplitude.isFinite else { throw DSPProgramError("table \(name): an amplitude is not finite") }
294
+ return amplitude
295
+ }
296
+ }
297
+ var parsedCosines: [[Float]]?
298
+ if let cosines = table["cosines"] {
299
+ guard let lists = cosines as? [[Any]], lists.count == parsedFrames.count else { throw DSPProgramError("table \(name): cosines are one list a frame") }
300
+ parsedCosines = try lists.enumerated().map { f, list in
301
+ guard list.count == parsedFrames[f].count else { throw DSPProgramError("table \(name): a frame's cosines are as many numbers as its sines") }
302
+ return try list.map { value in
303
+ let amplitude = try number(value, "table \(name)'s cosine")
304
+ guard amplitude.isFinite else { throw DSPProgramError("table \(name): a cosine is not finite") }
305
+ return amplitude
306
+ }
307
+ }
308
+ }
309
+ frameCount += parsedFrames.count
310
+ parsedTables.append(Table(name: name, frames: parsedFrames, cosines: parsedCosines))
311
+ }
312
+ guard frameCount <= Self.tableFramesMax else { throw DSPProgramError("the tables have more than \(Self.tableFramesMax) frames between them") }
313
+ }
314
+ var parsedRoutes: [[Int]] = []
315
+ if let routes = root["routes"] {
316
+ guard let lists = routes as? [[Any]] else { throw DSPProgramError("routes must be an array of lists") }
317
+ parsedRoutes = try lists.map { list in
318
+ guard list.count % 2 == 0 else { throw DSPProgramError("a route list is source and amount registers, pair after pair") }
319
+ guard list.count <= Self.routesMax else { throw DSPProgramError("a route list has more than \(Self.routesMax) registers") }
320
+ let registers = try list.map { try register($0, "route") }
321
+ // An amount is a parameter, the same in every lane: the machine tests it once for all of them.
322
+ for k in stride(from: 1, to: registers.count, by: 2) where !parsedParameters.contains(where: { $0.register == registers[k] }) {
323
+ throw DSPProgramError("route amount register \(registers[k]) is not a parameter")
324
+ }
325
+ return registers
326
+ }
327
+ }
328
+ var parsedCurves: [Curve] = []
329
+ if let curves = root["curves"] {
330
+ guard let entries = curves as? [[Any]] else { throw DSPProgramError("curves must be an array") }
331
+ parsedCurves = try entries.map { entry in
332
+ guard entry.count == 4, let instance = Self.int(entry[0]), instances.indices.contains(instance), instances[instance] == .adsr else {
333
+ throw DSPProgramError("a curve is [instance, attack, decay, release] for an adsr")
334
+ }
335
+ let exponents = try entry.dropFirst().map { try register($0, "curve") }
336
+ return Curve(instance: instance, attack: exponents[0], decay: exponents[1], release: exponents[2])
337
+ }
338
+ }
339
+ let constantValues = Dictionary(parsedConstants.map { ($0.register, $0.value) }, uniquingKeysWith: { _, last in last })
340
+ let stateRegisters = Set(parsedState.map(\.register))
341
+ for r in [output, outputRight].compactMap({ $0 }) + (masterParts?.outputs ?? []) where stateRegisters.contains(r) {
342
+ throw DSPProgramError("output register \(r) is state")
343
+ }
344
+ /// Every register an instruction writes.
345
+ var written = Set<Int>()
107
346
  func code(_ key: String) throws -> [Int32] {
108
347
  guard let words = root[key] as? [Any] else { throw DSPProgramError("\(key) must be an array") }
109
348
  let code = try words.map { word -> Int32 in
@@ -116,25 +355,134 @@ public struct DSPProgram: Sendable, Equatable {
116
355
  let length = 2 + op.arity + (op.builtin == nil ? 0 : 1)
117
356
  guard pc + length <= code.count else { throw DSPProgramError("\(key): \(op) at \(pc) is cut off") }
118
357
  for i in 1..<(2 + op.arity) { _ = try register(Int(code[pc + i]), "\(key) \(op) at \(pc)") }
358
+ written.insert(Int(code[pc + 1]))
119
359
  if let builtin = op.builtin {
120
360
  let instance = Int(code[pc + length - 1])
121
361
  guard instances.indices.contains(instance), instances[instance] == builtin else {
122
362
  throw DSPProgramError("\(key): \(op) at \(pc) names instance \(instance), not a \(builtin)")
123
363
  }
124
364
  }
365
+ // What sizes memory comes from a constant register.
366
+ if op == .wavetable || op == .tableread || op == .shape || op == .play || op == .loop || op == .grains {
367
+ let (count, what) = op == .wavetable || op == .tableread ? (parsedTables.count, "a table") : op == .shape ? (parsedShapes.count, "a shape") : (parsedSamples.count, "a sample")
368
+ guard let index = constantValues[Int(code[pc + 2])], index.rounded() == index, index >= 0, index < Float(count) else {
369
+ throw DSPProgramError("\(key): \(op) at \(pc) does not name \(what)")
370
+ }
371
+ } else if op == .modsum {
372
+ guard let index = constantValues[Int(code[pc + 2])], index.rounded() == index, index >= 0, index < Float(parsedRoutes.count) else {
373
+ throw DSPProgramError("\(key): modsum at \(pc) does not name a route list")
374
+ }
375
+ // It sums into its destination route by route, so the destination is none of the registers it reads.
376
+ guard !parsedRoutes[Int(index)].contains(Int(code[pc + 1])) else {
377
+ throw DSPProgramError("\(key): modsum at \(pc) writes a register its routes read")
378
+ }
379
+ } else if let longestAt = op.longest {
380
+ guard let longest = constantValues[Int(code[pc + 1 + longestAt])], longest > 0, longest <= Self.delaySecondsMax else {
381
+ throw DSPProgramError("\(key): \(op) at \(pc)'s longest time is not a constant up to \(Self.delaySecondsMax) s")
382
+ }
383
+ }
125
384
  pc += length
126
385
  }
127
386
  return code
128
387
  }
129
388
  self.voices = voices
389
+ self.mode = mode
130
390
  self.registers = registers
131
391
  self.constants = parsedConstants
132
392
  self.parameters = parsedParameters
133
393
  self.state = parsedState
134
394
  self.instances = instances
395
+ self.tables = parsedTables
396
+ self.shapes = parsedShapes
397
+ self.samples = parsedSamples
398
+ self.routes = parsedRoutes
399
+ self.curves = parsedCurves
135
400
  self.block = try code("block")
136
401
  self.sample = try code("sample")
137
402
  self.output = output
403
+ self.outputRight = outputRight
404
+ self.master = try masterParts.map { Master(code: try code("master"), inputs: $0.inputs, outputs: $0.outputs) }
405
+ // Gated regions: each one's parameter one of the program's, the region around it an earlier one, and its
406
+ // results registers no instruction writes and nothing else names (no output, state, parameter, constant, route
407
+ // or another result), each read as a register while on and one while off, neither of them state (one value a
408
+ // voice, not one a frame).
409
+ var named = Set([output, outputRight].compactMap { $0 } + (masterParts?.inputs ?? []) + (masterParts?.outputs ?? []) + parsedRoutes.flatMap { $0 })
410
+ named.formUnion(stateRegisters)
411
+ named.formUnion(parsedParameters.map(\.register))
412
+ named.formUnion(parsedConstants.map(\.register))
413
+ named.formUnion(parsedCurves.flatMap { [$0.attack, $0.decay, $0.release] })
414
+ var results = Set<Int>()
415
+ var parsedGroups: [Gate] = []
416
+ for (index, item) in (root["groups"] as? [Any] ?? []).enumerated() {
417
+ guard let group = item as? [String: Any], let parameter = Self.int(group["parameter"]), parsedParameters.indices.contains(parameter) else {
418
+ throw DSPProgramError("groups[\(index)] does not name a parameter")
419
+ }
420
+ guard let parent = Self.int(group["parent"]), parent >= -1, parent < index else { throw DSPProgramError("groups[\(index)]'s parent is not an earlier region") }
421
+ guard let list = group["outputs"] as? [Any] else { throw DSPProgramError("groups[\(index)]'s outputs must be an array") }
422
+ let outputs = try list.map { entry -> Output in
423
+ guard let triple = entry as? [Any], triple.count == 3 else { throw DSPProgramError("groups[\(index)]: a result is [register, on, off]") }
424
+ let (r, on, off) = (try register(triple[0], "groups[\(index)]"), try register(triple[1], "groups[\(index)]"), try register(triple[2], "groups[\(index)]"))
425
+ guard r >= Self.machineRegisters, !written.contains(r), !named.contains(r), !results.contains(r) else {
426
+ throw DSPProgramError("groups[\(index)]: result register \(r) is written or named elsewhere")
427
+ }
428
+ guard !stateRegisters.contains(on), !stateRegisters.contains(off) else { throw DSPProgramError("groups[\(index)]: result register \(r) reads as state") }
429
+ results.insert(r)
430
+ return Output(register: r, on: on, off: off)
431
+ }
432
+ parsedGroups.append(Gate(parameter: parameter, parent: parent, outputs: outputs))
433
+ }
434
+ self.groups = parsedGroups
435
+ // Choices: each one's parameter one of the program's, its register one nothing else writes or names (nor a
436
+ // region's result, nor another choice), its options 1 … choiceOptionsMax registers, none of them state.
437
+ let voicing = root["voicing"] as? [String: Any] ?? [:]
438
+ for key in voicing.keys {
439
+ guard ["voices", "mode"].contains(key), let index = Self.int(voicing[key]), parsedParameters.indices.contains(index) else {
440
+ throw DSPProgramError("voicing.\(key) does not name a parameter")
441
+ }
442
+ }
443
+ self.voicingVoices = Self.int(voicing["voices"])
444
+ self.voicingMode = Self.int(voicing["mode"])
445
+ self.choices = try (root["choices"] as? [Any] ?? []).enumerated().map { index, item in
446
+ guard let choice = item as? [String: Any], let parameter = Self.int(choice["parameter"]), parsedParameters.indices.contains(parameter) else {
447
+ throw DSPProgramError("choices[\(index)] does not name a parameter")
448
+ }
449
+ var scale = 1.0
450
+ if let value = choice["scale"] {
451
+ guard let number = value as? NSNumber, !(value is Bool), number.doubleValue.isFinite, number.doubleValue > 0 else {
452
+ throw DSPProgramError("choices[\(index)]'s scale is not a number above 0")
453
+ }
454
+ scale = number.doubleValue
455
+ }
456
+ let r = try register(choice["register"], "choices[\(index)]")
457
+ guard r >= Self.machineRegisters, !written.contains(r), !named.contains(r), !results.contains(r) else {
458
+ throw DSPProgramError("choices[\(index)]: register \(r) is written or named elsewhere")
459
+ }
460
+ results.insert(r)
461
+ guard let list = choice["options"] as? [Any], (1...Self.choiceOptionsMax).contains(list.count) else {
462
+ throw DSPProgramError("choices[\(index)] has 1 … \(Self.choiceOptionsMax) options")
463
+ }
464
+ let options = try list.map { try register($0, "choices[\(index)]") }
465
+ guard !options.contains(where: stateRegisters.contains) else { throw DSPProgramError("choices[\(index)]: an option is state") }
466
+ return Choice(parameter: parameter, register: r, options: options, scale: scale)
467
+ }
468
+ /// One region (or -1) per instruction of `code`.
469
+ func regions(_ key: String, _ code: [Int32]?) throws -> [Int32]? {
470
+ guard let value = root[key] else { return nil }
471
+ guard let list = value as? [Any], let code else { throw DSPProgramError("\(key) must be an array") }
472
+ var count = 0
473
+ var pc = 0
474
+ while pc < code.count, let op = DSPOpcode(rawValue: code[pc]) {
475
+ count += 1
476
+ pc += 2 + op.arity + (op.builtin == nil ? 0 : 1)
477
+ }
478
+ guard list.count == count else { throw DSPProgramError("\(key) has \(list.count) entries for \(count) instructions") }
479
+ return try list.map { item in
480
+ guard let g = Self.int(item), g >= -1, g < parsedGroups.count else { throw DSPProgramError("\(key): a region is out of range") }
481
+ return Int32(g)
482
+ }
483
+ }
484
+ self.sampleGates = try regions("sampleGates", sample)
485
+ self.masterGates = try regions("masterGates", master?.code)
138
486
  }
139
487
 
140
488
  private static func int(_ value: Any?) -> Int? {
@@ -152,13 +500,26 @@ public struct DSPProgramError: Error, CustomStringConvertible, Equatable {
152
500
 
153
501
  /// The stateful builtins, by the name a program's `instances` gives them.
154
502
  public enum DSPBuiltin: String, Sendable, CaseIterable {
155
- case phasor, sine, saw, square, triangle, noise, adsr, lowpass, highpass, bandpass
503
+ case phasor, sine, saw, square, triangle, noise, adsr, lowpass, highpass, bandpass, wavetable, delay, play, grains, smooth, hold
504
+ case onepole, dcblock, comb, allpass, tableread, loop
156
505
 
157
- /// Words of state per lane (program.ts BUILTIN_STATE).
506
+ /// Words of state per lane (program.ts BUILTIN_STATE): a delay's, a comb's and an all-pass's samples are a
507
+ /// buffer of their own.
158
508
  var words: Int {
159
509
  switch self {
160
- case .phasor, .sine, .saw, .square, .triangle, .noise: 1
161
- case .adsr, .lowpass, .highpass, .bandpass: 8
510
+ case .phasor, .sine, .saw, .square, .triangle, .noise, .delay, .allpass: 1
511
+ case .hold, .dcblock, .comb: 2
512
+ // A table read's frequency last seen and the level it reads at that frequency.
513
+ case .tableread: 2
514
+ // A one-pole's place, then the cutoff last seen and its coefficient.
515
+ case .onepole: 3
516
+ // A wavetable's phase, then the frequency last seen and the level it reads at that frequency.
517
+ case .play, .loop, .smooth, .wavetable: 3
518
+ // An envelope's stage, level, trigger and gate before, its decay and release times and coefficients, then
519
+ // (with curves) what it put out last and where its release started.
520
+ case .adsr: 10
521
+ case .lowpass, .highpass, .bandpass: 8
522
+ case .grains: 2 + 4 * 8
162
523
  }
163
524
  }
164
525
  }
@@ -167,22 +528,52 @@ public enum DSPBuiltin: String, Sendable, CaseIterable {
167
528
  public enum DSPOpcode: Int32, Sendable, CaseIterable {
168
529
  case mov = 0, add, sub, mul, div, mod, neg, min, max, abs, floor, ceil, round, sqrt, sin, cos, tan, exp, log, pow, tanh
169
530
  case lt = 21, le, gt, ge, eq, ne, and, or, not, select, clamp, mix, wrap, sign, atan
170
- case phasor = 40, sine, saw, square, triangle, noise, adsr, lowpass, highpass, bandpass
531
+ case phasor = 40, sine, saw, square, triangle, noise, adsr, lowpass, highpass, bandpass, wavetable, delay, shape, play, grains, smooth, hold, modsum
532
+ /// The superinstructions the compiler fuses: a × b + c and (a + b) × c.
533
+ case madd = 58, amul
534
+ /// The rest of Math, and softclip.
535
+ case log2 = 60, log10, exp2, trunc, asin, acos, atan2, sinh, cosh, cbrt, hypot, expm1, log1p, softclip
536
+ case onepole = 74, dcblock, comb, allpass, tableread, loop
171
537
 
172
538
  /// Operands after the destination.
173
539
  public var arity: Int {
174
540
  switch self {
175
541
  case .noise: 0
542
+ case .modsum: 1
176
543
  case .mov, .neg, .abs, .floor, .ceil, .round, .sqrt, .sin, .cos, .tan, .exp, .log, .tanh, .not, .wrap, .sign, .atan,
177
- .phasor, .sine, .saw, .triangle: 1
178
- case .select, .clamp, .mix, .lowpass, .highpass, .bandpass: 3
179
- case .adsr: 5
544
+ .phasor, .sine, .saw, .triangle, .log2, .log10, .exp2, .trunc, .asin, .acos, .sinh, .cosh, .cbrt, .expm1, .log1p,
545
+ .softclip, .dcblock: 1
546
+ case .select, .clamp, .mix, .lowpass, .highpass, .bandpass, .wavetable, .delay, .play, .loop, .madd, .amul: 3
547
+ case .allpass, .tableread: 4
548
+ case .adsr, .grains, .comb: 5
180
549
  default: 2
181
550
  }
182
551
  }
183
552
 
184
- /// The builtin whose state it keeps, for a stateful one.
553
+ /// The builtin whose state it keeps, for a stateful one (`shape` keeps none).
185
554
  public var builtin: DSPBuiltin? {
186
- rawValue >= 40 ? DSPBuiltin.allCases[Int(rawValue) - 40] : nil
555
+ switch self {
556
+ case .play: .play
557
+ case .grains: .grains
558
+ case .smooth: .smooth
559
+ case .hold: .hold
560
+ case .onepole: .onepole
561
+ case .dcblock: .dcblock
562
+ case .comb: .comb
563
+ case .allpass: .allpass
564
+ case .tableread: .tableread
565
+ case .loop: .loop
566
+ default: rawValue >= 40 && rawValue <= 51 ? DSPBuiltin.allCases[Int(rawValue) - 40] : nil
567
+ }
568
+ }
569
+
570
+ /// A delay line's operand (after the destination) that names its longest time: a delay's, a comb's, an all-pass's.
571
+ public var longest: Int? {
572
+ switch self {
573
+ case .delay: 3
574
+ case .comb: 5
575
+ case .allpass: 4
576
+ default: nil
577
+ }
187
578
  }
188
579
  }
@@ -0,0 +1,31 @@
1
+ // swift-tools-version: 6.0
2
+ import PackageDescription
3
+
4
+ // SwiftBrowserShader: the native side of @swiftbrowser/shader. A shader
5
+ // written in the shader language and compiled by the package's TypeScript
6
+ // compiler draws here in an `MTKView`, its Metal compiled when a view first
7
+ // draws it; JavaScript gives it as a `ShaderView`. A package of its own, so
8
+ // only an app that draws with shaders links it; it installs itself as the
9
+ // `SwiftBrowserShader.view` registered view (`SwiftBrowserShader.install()`).
10
+ //
11
+ // SwiftBrowserShaderCore is the view's props alone (the shader's layout, its
12
+ // uniforms packed as the draw reads them, its textures), with no Apple
13
+ // framework, so it builds and tests on Linux too.
14
+ let package = Package(
15
+ name: "SwiftBrowserShader",
16
+ platforms: [.iOS(.v17), .macOS(.v14)],
17
+ products: [
18
+ .library(name: "SwiftBrowserShader", targets: ["SwiftBrowserShader"]),
19
+ .library(name: "SwiftBrowserShaderCore", targets: ["SwiftBrowserShaderCore"]),
20
+ ],
21
+ dependencies: [
22
+ .package(path: "../.."),
23
+ ],
24
+ targets: [
25
+ .target(name: "SwiftBrowserShaderCore", dependencies: [.product(name: "SwiftBrowserProtocol", package: "swift")]),
26
+ .target(
27
+ name: "SwiftBrowserShader",
28
+ dependencies: ["SwiftBrowserShaderCore", .product(name: "SwiftBrowserNative", package: "swift")]
29
+ ),
30
+ ]
31
+ )
@@ -0,0 +1,45 @@
1
+ # SwiftBrowserShader
2
+
3
+ The native side of [@swiftbrowser/shader](../../../packages/shader): a
4
+ shader written in the shader language, compiled by the package's TypeScript
5
+ compiler to the Metal Shading Language (beside GLSL and JavaScript for a
6
+ page), drawn by an `MTKView` over a `ShaderView`'s box. The JavaScript only
7
+ sends the shader, its uniforms and its textures; nothing per pixel runs
8
+ there.
9
+
10
+ It is a package of its own, as SwiftBrowserDSP is, so only an app that
11
+ draws with shaders links it. @swiftbrowser/native ships it beside the core
12
+ (`node_modules/@swiftbrowser/native/swift/Packages/SwiftBrowserShader`; it
13
+ depends on `SwiftBrowserNative` at `../..`, here and there). Link it into
14
+ the app's Xcode project with `npx swiftbrowser native add shader` (or
15
+ `native init --with shader`) and install it at launch:
16
+
17
+ ```swift
18
+ import SwiftBrowserShader
19
+
20
+ init() {
21
+ SwiftBrowserShader.install()
22
+ }
23
+ ```
24
+
25
+ That registers `ShaderView`'s view (`SwiftBrowserShader.view`) with
26
+ SwiftBrowserNative's `ViewRegistry`. Without it, a `ShaderView` shows its
27
+ name, as any registered view nothing registered does.
28
+
29
+ - **SwiftBrowserShaderCore** is the view's props alone: the shader's layout
30
+ (`ShaderLayout`, every uniform inside the floats a draw reads), its
31
+ textures, and the floats packed as the JavaScript packs them on a page
32
+ (`packedUniforms`). No Apple framework, so it builds and tests on Linux.
33
+ - **SwiftBrowserShader** is the view: `ShaderRenderer` compiles a shader's
34
+ Metal when a view first draws it (`makeLibrary(source:)`, kept for every
35
+ view that draws the same source), uploads its textures as `r32Float`, and
36
+ draws one triangle over the view with the uniforms in a fragment buffer.
37
+ The `MTKView` draws when its props or its size change, or every display
38
+ frame when the view is `animated`. A shader its device does not compile
39
+ draws nothing and sends the compiler's message to `onError`, once.
40
+
41
+ The tests (`swift test`) read @swiftbrowser/shader's render fixtures
42
+ (packages/shader/test/fixtures/render.json) and, on a Mac, compile each
43
+ case's Metal, draw it into a texture and hold its pixels to the ones the
44
+ package's JavaScript backend drew (printed as `[shader render]`), as the
45
+ page's tests hold WebGL2's.