@swiftbrowser/native 0.21.0 → 0.25.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.
@@ -40,8 +40,8 @@ import Foundation
40
40
  /// reset only in `init` and on `reset`.
41
41
  ///
42
42
  /// Block processing: a block runs in chunks of 16 frames, each instruction over every frame of the chunk for every
43
- /// sounding lane, but for a stream's serial part (feedback through state, which the compiler marks), which runs
44
- /// frame by frame within the chunk. Unlike the JavaScript machine's, a register holds a frame's lanes side by side,
43
+ /// sounding lane, but for a stream's serial part (feedback through state, which it schedules when it loads the
44
+ /// program), which runs frame by frame within the chunk. Unlike the JavaScript machine's, a register holds a frame's lanes side by side,
45
45
  /// then the next frame's, so the loops over lanes stay vector loops; the results are the same.
46
46
  public final class DSPMachine: @unchecked Sendable {
47
47
  public let program: DSPProgram
@@ -59,15 +59,77 @@ public final class DSPMachine: @unchecked Sendable {
59
59
  private let stateCount: Int
60
60
  private let noise: UnsafeMutablePointer<UInt32>
61
61
  private let noiseCount: Int
62
+ /// The streams as they run: what reads a gated region's result reading what the region gives it, `sample` and
63
+ /// `master` without the regions that are off, and scheduled (their parts that run frame by frame, in instructions).
62
64
  private let blockCode: UnsafeMutablePointer<Int32>
63
65
  private let blockCount: Int
66
+ /// How many of `blockCode`'s instructions run (those the streams were last made with).
67
+ private var blockRunning: Int
64
68
  private let sampleCode: UnsafeMutablePointer<Int32>
65
- private let sampleCount: Int
69
+ private var sampleCount: Int
66
70
  private let masterCode: UnsafeMutablePointer<Int32>
67
- private let masterCount: Int
68
- /// The parts of `sample` and `master` that run frame by frame, in instructions.
69
- private let sampleSerial: Range<Int>
70
- private let masterSerial: Range<Int>
71
+ private var masterCount: Int
72
+ private var sampleSerial: Range<Int> = 0..<0
73
+ private var masterSerial: Range<Int> = 0..<0
74
+ /// The streams as the program has them, each instruction's region, each region's parameter, parent and results
75
+ /// (register, then what it reads as on and off, as offsets), whether each is on (2 before the program loads), and
76
+ /// per register what a result reads as now (-1 for a register that is none).
77
+ private let fullBlock: UnsafeMutablePointer<Int32>
78
+ private let fullSample: UnsafeMutablePointer<Int32>
79
+ private let fullSampleCount: Int
80
+ private let fullMaster: UnsafeMutablePointer<Int32>
81
+ private let fullMasterCount: Int
82
+ private let sampleGates: [Int32]?
83
+ private let masterGates: [Int32]?
84
+ private let groupParameters: [Int]
85
+ private let groupParents: [Int]
86
+ private let groupOutputs: [[(register: Int, on: Int32, off: Int32)]]
87
+ private var groupOn: [UInt8]
88
+ /// What runs: per instruction of the block, sample and master streams as the program has them, whether something
89
+ /// the outputs need reads it, now and when the streams were last made; per register, whether it is read, now and
90
+ /// then; whether the streams have been made; and each instruction's builtin (-1 for none). An instruction that comes
91
+ /// to run again starts over (its builtin, the state it writes), as one in a region coming on does.
92
+ private let aliveNow: [UnsafeMutablePointer<UInt8>]
93
+ private let aliveBefore: [UnsafeMutablePointer<UInt8>]
94
+ private let readNow: UnsafeMutablePointer<UInt8>
95
+ private let readBefore: UnsafeMutablePointer<UInt8>
96
+ private var made = false
97
+ private let streamInstances: [[Int32]]
98
+ private let streamCounts: [Int]
99
+ private let renameTable: UnsafeMutablePointer<Int32>
100
+ /// Scratch for making a stream again: its instructions before they are scheduled, a byte an instruction (touches
101
+ /// state, computed from it, needed by it) and a byte a register (tainted, needed).
102
+ private let staging: UnsafeMutablePointer<Int32>
103
+ private let instructionMarks: UnsafeMutablePointer<UInt8>
104
+ private let registerMarks: UnsafeMutablePointer<UInt8>
105
+ /// Choices: each one's parameter, register and options (as offsets); the option each reads as now (the last
106
+ /// choices with no cycle), the ones last asked for (so choices refused for a cycle are not tried every block),
107
+ /// and the ones asked for this block.
108
+ private let choiceParameters: [Int]
109
+ private let choiceRegisters: [Int]
110
+ private let choiceOptions: [[Int32]]
111
+ private let choiceScales: [Double]
112
+ private var choiceUsed: [Int]
113
+ private var choiceAsked: [Int]
114
+ private var choiceNow: [Int]
115
+ /// The voicing's parameters (nil: the program's own), and the voices and mode last played with (-1 before the
116
+ /// first block), so a turn lets go of what no longer plays.
117
+ private let voicesParameter: Int?
118
+ private let modeParameter: Int?
119
+ private var voicedLimit = -1
120
+ private var voicedMode = 0
121
+ /// Scratch for ordering a stream when choices change (made only when there are choices): the ordered sample and
122
+ /// master streams; per register its writer (not state) or the first of its writers (state, then linked by
123
+ /// instruction) and where its readers start in `readers`; per instruction how many it waits on; and a heap.
124
+ private let orderedSample: UnsafeMutablePointer<Int32>
125
+ private let orderedMaster: UnsafeMutablePointer<Int32>
126
+ private let writerOf: UnsafeMutablePointer<Int32>
127
+ private let stateWriterFirst: UnsafeMutablePointer<Int32>
128
+ private let stateWriterNext: UnsafeMutablePointer<Int32>
129
+ private let readerStarts: UnsafeMutablePointer<Int32>
130
+ private let readers: UnsafeMutablePointer<Int32>
131
+ private let waiting: UnsafeMutablePointer<Int32>
132
+ private let heap: UnsafeMutablePointer<Int32>
71
133
  /// Per instance: whether the master stage runs it.
72
134
  private let instanceMaster: [Bool]
73
135
  /// Each instance's builtin and where its state starts (in floats, lanes included; a noise instance's in `noise`).
@@ -80,36 +142,60 @@ public final class DSPMachine: @unchecked Sendable {
80
142
  private let parameterIndices: [String: Int]
81
143
  private let targets: UnsafeMutablePointer<Float>
82
144
  private var snapped = true
83
- /// The wavetables' samples (table, level, 2048 samples, each its frames side by side), where each table starts
84
- /// and its frames.
85
- private let tableSamples: UnsafeMutablePointer<Float>
145
+ /// Whether a parameter was set since the last block: only then can a region, a choice or the voicing turn.
146
+ private var retargeted = false
147
+ /// Each wavetable's samples (level, 2048 samples, each its frames side by side), how many floats, and its frames;
148
+ /// each its own, so one is replaced (`replaceTable`) by swapping it whole. (`Slots`, not arrays replaced in place,
149
+ /// which Swift would check for exclusive access at every read as it renders.)
150
+ private let tablePointers: Slots<UnsafeMutablePointer<Float>>
151
+ private let tableSizes: Slots<Int>
152
+ private let tableFrames: Slots<Int>
86
153
  private let tableCount: Int
87
- /// Envelopes' curves (attack, decay, release exponents), three a curve; an `adsr` names its curve in slot 11.
88
- private let curveExponents: [Float]
89
- private let tableBases: [Int]
90
- private let tableFrames: [Int]
154
+ /// Envelopes' curves: the registers (as offsets) their attack, decay and release exponents are read from, three a
155
+ /// curve; an `adsr` names its curve in slot 11.
156
+ private let curveRegisters: [Int]
91
157
  /// The delays' samples: each instance's buffer per lane, a power of two long; by instance, where it starts and its length.
92
158
  private let delaySamples: UnsafeMutablePointer<Float>
93
159
  private let delaySampleCount: Int
94
160
  private let delayBases: [Int]
95
161
  private let delayLengths: [Int]
96
- /// The shapes' points, one after another; where each starts and its length.
97
- private let shapePoints: UnsafeMutablePointer<Float>
98
- private let shapeBases: [Int]
99
- private let shapeLengths: [Int]
100
- /// The samples' audio, one after another; per sample where it starts, its length, its frames a frame at this
101
- /// rate, and its spray (20 ms) in frames.
102
- private let sampleAudio: UnsafeMutablePointer<Float>
103
- private let sampleBases: [Int]
104
- private let sampleLengths: [Int]
105
- private let sampleRatios: [Float]
106
- private let sampleSprays: [UInt32]
107
- /// `modsum`'s route lists one after another, as register offsets (× `width`): a source's, then its amount's, pair
108
- /// after pair; list i is `routeStarts[i] ..< routeStarts[i + 1]`.
162
+ /// Each shape's points and how many; each sample's audio, its length, its frames a frame at this rate, and its
163
+ /// spray (20 ms) in frames. Each its own, so one is replaced by swapping it whole.
164
+ private let shapePointers: Slots<UnsafeMutablePointer<Float>>
165
+ private let shapeLengths: Slots<Int>
166
+ private let samplePointers: Slots<UnsafeMutablePointer<Float>>
167
+ private let sampleLengths: Slots<Int>
168
+ private let sampleRatios: Slots<Float>
169
+ private let sampleSprays: Slots<UInt32>
170
+ /// `modsum`'s route lists, each with room for `DSPProgram.routesMax` registers (so one is replaced in place), as
171
+ /// register offsets (× `width`): a source's, then its amount's, pair after pair; list i starts at
172
+ /// `i × routesMax` and is `routeCounts[i]` long.
109
173
  private let routeRegisters: UnsafeMutablePointer<Int>
110
174
  /// Whether each route's register is state (one value a lane, not one a frame).
111
175
  private let routeState: UnsafeMutablePointer<Bool>
112
- private let routeStarts: [Int]
176
+ private let routeCounts: UnsafeMutablePointer<Int>
177
+ private let routeCount: Int
178
+ /// Whether a route list was replaced since the last block: the streams are made again.
179
+ private var rerouted = false
180
+ /// Per register: whether it is a parameter, state, or one a machine renames (a choice's, a region's result).
181
+ private let parameterMask: UnsafeMutablePointer<UInt8>
182
+ private let stateMask: UnsafeMutablePointer<UInt8>
183
+ private let renamedMask: UnsafeMutablePointer<UInt8>
184
+ /// Folding: each register's value where it is a constant (NaN where not), an offset holding 0 (-1 for none), the
185
+ /// registers nothing may fold away (outputs, state, curves, the master stage's inputs) and those a route list names
186
+ /// now, and the registers folded when the streams were last made (their renaming undone before the next).
187
+ private let constantValues: UnsafeMutablePointer<Float>
188
+ /// Each parameter's range, by its register (NaN elsewhere): a clamp it is inside folds away.
189
+ private let parameterLowest: UnsafeMutablePointer<Float>
190
+ private let parameterHighest: UnsafeMutablePointer<Float>
191
+ private let zeroOffset: Int32
192
+ private let pinnedMask: UnsafeMutablePointer<UInt8>
193
+ private let routedMask: UnsafeMutablePointer<UInt8>
194
+ private let folded: UnsafeMutablePointer<Int32>
195
+ private var foldedCount = 0
196
+ /// Re-rating: per register whether it may change from frame to frame, and the sample stream's instructions kept.
197
+ private let changing: UnsafeMutablePointer<UInt8>
198
+ private let keptSample: UnsafeMutablePointer<Int32>
113
199
 
114
200
  private let laneNote: UnsafeMutablePointer<Int32>
115
201
  private let laneGate: UnsafeMutablePointer<Float>
@@ -185,22 +271,68 @@ public final class DSPMachine: @unchecked Sendable {
185
271
  noiseCount = max(1, noises) * L
186
272
  noise = .allocate(capacity: noiseCount)
187
273
  noise.initialize(repeating: 1, count: noiseCount)
188
- (tableSamples, tableBases, tableFrames) = Self.buildTables(program.tables)
274
+ let tables = program.tables.isEmpty ? [(Self.zeros(DSPProgram.tableLevels * DSPProgram.tableFrame), DSPProgram.tableLevels * DSPProgram.tableFrame)] : program.tables.map { Self.levels(frames: $0.frames, cosines: $0.cosines) }
275
+ tablePointers = Slots(tables.map(\.0))
276
+ tableSizes = Slots(tables.map(\.1))
277
+ tableFrames = Slots(program.tables.isEmpty ? [1] : program.tables.map(\.frames.count))
189
278
  tableCount = program.tables.count
190
- (shapePoints, shapeBases, shapeLengths) = Self.concatenate(program.shapes.map(\.points))
191
- (sampleAudio, sampleBases, sampleLengths) = Self.concatenate(program.samples.map(\.data))
192
- sampleRatios = program.samples.map { Float($0.rate / Double(Float(sampleRate))) }
193
- routeStarts = program.routes.reduce(into: [0]) { starts, list in starts.append(starts.last! + list.count) }
194
- routeRegisters = .allocate(capacity: Swift.max(1, routeStarts.last!))
195
- routeState = .allocate(capacity: Swift.max(1, routeStarts.last!))
196
- routeState.initialize(repeating: false, count: Swift.max(1, routeStarts.last!))
279
+ shapePointers = Slots(program.shapes.isEmpty ? [Self.zeros(2)] : program.shapes.map { Self.copy($0.points) })
280
+ shapeLengths = Slots(program.shapes.isEmpty ? [2] : program.shapes.map(\.points.count))
281
+ samplePointers = Slots(program.samples.isEmpty ? [Self.zeros(2)] : program.samples.map { Self.copy($0.data) })
282
+ sampleLengths = Slots(program.samples.isEmpty ? [2] : program.samples.map(\.data.count))
283
+ sampleRatios = Slots(program.samples.isEmpty ? [1] : program.samples.map { Float($0.rate / Double(Float(sampleRate))) })
284
+ let routesMax = DSPProgram.routesMax
285
+ routeCount = program.routes.count
286
+ routeCounts = .allocate(capacity: Swift.max(1, routeCount))
287
+ routeCounts.initialize(repeating: 0, count: Swift.max(1, routeCount))
288
+ for (i, list) in program.routes.enumerated() { routeCounts[i] = list.count }
289
+ routeRegisters = .allocate(capacity: Swift.max(1, routeCount * routesMax))
290
+ routeRegisters.initialize(repeating: 0, count: Swift.max(1, routeCount * routesMax))
291
+ routeState = .allocate(capacity: Swift.max(1, routeCount * routesMax))
292
+ routeState.initialize(repeating: false, count: Swift.max(1, routeCount * routesMax))
197
293
  for (i, list) in program.routes.enumerated() {
198
294
  for (k, r) in list.enumerated() {
199
- routeRegisters[routeStarts[i] + k] = r * W
200
- routeState[routeStarts[i] + k] = stateRegisters.contains(r)
295
+ routeRegisters[i * routesMax + k] = r * W
296
+ routeState[i * routesMax + k] = stateRegisters.contains(r)
201
297
  }
202
298
  }
203
- sampleSprays = program.samples.map { UInt32(Swift.max(1, ($0.rate * 0.02).rounded(.down))) }
299
+ let registerCount = program.registers
300
+ func mask() -> UnsafeMutablePointer<UInt8> {
301
+ let pointer = UnsafeMutablePointer<UInt8>.allocate(capacity: registerCount)
302
+ pointer.initialize(repeating: 0, count: registerCount)
303
+ return pointer
304
+ }
305
+ parameterMask = mask()
306
+ stateMask = mask()
307
+ renamedMask = mask()
308
+ pinnedMask = mask()
309
+ routedMask = mask()
310
+ changing = mask()
311
+ for p in program.parameters { parameterMask[p.register] = 1 }
312
+ for r in stateRegisters { stateMask[r] = 1 }
313
+ for c in program.choices { renamedMask[c.register] = 1 }
314
+ for g in program.groups { for o in g.outputs { renamedMask[o.register] = 1 } }
315
+ for r in [program.output, program.outputRight ?? program.output] + (program.master?.outputs ?? []) + (program.master?.inputs ?? []) { pinnedMask[r] = 1 }
316
+ for r in stateRegisters { pinnedMask[r] = 1 }
317
+ for c in program.curves { for r in [c.attack, c.decay, c.release] { pinnedMask[r] = 1 } }
318
+ for list in program.routes { for r in list { routedMask[r] = 1 } }
319
+ constantValues = .allocate(capacity: registerCount)
320
+ constantValues.initialize(repeating: .nan, count: registerCount)
321
+ parameterLowest = .allocate(capacity: registerCount)
322
+ parameterLowest.initialize(repeating: .nan, count: registerCount)
323
+ parameterHighest = .allocate(capacity: registerCount)
324
+ parameterHighest.initialize(repeating: .nan, count: registerCount)
325
+ for p in program.parameters {
326
+ parameterLowest[p.register] = p.min
327
+ parameterHighest[p.register] = p.max
328
+ }
329
+ var zero: Int32 = -1
330
+ for constant in program.constants {
331
+ constantValues[constant.register] = constant.value
332
+ if constant.value == 0 && zero < 0 { zero = Int32(constant.register * W) }
333
+ }
334
+ zeroOffset = zero
335
+ sampleSprays = Slots(program.samples.isEmpty ? [1] : program.samples.map { UInt32(Swift.max(1, ($0.rate * 0.02).rounded(.down))) })
204
336
  // Each delay's buffer: its longest time (a constant register) at this sample rate, rounded up to a power of
205
337
  // two, per voice (the master stage's, once).
206
338
  let constantValues = Dictionary(program.constants.map { ($0.register, $0.value) }, uniquingKeysWith: { _, last in last })
@@ -228,13 +360,95 @@ public final class DSPMachine: @unchecked Sendable {
228
360
  delaySampleCount = Swift.max(1, delayTotal)
229
361
  delaySamples = .allocate(capacity: delaySampleCount)
230
362
  delaySamples.initialize(repeating: 0, count: delaySampleCount)
231
- curveExponents = program.curves.flatMap { [$0.attack, $0.decay, $0.release] }
363
+ curveRegisters = program.curves.flatMap { [$0.attack * W, $0.decay * W, $0.release * W] }
232
364
  let curves = Dictionary(program.curves.enumerated().map { ($1.instance, $0 + 1) }, uniquingKeysWith: { _, last in last })
233
- (blockCode, blockCount, _) = Self.decode(program.block, nil, width: W, state: stateRegisters, offsets: offsets, delays: (bases, lengths), curves: curves)
234
- (sampleCode, sampleCount, sampleSerial) = Self.decode(
235
- program.sample, program.serial, width: W, state: stateRegisters, offsets: offsets, delays: (bases, lengths), curves: curves)
236
- (masterCode, masterCount, masterSerial) = Self.decode(
237
- program.master?.code ?? [], program.master?.serial, width: W, state: stateRegisters, offsets: offsets, delays: (bases, lengths), curves: curves)
365
+ (fullBlock, blockCount) = Self.decode(program.block, width: W, state: stateRegisters, offsets: offsets, delays: (bases, lengths), curves: curves)
366
+ (fullSample, fullSampleCount) = Self.decode(program.sample, width: W, state: stateRegisters, offsets: offsets, delays: (bases, lengths), curves: curves)
367
+ (fullMaster, fullMasterCount) = Self.decode(program.master?.code ?? [], width: W, state: stateRegisters, offsets: offsets, delays: (bases, lengths), curves: curves)
368
+ // Each made again (renamed, compacted, scheduled) when the program has loaded, and when a region turns.
369
+ // The block stream's room takes the sample stream's too: what no longer changes frame by frame moves there.
370
+ blockCode = .allocate(capacity: max(12, (blockCount + fullSampleCount) * 12))
371
+ blockCode.initialize(repeating: 0, count: max(12, (blockCount + fullSampleCount) * 12))
372
+ blockCode.update(from: fullBlock, count: blockCount * 12)
373
+ keptSample = .allocate(capacity: max(12, fullSampleCount * 12))
374
+ folded = .allocate(capacity: max(1, blockCount + fullSampleCount + fullMasterCount))
375
+ blockRunning = blockCount
376
+ sampleCode = .allocate(capacity: max(12, fullSampleCount * 12))
377
+ sampleCode.update(from: fullSample, count: max(12, fullSampleCount * 12))
378
+ masterCode = .allocate(capacity: max(12, fullMasterCount * 12))
379
+ masterCode.update(from: fullMaster, count: max(12, fullMasterCount * 12))
380
+ (sampleCount, masterCount) = (fullSampleCount, fullMasterCount)
381
+ sampleGates = program.sampleGates
382
+ masterGates = program.masterGates
383
+ groupParameters = program.groups.map(\.parameter)
384
+ groupParents = program.groups.map(\.parent)
385
+ groupOutputs = program.groups.map { $0.outputs.map { (register: $0.register, on: Int32($0.on * W), off: Int32($0.off * W)) } }
386
+ groupOn = [UInt8](repeating: 2, count: program.groups.count)
387
+ streamInstances = [program.block, program.sample, program.master?.code ?? []].map { code in
388
+ var instances: [Int32] = []
389
+ var pc = 0
390
+ while pc < code.count, let op = DSPOpcode(rawValue: code[pc]) {
391
+ instances.append(op.builtin != nil ? code[pc + 2 + op.arity] : -1)
392
+ pc += 2 + op.arity + (op.builtin == nil ? 0 : 1)
393
+ }
394
+ return instances
395
+ }
396
+ streamCounts = [blockCount, fullSampleCount, fullMasterCount]
397
+ aliveNow = [blockCount, fullSampleCount, fullMasterCount].map { count in
398
+ let marks = UnsafeMutablePointer<UInt8>.allocate(capacity: Swift.max(1, count))
399
+ marks.initialize(repeating: 0, count: Swift.max(1, count))
400
+ return marks
401
+ }
402
+ aliveBefore = [blockCount, fullSampleCount, fullMasterCount].map { count in
403
+ let marks = UnsafeMutablePointer<UInt8>.allocate(capacity: Swift.max(1, count))
404
+ marks.initialize(repeating: 0, count: Swift.max(1, count))
405
+ return marks
406
+ }
407
+ readNow = .allocate(capacity: program.registers)
408
+ readNow.initialize(repeating: 0, count: program.registers)
409
+ readBefore = .allocate(capacity: program.registers)
410
+ readBefore.initialize(repeating: 0, count: program.registers)
411
+ renameTable = .allocate(capacity: program.registers)
412
+ renameTable.initialize(repeating: -1, count: program.registers)
413
+ let longest = max(1, blockCount, fullSampleCount, fullMasterCount)
414
+ staging = .allocate(capacity: longest * 12)
415
+ staging.initialize(repeating: 0, count: longest * 12)
416
+ instructionMarks = .allocate(capacity: longest)
417
+ instructionMarks.initialize(repeating: 0, count: longest)
418
+ registerMarks = .allocate(capacity: program.registers)
419
+ registerMarks.initialize(repeating: 0, count: program.registers)
420
+ choiceParameters = program.choices.map(\.parameter)
421
+ choiceRegisters = program.choices.map(\.register)
422
+ choiceOptions = program.choices.map { $0.options.map { Int32($0 * W) } }
423
+ choiceScales = program.choices.map(\.scale)
424
+ choiceUsed = [Int](repeating: -1, count: program.choices.count)
425
+ choiceAsked = choiceUsed
426
+ choiceNow = choiceUsed
427
+ voicesParameter = program.voicingVoices
428
+ modeParameter = program.voicingMode
429
+ // The most registers an instruction of the sample or master stream reads: its operands, and a modsum's routes.
430
+ let ordering = !program.choices.isEmpty || !program.routes.isEmpty
431
+ // A route list may be replaced by one as long as any may be.
432
+ let longestRoute = program.routes.isEmpty ? 0 : routesMax
433
+ func readsIn(_ code: UnsafeMutablePointer<Int32>, _ count: Int) -> Int {
434
+ (0..<count).reduce(0) { sum, i in
435
+ let op = DSPOpcode(rawValue: code[i * 12])!
436
+ return sum + op.arity + (op == .modsum ? longestRoute : 0)
437
+ }
438
+ }
439
+ let readCount = ordering ? Swift.max(readsIn(fullSample, fullSampleCount), readsIn(fullMaster, fullMasterCount)) : 0
440
+ let orderable = ordering ? longest : 1
441
+ orderedSample = .allocate(capacity: Swift.max(12, fullSampleCount * 12))
442
+ orderedMaster = .allocate(capacity: Swift.max(12, fullMasterCount * 12))
443
+ writerOf = .allocate(capacity: ordering ? program.registers : 1)
444
+ writerOf.initialize(repeating: -1, count: ordering ? program.registers : 1)
445
+ stateWriterFirst = .allocate(capacity: ordering ? program.registers : 1)
446
+ stateWriterFirst.initialize(repeating: -1, count: ordering ? program.registers : 1)
447
+ stateWriterNext = .allocate(capacity: orderable)
448
+ readerStarts = .allocate(capacity: ordering ? program.registers + 1 : 1)
449
+ readers = .allocate(capacity: Swift.max(1, readCount))
450
+ waiting = .allocate(capacity: orderable)
451
+ heap = .allocate(capacity: orderable)
238
452
  for constant in program.constants {
239
453
  (registers + constant.register * W).update(repeating: constant.value, count: W)
240
454
  }
@@ -271,18 +485,43 @@ public final class DSPMachine: @unchecked Sendable {
271
485
  queueEvent.initialize(repeating: 0, count: Self.queueCapacity * 3)
272
486
  for lane in 0..<L { resetLane(lane) }
273
487
  resetMaster()
488
+ // The streams as they run: the regions its parameters turn on, scheduled.
489
+ turnRegions(loading: true)
490
+ revoice()
274
491
  }
275
492
 
276
493
  deinit {
277
- for pointer in [registers, state, targets, laneGate, laneVelocity, laneActive, trigger, masterTrigger, shapePoints, sampleAudio] { pointer.deallocate() }
494
+ for pointer in [registers, state, targets, laneGate, laneVelocity, laneActive, trigger, masterTrigger] { pointer.deallocate() }
495
+ for slots in [shapePointers, samplePointers, tablePointers] {
496
+ for i in 0..<slots.count { slots[i].deallocate() }
497
+ slots.deallocate()
498
+ }
499
+ for slots in [tableSizes, tableFrames, shapeLengths, sampleLengths] { slots.deallocate() }
500
+ sampleRatios.deallocate()
501
+ sampleSprays.deallocate()
278
502
  noise.deallocate()
279
503
  routeRegisters.deallocate()
504
+ routeCounts.deallocate()
280
505
  routeState.deallocate()
281
- tableSamples.deallocate()
506
+ for pointer in [parameterMask, stateMask, renamedMask, pinnedMask, routedMask, changing] { pointer.deallocate() }
507
+ constantValues.deallocate()
508
+ parameterLowest.deallocate()
509
+ parameterHighest.deallocate()
510
+ keptSample.deallocate()
511
+ folded.deallocate()
282
512
  delaySamples.deallocate()
283
513
  blockCode.deallocate()
284
514
  sampleCode.deallocate()
285
515
  masterCode.deallocate()
516
+ fullBlock.deallocate()
517
+ fullSample.deallocate()
518
+ fullMaster.deallocate()
519
+ renameTable.deallocate()
520
+ staging.deallocate()
521
+ instructionMarks.deallocate()
522
+ registerMarks.deallocate()
523
+ for pointer in aliveNow + aliveBefore + [readNow, readBefore] { pointer.deallocate() }
524
+ for pointer in [orderedSample, orderedMaster, writerOf, stateWriterFirst, stateWriterNext, readerStarts, readers, waiting, heap] { pointer.deallocate() }
286
525
  laneNote.deallocate()
287
526
  stack.deallocate()
288
527
  queueBeat.deallocate()
@@ -293,18 +532,460 @@ public final class DSPMachine: @unchecked Sendable {
293
532
 
294
533
  /// Instructions at a stride of 12 (op, destination, five operands, state offset, which of slots 1 … 6 are state
295
534
  /// registers as bits, then a delay line's buffer: where it starts and its length, then an envelope's curve, 1 up,
296
- /// 0 for none), registers as offsets into the file. With the serial part's word offsets, its instructions.
297
- private static func decode(
298
- _ code: [Int32], _ serial: Range<Int>?, width: Int, state: Set<Int>, offsets: [Int], delays: (bases: [Int], lengths: [Int]),
299
- curves: [Int: Int]
300
- ) -> (UnsafeMutablePointer<Int32>, Int, Range<Int>) {
301
- var out: [Int32] = []
535
+ /// 0 for none), registers as offsets into the file. With how many there are.
536
+ /// Gated regions and choices: a region is on while its parameter is set above 0.5 and the region around it is on;
537
+ /// a choice reads as the option its parameter names. When either changes (or the program loads), the streams are
538
+ /// made again: without the regions that are off, with what reads a region's result or a choice reading what it
539
+ /// gives, without what nothing the outputs need reads (an option not chosen), ordered so what a choice reads comes
540
+ /// first, and scheduled. Into buffers made when the program loaded: nothing is allocated.
541
+ private func turnRegions(loading: Bool, force: Bool = false) {
542
+ var changed = loading || force
543
+ for g in 0..<groupParameters.count {
544
+ let parent = groupParents[g]
545
+ let on: UInt8 = (parent < 0 || groupOn[parent] == 1) && targets[groupParameters[g]] > 0.5 ? 1 : 0
546
+ if on != groupOn[g] {
547
+ groupOn[g] = on
548
+ changed = true
549
+ }
550
+ }
551
+ for c in 0..<choiceParameters.count {
552
+ let value = Float((Double(targets[choiceParameters[c]]) * choiceScales[c]).rounded())
553
+ let last = choiceOptions[c].count - 1
554
+ choiceNow[c] = value.isNaN ? 0 : value <= 0 ? 0 : value >= Float(last) ? last : Int(value)
555
+ if choiceNow[c] != choiceAsked[c] { changed = true }
556
+ }
557
+ guard changed else { return }
558
+ for c in 0..<choiceNow.count { choiceAsked[c] = choiceNow[c] }
559
+ // The choices asked for; if they would make a cycle, the last that did not (with the regions as they are now);
560
+ // and on loading, if even those would, the program's own order.
561
+ if make(choiceNow, sort: true) {
562
+ for c in 0..<choiceNow.count { choiceUsed[c] = choiceNow[c] }
563
+ } else if loading || !make(choiceUsed, sort: true) {
564
+ _ = make(loading ? choiceNow : choiceUsed, sort: false)
565
+ }
566
+ if loading { for c in 0..<choiceNow.count { choiceUsed[c] = choiceNow[c] } }
567
+ }
568
+
569
+ /// The streams made again for the regions as they are and the choices `chosen`; false (nothing made) on a cycle.
570
+ private func make(_ chosen: [Int], sort: Bool) -> Bool {
571
+ // What was folded last time reads as itself again, until folded again below.
572
+ for i in 0..<foldedCount { renameTable[Int(folded[i])] = -1 }
573
+ foldedCount = 0
574
+ var results = choiceRegisters.count
575
+ for g in groupOutputs.indices {
576
+ for output in groupOutputs[g] { renameTable[output.register] = groupOn[g] == 1 ? output.on : output.off }
577
+ results += groupOutputs[g].count
578
+ }
579
+ for c in 0..<choiceRegisters.count { renameTable[choiceRegisters[c]] = choiceOptions[c][chosen[c]] }
580
+ // A result or a choice may read as another (nested regions, a choice of results): followed, at most once each.
581
+ let W = Int32(width)
582
+ func resolve(_ register: Int) {
583
+ var target = renameTable[register]
584
+ var steps = 0
585
+ while steps < results, renameTable[Int(target / W)] >= 0 {
586
+ target = renameTable[Int(target / W)]
587
+ steps += 1
588
+ }
589
+ renameTable[register] = target
590
+ }
591
+ for g in groupOutputs.indices { for output in groupOutputs[g] { resolve(output.register) } }
592
+ for register in choiceRegisters { resolve(register) }
593
+ fold()
594
+ liveness()
595
+ // Ordered where something may read what comes after it (a choice of a value made later, a route from a source
596
+ // made later); kept apart from `staging`, which the next stream is compacted into.
597
+ let ordering = sort && (!choiceRegisters.isEmpty || routeCount > 0)
598
+ let samples = compact(fullSample, fullSampleCount, aliveNow[1])
599
+ if ordering {
600
+ guard order(samples, into: orderedSample) else { return false }
601
+ } else {
602
+ orderedSample.update(from: staging, count: samples * 12)
603
+ }
604
+ let masters = compact(fullMaster, fullMasterCount, aliveNow[2])
605
+ if ordering {
606
+ guard order(masters, into: orderedMaster) else { return false }
607
+ } else {
608
+ orderedMaster.update(from: staging, count: masters * 12)
609
+ }
610
+ revive()
611
+ blockRunning = compact(fullBlock, blockCount, aliveNow[0])
612
+ blockCode.update(from: staging, count: blockRunning * 12)
613
+ // What in the sample stream no longer changes frame by frame runs once a block (as machine.ts moves it).
614
+ sampleCount = rerate(samples, from: orderedSample)
615
+ sampleSerial = reschedule(sampleCount, from: keptSample, into: sampleCode)
616
+ masterCount = masters
617
+ masterSerial = reschedule(masters, from: orderedMaster, into: masterCode)
618
+ return true
619
+ }
620
+
621
+ /// Folds what turned constant, before what runs is found (as machine.ts folds it): an instruction that comes to
622
+ /// one of its operands (x + 0, x × 1, a mix at 0 or 1, a select of a constant, a mov) or to 0 (x × 0, an empty
623
+ /// route list) is passed over, what reads it reading that instead. Never one whose register is an output, state, a
624
+ /// curve or in a route list.
625
+ private func fold() {
626
+ let W = Int32(width)
627
+ let zero = zeroOffset
628
+ func pass(_ code: UnsafeMutablePointer<Int32>, _ count: Int) {
629
+ for i in 0..<count {
630
+ let slot = code + i * 12
631
+ let d = Int(slot[1] / W)
632
+ if pinnedMask[d] != 0 || routedMask[d] != 0 { continue }
633
+ let op = slot[0]
634
+ let arity = DSPOpcode(rawValue: op)!.arity
635
+ func operand(_ k: Int) -> Int32 {
636
+ guard k < arity else { return -1 }
637
+ let t = renameTable[Int(slot[2 + k] / W)]
638
+ return t >= 0 ? t : slot[2 + k]
639
+ }
640
+ let a = operand(0)
641
+ let b = operand(1)
642
+ let c = operand(2)
643
+ func value(_ offset: Int32) -> Float { offset >= 0 ? constantValues[Int(offset / W)] : .nan }
644
+ var to: Int32 = -1
645
+ switch op {
646
+ case 0: to = a
647
+ case 1: to = value(a) == 0 ? b : value(b) == 0 ? a : -1
648
+ case 2: to = value(b) == 0 ? a : -1
649
+ case 3: to = value(a) == 1 ? b : value(b) == 1 ? a : value(a) == 0 || value(b) == 0 ? zero : -1
650
+ case 58: to = value(a) == 0 || value(b) == 0 ? c : value(c) == 0 && value(a) == 1 ? b : value(c) == 0 && value(b) == 1 ? a : -1
651
+ case 59: to = value(c) == 0 ? zero : value(c) == 1 && value(a) == 0 ? b : value(c) == 1 && value(b) == 0 ? a : -1
652
+ case 30: to = a == b ? b : !value(a).isNaN ? (value(a) != 0 ? b : c) : -1
653
+ // A parameter clamped to a range its own is inside.
654
+ case 31: to = a >= 0 && value(b) <= parameterLowest[Int(a / W)] && parameterHighest[Int(a / W)] <= value(c) ? a : -1
655
+ case 32: to = value(c) == 0 || a == b ? a : value(c) == 1 ? b : -1
656
+ case 57:
657
+ let index = Self.index(registers[Int(slot[2])], routeCount)
658
+ to = index >= routeCount || routeCounts[index] == 0 ? zero : -1
659
+ default: break
660
+ }
661
+ if to < 0 || Int(to / W) == d { continue }
662
+ renameTable[d] = to
663
+ folded[foldedCount] = Int32(d)
664
+ foldedCount += 1
665
+ }
666
+ }
667
+ pass(fullBlock, blockCount)
668
+ pass(fullSample, fullSampleCount)
669
+ pass(fullMaster, fullMasterCount)
670
+ }
671
+
672
+ /// The `count` instructions of the sample stream at `source`, split (as machine.ts splits them): those that read
673
+ /// only what is set once a block (no state, nothing worked out frame by frame, not the beat) and are not builtins,
674
+ /// onto the end of `blockCode`; the rest into `keptSample`, in their order. How many stayed.
675
+ private func rerate(_ count: Int, from source: UnsafeMutablePointer<Int32>) -> Int {
676
+ let W = Int32(width)
677
+ changing[6] = 1
678
+ var kept = 0
679
+ for i in 0..<count {
680
+ let slot = source + i * 12
681
+ let d = Int(slot[1] / W)
682
+ var still = DSPOpcode(rawValue: slot[0])!.builtin == nil && stateMask[d] == 0
683
+ if still { forEachRead(slot) { r, state in if state || changing[r] != 0 { still = false } } }
684
+ if still {
685
+ (blockCode + blockRunning * 12).update(from: slot, count: 12)
686
+ blockRunning += 1
687
+ } else {
688
+ changing[d] = 1
689
+ (keptSample + kept * 12).update(from: slot, count: 12)
690
+ kept += 1
691
+ }
692
+ }
693
+ // The marks back to none, for the next time.
694
+ for i in 0..<kept { changing[Int(keptSample[i * 12 + 1] / W)] = 0 }
695
+ changing[6] = 0
696
+ return kept
697
+ }
698
+
699
+ /// Replaces route list `index` from the next block with `list`'s registers (a source's, then its amount's, pair
700
+ /// after pair, as floats), copied in place (as machine.ts `replaceRoutes`): the streams are made again. False where
701
+ /// there is no such list, or an amount is not a parameter, a register is past the program's or one renamed, or the
702
+ /// list is past `DSPProgram.routesMax`.
703
+ public func replaceRoutes(_ index: Int, registers list: DSPBuffer) -> Bool {
704
+ guard index >= 0, index < routeCount, list.count % 2 == 0, list.count <= DSPProgram.routesMax else { return false }
705
+ for k in 0..<list.count {
706
+ let x = list.pointer[k]
707
+ guard x >= 0, x < Float(program.registers), x == x.rounded(.down) else { return false }
708
+ let r = Int(x)
709
+ guard renamedMask[r] == 0, k % 2 == 0 || parameterMask[r] != 0 else { return false }
710
+ }
711
+ let start = index * DSPProgram.routesMax
712
+ // The same list again: nothing to make again.
713
+ if routeCounts[index] == list.count && (0..<list.count).allSatisfy({ routeRegisters[start + $0] == Int(list.pointer[$0]) * width }) { return true }
714
+ for k in 0..<list.count {
715
+ let r = Int(list.pointer[k])
716
+ routeRegisters[start + k] = r * width
717
+ routeState[start + k] = stateMask[r] != 0
718
+ }
719
+ routeCounts[index] = list.count
720
+ routedMask.update(repeating: 0, count: program.registers)
721
+ for i in 0..<routeCount {
722
+ for k in 0..<routeCounts[i] { routedMask[routeRegisters[i * DSPProgram.routesMax + k] / width] = 1 }
723
+ }
724
+ rerouted = true
725
+ return true
726
+ }
727
+
728
+
729
+ /// The `count` instructions in `staging` into `out`, ordered so each comes after what writes what it reads (not
730
+ /// state), and a state register's readers before what writes it (they read the frame before's); program order
731
+ /// where neither says (as machine.ts orders). False on a cycle.
732
+ private func order(_ count: Int, into out: UnsafeMutablePointer<Int32>) -> Bool {
733
+ let W = Int32(width)
734
+ let registerCount = program.registers
735
+ for i in 0..<count {
736
+ let slot = staging + i * 12
737
+ let d = Int(slot[1] / W)
738
+ if (slot[8] >> 1) & 1 == 1 {
739
+ stateWriterNext[i] = stateWriterFirst[d]
740
+ stateWriterFirst[d] = Int32(i)
741
+ } else {
742
+ writerOf[d] = Int32(i)
743
+ }
744
+ waiting[i] = 0
745
+ }
746
+ // Each register's readers (reading it not as state), in program order: counted, summed to where each ends,
747
+ // then placed back to front so each ends where it starts.
748
+ readerStarts.update(repeating: 0, count: registerCount + 1)
749
+ for i in 0..<count { forEachRead(staging + i * 12) { r, state in if !state { readerStarts[r] += 1 } } }
750
+ var total: Int32 = 0
751
+ for r in 0..<registerCount {
752
+ total += readerStarts[r]
753
+ readerStarts[r] = total
754
+ }
755
+ readerStarts[registerCount] = total
756
+ for i in stride(from: count - 1, through: 0, by: -1) {
757
+ forEachRead(staging + i * 12) { r, state in
758
+ guard !state else { return }
759
+ readerStarts[r] -= 1
760
+ readers[Int(readerStarts[r])] = Int32(i)
761
+ }
762
+ }
763
+ for i in 0..<count {
764
+ forEachRead(staging + i * 12) { r, state in
765
+ if state {
766
+ var w = stateWriterFirst[r]
767
+ while w >= 0 {
768
+ if Int(w) != i { waiting[Int(w)] += 1 }
769
+ w = stateWriterNext[Int(w)]
770
+ }
771
+ } else if writerOf[r] >= 0, Int(writerOf[r]) != i {
772
+ waiting[i] += 1
773
+ }
774
+ }
775
+ }
776
+ // The ready instructions, least first (a binary heap), so program order holds where nothing else decides.
777
+ var size = 0
778
+ func push(_ i: Int32) {
779
+ var k = size
780
+ heap[k] = i
781
+ size += 1
782
+ while k > 0, heap[(k - 1) / 2] > heap[k] {
783
+ (heap[k], heap[(k - 1) / 2]) = (heap[(k - 1) / 2], heap[k])
784
+ k = (k - 1) / 2
785
+ }
786
+ }
787
+ func pop() -> Int {
788
+ let top = heap[0]
789
+ size -= 1
790
+ heap[0] = heap[size]
791
+ var k = 0
792
+ while true {
793
+ let l = 2 * k + 1
794
+ var m = k
795
+ if l < size, heap[l] < heap[m] { m = l }
796
+ if l + 1 < size, heap[l + 1] < heap[m] { m = l + 1 }
797
+ if m == k { break }
798
+ (heap[k], heap[m]) = (heap[m], heap[k])
799
+ k = m
800
+ }
801
+ return Int(top)
802
+ }
803
+ func done(_ j: Int) {
804
+ waiting[j] -= 1
805
+ if waiting[j] == 0 { push(Int32(j)) }
806
+ }
807
+ for i in 0..<count where waiting[i] == 0 { push(Int32(i)) }
808
+ var placed = 0
809
+ while size > 0 {
810
+ let i = pop()
811
+ let slot = staging + i * 12
812
+ (out + placed * 12).update(from: slot, count: 12)
813
+ placed += 1
814
+ forEachRead(slot) { r, state in
815
+ guard state else { return }
816
+ var w = stateWriterFirst[r]
817
+ while w >= 0 {
818
+ if Int(w) != i { done(Int(w)) }
819
+ w = stateWriterNext[Int(w)]
820
+ }
821
+ }
822
+ let d = Int(slot[1] / W)
823
+ if (slot[8] >> 1) & 1 == 0, Int(writerOf[d]) == i {
824
+ for k in Int(readerStarts[d])..<Int(readerStarts[d + 1]) where Int(readers[k]) != i { done(Int(readers[k])) }
825
+ }
826
+ }
827
+ // The tables back to none, for the next time.
828
+ for i in 0..<count {
829
+ let d = Int(staging[i * 12 + 1] / W)
830
+ writerOf[d] = -1
831
+ stateWriterFirst[d] = -1
832
+ }
833
+ return placed == count
834
+ }
835
+
836
+ /// An envelope's curve exponent as read: held to 1/16 … 16, 1 for NaN (as machine.ts reads it).
837
+ @inline(__always) private static func exponent(_ x: Float) -> Double {
838
+ x >= 0.0625 ? Double(x <= 16 ? x : 16) : x < 0.0625 ? 0.0625 : 1
839
+ }
840
+
841
+ /// Each register the instruction at `slot` reads (its operands, an envelope's curves, and a modsum's routes), and
842
+ /// whether it is state.
843
+ private func forEachRead(_ slot: UnsafeMutablePointer<Int32>, _ body: (Int, Bool) -> Void) {
844
+ let W = Int32(width)
845
+ let op = DSPOpcode(rawValue: slot[0])!
846
+ for k in 0..<op.arity { body(Int(slot[2 + k] / W), (slot[8] >> (2 + k)) & 1 == 1) }
847
+ if op == .adsr, slot[11] > 0 {
848
+ let curve = Int(slot[11]) - 1
849
+ for k in 0..<3 { body(curveRegisters[curve * 3 + k] / width, false) }
850
+ }
851
+ if op == .modsum {
852
+ let index = Self.index(registers[Int(slot[2])], routeCount)
853
+ if index < routeCount {
854
+ let start = index * DSPProgram.routesMax
855
+ for k in start..<(start + routeCounts[index]) { body(routeRegisters[k] / width, routeState[k]) }
856
+ }
857
+ }
858
+ }
859
+
860
+ /// What runs, into `aliveNow` and `readNow`, with what reads a result or a choice reading what `renameTable` says:
861
+ /// from the outputs back, each instruction outside a region that is off whose register something running reads,
862
+ /// and what it reads (a state register read, what writes it). Again until nothing more runs, since a choice or
863
+ /// state may read what comes after (as machine.ts finds it).
864
+ private func liveness() {
865
+ let W = Int32(width)
866
+ readNow.update(repeating: 0, count: program.registers)
867
+ readNow[program.output] = 1
868
+ if let right = program.outputRight { readNow[right] = 1 }
869
+ for r in program.master?.outputs ?? [] { readNow[r] = 1 }
870
+ for s in 0..<3 { aliveNow[s].update(repeating: 0, count: streamCounts[s]) }
871
+ /// One pass over a stream, back to front: whether anything more runs.
872
+ func sweep(_ code: UnsafeMutablePointer<Int32>, _ count: Int, _ gates: [Int32]?, _ marks: UnsafeMutablePointer<UInt8>) -> Bool {
873
+ var more = false
874
+ for i in stride(from: count - 1, through: 0, by: -1) {
875
+ let slot = code + i * 12
876
+ guard marks[i] == 0, readNow[Int(slot[1] / W)] != 0 else { continue }
877
+ if let gates, gates[i] >= 0, groupOn[Int(gates[i])] == 0 { continue }
878
+ marks[i] = 1
879
+ more = true
880
+ forEachRead(slot) { r, _ in
881
+ let renamed = renameTable[r]
882
+ readNow[renamed >= 0 ? Int(renamed / W) : r] = 1
883
+ }
884
+ }
885
+ return more
886
+ }
887
+ var more = true
888
+ while more {
889
+ more = sweep(fullMaster, fullMasterCount, masterGates, aliveNow[2])
890
+ more = sweep(fullSample, fullSampleCount, sampleGates, aliveNow[1]) || more
891
+ more = sweep(fullBlock, blockCount, nil, aliveNow[0]) || more
892
+ }
893
+ }
894
+
895
+ /// What comes to run again (it did not when the streams were last made) starts over: a builtin as when a voice
896
+ /// starts, a state register at its first value. Nothing does when the program loads. Then what runs now is what ran.
897
+ private func revive() {
898
+ let counts = streamCounts
899
+ if made {
900
+ for s in 0..<3 {
901
+ for i in 0..<counts[s] where aliveNow[s][i] != 0 && aliveBefore[s][i] == 0 && streamInstances[s][i] >= 0 {
902
+ let instance = Int(streamInstances[s][i])
903
+ if instanceMaster[instance] { resetInstance(instance, 0) } else { for l in 0..<program.voices { resetInstance(instance, l) } }
904
+ }
905
+ }
906
+ for entry in program.state where readNow[entry.register] != 0 && readBefore[entry.register] == 0 {
907
+ if entry.master { registers[entry.register * width] = entry.initial } else { for l in 0..<program.voices { registers[entry.register * width + l] = entry.initial } }
908
+ }
909
+ }
910
+ for s in 0..<3 { aliveBefore[s].update(from: aliveNow[s], count: counts[s]) }
911
+ readBefore.update(from: readNow, count: program.registers)
912
+ made = true
913
+ }
914
+
915
+ /// A stream's instructions that run (`alive`), their operands renamed, into `staging`: how many instructions.
916
+ private func compact(_ code: UnsafeMutablePointer<Int32>, _ count: Int, _ alive: UnsafeMutablePointer<UInt8>) -> Int {
917
+ let W = Int32(width)
918
+ var kept = 0
919
+ for i in 0..<count {
920
+ if alive[i] == 0 { continue }
921
+ let at = code + i * 12
922
+ let slot = staging + kept * 12
923
+ slot.update(from: at, count: 12)
924
+ for k in 0..<DSPOpcode(rawValue: at[0])!.arity {
925
+ let r = renameTable[Int(at[2 + k] / W)]
926
+ if r >= 0 { slot[2 + k] = r }
927
+ }
928
+ kept += 1
929
+ }
930
+ return kept
931
+ }
932
+
933
+ /// A stream's schedule over the `count` instructions at `source`, into `out`: those reading or writing state, and
934
+ /// those both computed from state and needed by it this frame, run frame by frame (the serial part, returned); what
935
+ /// they need comes before, what needs them after; program order kept within each part (as machine.ts schedules).
936
+ private func reschedule(_ count: Int, from source: UnsafeMutablePointer<Int32>, into out: UnsafeMutablePointer<Int32>) -> Range<Int> {
937
+ let W = Int32(width)
938
+ let touches: UInt8 = 1, downstream: UInt8 = 2, upstream: UInt8 = 4
939
+ let tainted: UInt8 = 1, needed: UInt8 = 2
940
+ for i in 0..<count {
941
+ let slot = source + i * 12
942
+ var marks: UInt8 = (slot[8] >> 1) & 1 == 1 ? touches : 0
943
+ var fromState = false
944
+ forEachRead(slot) { r, state in
945
+ if state { marks |= touches }
946
+ if registerMarks[r] & tainted != 0 { fromState = true }
947
+ }
948
+ if marks & touches != 0 || fromState {
949
+ marks |= downstream
950
+ if (slot[8] >> 1) & 1 == 0 { registerMarks[Int(slot[1] / W)] |= tainted }
951
+ }
952
+ instructionMarks[i] = marks
953
+ }
954
+ for i in stride(from: count - 1, through: 0, by: -1) {
955
+ let slot = source + i * 12
956
+ guard instructionMarks[i] & touches != 0 || registerMarks[Int(slot[1] / W)] & needed != 0 else { continue }
957
+ instructionMarks[i] |= upstream
958
+ forEachRead(slot) { r, state in if !state { registerMarks[r] |= needed } }
959
+ }
960
+ var kept = 0
302
961
  var from = 0
303
962
  var to = 0
963
+ for part in 0..<3 {
964
+ if part == 1 { from = kept }
965
+ if part == 2 { to = kept }
966
+ for i in 0..<count {
967
+ let marks = instructionMarks[i]
968
+ let serial = marks & touches != 0 || (marks & downstream != 0 && marks & upstream != 0)
969
+ guard (part == 0 && !serial && marks & downstream == 0) || (part == 1 && serial) || (part == 2 && !serial && marks & downstream != 0) else { continue }
970
+ (out + kept * 12).update(from: source + i * 12, count: 12)
971
+ kept += 1
972
+ }
973
+ }
974
+ // The marks back to none, for the next time.
975
+ for i in 0..<count {
976
+ let slot = source + i * 12
977
+ registerMarks[Int(slot[1] / W)] = 0
978
+ forEachRead(slot) { r, _ in registerMarks[r] = 0 }
979
+ }
980
+ return from..<to
981
+ }
982
+
983
+ private static func decode(
984
+ _ code: [Int32], width: Int, state: Set<Int>, offsets: [Int], delays: (bases: [Int], lengths: [Int]), curves: [Int: Int]
985
+ ) -> (UnsafeMutablePointer<Int32>, Int) {
986
+ var out: [Int32] = []
304
987
  var pc = 0
305
988
  while pc < code.count {
306
- if pc == serial?.lowerBound { from = out.count / 12 }
307
- if pc == serial?.upperBound { to = out.count / 12 }
308
989
  let op = DSPOpcode(rawValue: code[pc])!
309
990
  var slot = [Int32](repeating: 0, count: 12)
310
991
  slot[0] = op.rawValue
@@ -323,41 +1004,77 @@ public final class DSPMachine: @unchecked Sendable {
323
1004
  out += slot
324
1005
  pc += 2 + op.arity + (op.builtin == nil ? 0 : 1)
325
1006
  }
326
- if pc == serial?.lowerBound { from = out.count / 12 }
327
- if pc == serial?.upperBound { to = out.count / 12 }
328
1007
  let pointer = UnsafeMutablePointer<Int32>.allocate(capacity: max(12, out.count))
329
1008
  pointer.initialize(repeating: 0, count: max(12, out.count))
330
1009
  out.withUnsafeBufferPointer { pointer.update(from: $0.baseAddress!, count: out.count) }
331
- return (pointer, out.count / 12, from..<to)
1010
+ return (pointer, out.count / 12)
332
1011
  }
333
1012
 
334
1013
  /// Lists one after another: where each starts and its length.
335
- private static func concatenate(_ lists: [[Float]]) -> (UnsafeMutablePointer<Float>, [Int], [Int]) {
336
- let total = Swift.max(1, lists.reduce(0) { $0 + $1.count })
337
- let pointer = UnsafeMutablePointer<Float>.allocate(capacity: total)
338
- pointer.initialize(repeating: 0, count: total)
339
- var bases: [Int] = []
340
- var at = 0
341
- for list in lists {
342
- bases.append(at)
343
- list.withUnsafeBufferPointer { (pointer + at).update(from: $0.baseAddress!, count: list.count) }
344
- at += list.count
345
- }
346
- return (pointer, bases, lists.map(\.count))
1014
+ private static func zeros(_ count: Int) -> UnsafeMutablePointer<Float> {
1015
+ let pointer = UnsafeMutablePointer<Float>.allocate(capacity: Swift.max(1, count))
1016
+ pointer.initialize(repeating: 0, count: Swift.max(1, count))
1017
+ return pointer
1018
+ }
1019
+
1020
+ private static func copy(_ values: [Float]) -> UnsafeMutablePointer<Float> {
1021
+ let pointer = zeros(values.count)
1022
+ values.withUnsafeBufferPointer { pointer.update(from: $0.baseAddress!, count: values.count) }
1023
+ return pointer
1024
+ }
1025
+
1026
+ /// A wavetable's samples as the machine reads them (as machine.ts `tableLevels` makes them), off the audio thread,
1027
+ /// for `replaceTable`.
1028
+ public static func tableLevels(frames: [[Float]], cosines: [[Float]]? = nil) -> DSPBuffer {
1029
+ let (pointer, count) = levels(frames: frames, cosines: cosines)
1030
+ return DSPBuffer(taking: pointer, count: count)
1031
+ }
1032
+
1033
+ /// Replaces table `index`'s samples (`tableLevels`) from the next block, swapping them with `levels`, which then
1034
+ /// holds what the machine let go of, to free off the audio thread. False (nothing swapped) where they do not fit.
1035
+ public func replaceTable(_ index: Int, levels: DSPBuffer, frames: Int) -> Bool {
1036
+ guard index >= 0, index < tableCount, frames >= 1, levels.count == frames * DSPProgram.tableLevels * DSPProgram.tableFrame else { return false }
1037
+ var total = frames
1038
+ for i in 0..<tableFrames.count where i != index { total += tableFrames[i] }
1039
+ guard total <= DSPProgram.tableFramesMax else { return false }
1040
+ swap(&tablePointers[index], &levels.pointer)
1041
+ swap(&tableSizes[index], &levels.count)
1042
+ tableFrames[index] = frames
1043
+ return true
1044
+ }
1045
+
1046
+ /// Replaces shape `index`'s points from the next block, swapping them with `points` (as `replaceTable`).
1047
+ public func replaceShape(_ index: Int, points: DSPBuffer) -> Bool {
1048
+ guard index >= 0, index < program.shapes.count, (2...DSPProgram.shapePointsMax).contains(points.count) else { return false }
1049
+ var total = points.count
1050
+ for i in 0..<shapeLengths.count where i != index { total += shapeLengths[i] }
1051
+ guard total <= DSPProgram.shapesPointsMax else { return false }
1052
+ swap(&shapePointers[index], &points.pointer)
1053
+ swap(&shapeLengths[index], &points.count)
1054
+ return true
347
1055
  }
348
1056
 
349
- /// The wavetables' samples: per table, per frame, per level (level n keeps the harmonics up to 512 >> n), 2048
350
- /// samples from an inverse FFT of the frame's sine harmonics (and cosine ones, when it has them) in doubles, stored as floats, a level's samples in
351
- /// order with each sample's frames side by side: the JavaScript machine's `buildTables`, step for step.
352
- private static func buildTables(_ tables: [DSPProgram.Table]) -> (UnsafeMutablePointer<Float>, [Int], [Int]) {
1057
+ /// Replaces sample `index`'s audio, at `rate` frames a second, from the next block, swapping it with `audio` (as
1058
+ /// `replaceTable`); a sound playing it goes on from where it is, inside the new length.
1059
+ public func replaceSample(_ index: Int, audio: DSPBuffer, rate: Double) -> Bool {
1060
+ guard index >= 0, index < program.samples.count, rate >= 1000, rate <= 384_000, audio.count >= 2 else { return false }
1061
+ var total = audio.count
1062
+ for i in 0..<sampleLengths.count where i != index { total += sampleLengths[i] }
1063
+ guard total <= DSPProgram.samplesMax else { return false }
1064
+ swap(&samplePointers[index], &audio.pointer)
1065
+ swap(&sampleLengths[index], &audio.count)
1066
+ sampleRatios[index] = Float(rate / Double(Float(sampleRate)))
1067
+ sampleSprays[index] = UInt32(Swift.max(1, (rate * 0.02).rounded(.down)))
1068
+ return true
1069
+ }
1070
+
1071
+ /// A table's samples and how many floats: each level one with the harmonics past 512, 256 … dropped, 2048 samples,
1072
+ /// each its frames side by side.
1073
+ private static func levels(frames tableFrames: [[Float]], cosines tableCosines: [[Float]]?) -> (UnsafeMutablePointer<Float>, Int) {
353
1074
  let N = DSPProgram.tableFrame
354
1075
  let levels = DSPProgram.tableLevels
355
- let frameCount = tables.reduce(0) { $0 + $1.frames.count }
356
- let count = Swift.max(1, frameCount * levels * N)
357
- let samples = UnsafeMutablePointer<Float>.allocate(capacity: count)
358
- samples.initialize(repeating: 0, count: count)
359
- var bases: [Int] = []
360
- var frames: [Int] = []
1076
+ let count = Swift.max(1, tableFrames.count) * levels * N
1077
+ let samples = zeros(count)
361
1078
  var cosT = [Double](repeating: 0, count: N / 2)
362
1079
  var sinT = [Double](repeating: 0, count: N / 2)
363
1080
  for j in 0..<(N / 2) {
@@ -366,15 +1083,12 @@ public final class DSPMachine: @unchecked Sendable {
366
1083
  }
367
1084
  var re = [Double](repeating: 0, count: N)
368
1085
  var im = [Double](repeating: 0, count: N)
369
- var at = 0
370
- for table in tables {
371
- bases.append(at)
372
- let count = table.frames.count
373
- frames.append(count)
1086
+ do {
1087
+ let count = tableFrames.count
374
1088
  // A level's samples one after another, each its frames side by side: a read between two samples and two
375
1089
  // frames is one span of memory.
376
- func store(_ level: Int, _ n: Int, _ f: Int) -> Int { at + (level * N + n) * count + f }
377
- for (f, amplitudes) in table.frames.enumerated() {
1090
+ func store(_ level: Int, _ n: Int, _ f: Int) -> Int { (level * N + n) * count + f }
1091
+ for (f, amplitudes) in tableFrames.enumerated() {
378
1092
  for level in 0..<levels {
379
1093
  let limit = Swift.min(512 >> level, amplitudes.count)
380
1094
  if level > 0 && limit == Swift.min(512 >> (level - 1), amplitudes.count) {
@@ -394,7 +1108,7 @@ public final class DSPMachine: @unchecked Sendable {
394
1108
  im[N - k] = a / 2
395
1109
  }
396
1110
  // cos(kθ) = (e^{ikθ} + e^{−ikθ}) / 2
397
- if let cosines = table.cosines?[f] {
1111
+ if let cosines = tableCosines?[f] {
398
1112
  for k in 1...limit {
399
1113
  let c = Double(cosines[k - 1])
400
1114
  re[k] = c / 2
@@ -439,9 +1153,8 @@ public final class DSPMachine: @unchecked Sendable {
439
1153
  for n in 0..<N { samples[store(level, n, f)] = Float(re[n]) }
440
1154
  }
441
1155
  }
442
- at += count * levels * N
443
1156
  }
444
- return (samples, bases, frames)
1157
+ return (samples, count)
445
1158
  }
446
1159
 
447
1160
  // MARK: Events
@@ -455,6 +1168,7 @@ public final class DSPMachine: @unchecked Sendable {
455
1168
  public func setParameter(at index: Int, _ value: Float) {
456
1169
  guard index >= 0, index < parameterRegisters.count, value.isFinite else { return }
457
1170
  targets[index] = Swift.min(Swift.max(value, parameterRanges[index].min), parameterRanges[index].max)
1171
+ retargeted = true
458
1172
  }
459
1173
 
460
1174
  /// The transport's tempo, in beats a minute (1 … 999), from the next block.
@@ -544,12 +1258,48 @@ public final class DSPMachine: @unchecked Sendable {
544
1258
  return frames >= Double(DSPProgram.blockFrames) ? DSPProgram.blockFrames : Swift.max(1, Int(frames))
545
1259
  }
546
1260
 
1261
+ /// The mode notes play in now: the voicing's parameter's, else the program's.
1262
+ private func currentMode() -> DSPProgram.Mode? {
1263
+ guard let p = modeParameter else { return program.mode }
1264
+ let m = targets[p].rounded()
1265
+ return m == 1 ? .mono : m == 2 ? .legato : nil
1266
+ }
1267
+
1268
+ /// How many voices play now: the voicing's parameter's (1 … the program's), else the program's.
1269
+ private func voiceLimit() -> Int {
1270
+ guard let p = voicesParameter else { return program.voices }
1271
+ let n = targets[p].rounded()
1272
+ return n.isNaN || n < 1 ? 1 : n >= Float(program.voices) ? program.voices : Int(n)
1273
+ }
1274
+
1275
+ /// At a block's start, where the voicing turned: the voices past those that play now let go, and the held notes a
1276
+ /// mode keeps start again from the one voice that sounds (as machine.ts does).
1277
+ private func revoice() {
1278
+ let mode = currentMode()
1279
+ let modeIndex = mode == .mono ? 1 : mode == .legato ? 2 : 0
1280
+ let limit = mode != nil ? 1 : voiceLimit()
1281
+ guard limit != voicedLimit || modeIndex != voicedMode else { return }
1282
+ let first = voicedLimit < 0
1283
+ voicedLimit = limit
1284
+ voicedMode = modeIndex
1285
+ if first { return }
1286
+ for l in limit..<Self.lanes {
1287
+ laneGate[l] = 0
1288
+ laneHeld[l] = false
1289
+ }
1290
+ stackCount = 0
1291
+ if mode != nil && laneGate[0] > 0.5 {
1292
+ stack[0] = laneNote[0]
1293
+ stackCount = 1
1294
+ }
1295
+ }
1296
+
547
1297
  private func start(_ note: Int32, velocity: Int32) {
548
1298
  if velocity <= 0 {
549
1299
  release(note)
550
1300
  return
551
1301
  }
552
- if let mode = program.mode {
1302
+ if let mode = currentMode() {
553
1303
  // One voice: the newest note, legato while another is held.
554
1304
  unstack(note)
555
1305
  if stackCount == Self.lanes {
@@ -572,7 +1322,7 @@ public final class DSPMachine: @unchecked Sendable {
572
1322
  }
573
1323
  return
574
1324
  }
575
- let voices = program.voices
1325
+ let voices = voiceLimit()
576
1326
  var lane = -1
577
1327
  for l in 0..<voices where lane < 0 && laneNote[l] == note && laneGate[l] > 0.5 { lane = l }
578
1328
  var fresh = false
@@ -602,7 +1352,7 @@ public final class DSPMachine: @unchecked Sendable {
602
1352
  }
603
1353
 
604
1354
  private func release(_ note: Int32) {
605
- if let mode = program.mode {
1355
+ if let mode = currentMode() {
606
1356
  unstack(note)
607
1357
  guard laneNote[0] == note && laneGate[0] > 0.5 else { return }
608
1358
  if stackCount > 0 {
@@ -645,6 +1395,8 @@ public final class DSPMachine: @unchecked Sendable {
645
1395
  resetLane(l)
646
1396
  }
647
1397
  resetMaster()
1398
+ // Nothing sounds: what is set before the next block jumps there, with no glide (a preset loaded as data).
1399
+ snapped = true
648
1400
  default:
649
1401
  break
650
1402
  }
@@ -747,12 +1499,24 @@ public final class DSPMachine: @unchecked Sendable {
747
1499
  let W = width
748
1500
  let R = registers
749
1501
  let sr = sampleRate
750
- // Parameters glide toward their targets, about 20 ms.
1502
+ // Parameters glide toward their targets, about 20 ms, and come to rest on them: one at rest is left as it is.
751
1503
  let alpha = snapped ? Float(1) : 1 - exp(-Float(n) / (0.02 * sr))
752
1504
  snapped = false
753
1505
  for (i, register) in parameterRegisters.enumerated() {
754
1506
  let at = R + register * W
755
- at.update(repeating: at[0] + (targets[i] - at[0]) * alpha, count: W)
1507
+ let now = at[0]
1508
+ let target = targets[i]
1509
+ if now == target { continue }
1510
+ var value = now + (target - now) * alpha
1511
+ if abs(target - value) <= 1e-6 * (1 + abs(target)) { value = target }
1512
+ at.update(repeating: value, count: W)
1513
+ }
1514
+ if retargeted || rerouted {
1515
+ let force = rerouted
1516
+ retargeted = false
1517
+ rerouted = false
1518
+ if !groupParameters.isEmpty || !choiceParameters.isEmpty || force { turnRegions(loading: false, force: force) }
1519
+ if voicesParameter != nil || modeParameter != nil { revoice() }
756
1520
  }
757
1521
  (R + 5 * W).update(repeating: Float(tempo), count: W)
758
1522
  let beatStep = tempo / 60 / Double(sr)
@@ -780,7 +1544,7 @@ public final class DSPMachine: @unchecked Sendable {
780
1544
  R[4 * W + k * V + l] = note >= 0 ? Float(note) : 0
781
1545
  }
782
1546
  }
783
- run(blockCode, 0..<blockCount, lanes: lanes, trigger: trigger, frames: F)
1547
+ run(blockCode, 0..<blockRunning, lanes: lanes, trigger: trigger, frames: F)
784
1548
  }
785
1549
  if master != nil {
786
1550
  // The newest note, the master stage's inputs (registers 7 … 10), in lane 0.
@@ -930,9 +1694,13 @@ public final class DSPMachine: @unchecked Sendable {
930
1694
  }
931
1695
  }
932
1696
  }
933
- var at = range.lowerBound * 12
1697
+ var next = range.lowerBound * 12
934
1698
  let end = range.upperBound * 12
935
- while at < end {
1699
+ while next < end {
1700
+ // A constant for the instruction: a variable the kernels' closures read would be checked for exclusive
1701
+ // access at every read.
1702
+ let at = next
1703
+ next += 12
936
1704
  let op = code[at]
937
1705
  let states = code[at + 8]
938
1706
  // A register at this frame, or a state register's one value.
@@ -1121,11 +1889,18 @@ public final class DSPMachine: @unchecked Sendable {
1121
1889
  let releaseIn = register(6)
1122
1890
  let curve = Int(code[at + 11]) - 1
1123
1891
  let curved = curve >= 0
1124
- let ea = curved ? Double(curveExponents[curve * 3]) : 1
1125
- let ed = curved ? Double(curveExponents[curve * 3 + 1]) : 1
1126
- let er = curved ? Double(curveExponents[curve * 3 + 2]) : 1
1127
- for fk in 0..<fc {
1128
- for l in 0..<lanes {
1892
+ // The curves' registers (a knob's, or modulated), read at the chunk's first frame a voice (as machine.ts
1893
+ // reads them), 1/16 … 16, 1 for NaN.
1894
+ let ra = curved ? R + curveRegisters[curve * 3] + shift : R
1895
+ let rd = curved ? R + curveRegisters[curve * 3 + 1] + shift : R
1896
+ let rr = curved ? R + curveRegisters[curve * 3 + 2] + shift : R
1897
+ for l in 0..<lanes {
1898
+ let ea = curved ? Self.exponent(ra[l]) : 1
1899
+ let ed = curved ? Self.exponent(rd[l]) : 1
1900
+ let er = curved ? Self.exponent(rr[l]) : 1
1901
+ // All three 1: no stage bent (what it put out kept all the same, for a curve turned later).
1902
+ let bent = curved && (ea != 1 || ed != 1 || er != 1)
1903
+ for fk in 0..<fc {
1129
1904
  let el = fk * V + l
1130
1905
  let gate = a[el]
1131
1906
  var stage = s[l]
@@ -1142,7 +1917,7 @@ public final class DSPMachine: @unchecked Sendable {
1142
1917
  if curved && stage != before {
1143
1918
  let last = s[8 * L + l]
1144
1919
  if stage == 1 {
1145
- level = last > 0 ? Float(Foundation.pow(Double(last), 1 / ea)) : 0
1920
+ level = last > 0 ? (ea == 1 ? last : Float(Foundation.pow(Double(last), 1 / ea))) : 0
1146
1921
  } else if stage == 4 {
1147
1922
  level = last
1148
1923
  s[9 * L + l] = last
@@ -1182,18 +1957,21 @@ public final class DSPMachine: @unchecked Sendable {
1182
1957
  s[l] = stage
1183
1958
  s[L + l] = level
1184
1959
  var out = level
1185
- if curved {
1960
+ if bent {
1961
+ // An exponent of 1 is no bend: the stage as it is, with no power worked out.
1186
1962
  if stage == 1 {
1187
- out = Float(Foundation.pow(Double(level), ea))
1963
+ out = ea == 1 ? level : Float(Foundation.pow(Double(level), ea))
1188
1964
  } else if stage == 2 {
1189
1965
  let span = 1 - sustain
1190
- out = span > 1e-6 ? sustain + span * Float(Foundation.pow(Double(Swift.max(0, (level - sustain) / span)), ed)) : level
1966
+ let along = Swift.max(0, (level - sustain) / span)
1967
+ out = span > 1e-6 ? sustain + span * (ed == 1 ? along : Float(Foundation.pow(Double(along), ed))) : level
1191
1968
  } else if stage == 4 {
1192
1969
  let from = s[9 * L + l]
1193
- out = from > 0 ? from * Float(Foundation.pow(Double(Swift.min(1, level / from)), er)) : 0
1970
+ let along = Swift.min(1, level / from)
1971
+ out = from > 0 ? from * (er == 1 ? along : Float(Foundation.pow(Double(along), er))) : 0
1194
1972
  }
1195
- s[8 * L + l] = out
1196
1973
  }
1974
+ if curved { s[8 * L + l] = out }
1197
1975
  d[el] = out
1198
1976
  }
1199
1977
  }
@@ -1234,12 +2012,12 @@ public final class DSPMachine: @unchecked Sendable {
1234
2012
  // wavetable(table, freq, position): the level that cannot alias at freq (worked out again only when freq
1235
2013
  // changes), read between samples and between frames.
1236
2014
  let s = S + Int(code[at + 7])
1237
- let T = tableSamples
1238
2015
  // The table is a constant register's, the same in every lane.
1239
2016
  let index = a[0]
1240
2017
  let t = index >= 0 && Int(exactly: index.rounded(.down)).map { $0 < tableCount } == true ? Int(index) : 0
1241
2018
  let frames = tableFrames.isEmpty ? 1 : tableFrames[t]
1242
- let base = tableBases.isEmpty ? 0 : tableBases[t]
2019
+ let T = tablePointers[t]
2020
+ let base = 0
1243
2021
  for fk in 0..<fc {
1244
2022
  for l in 0..<lanes {
1245
2023
  let el = fk * V + l
@@ -1306,13 +2084,13 @@ public final class DSPMachine: @unchecked Sendable {
1306
2084
  }
1307
2085
  case 52:
1308
2086
  // shape(shape, x): x 0 … 1 along it, between points.
1309
- let P = shapePoints
1310
2087
  for fk in 0..<fc {
1311
2088
  for l in 0..<lanes {
1312
2089
  let el = fk * V + l
1313
2090
  let t = Self.index(a[el], shapeLengths.count)
1314
2091
  let points = shapeLengths.isEmpty ? 1 : shapeLengths[t]
1315
- let base = shapeBases.isEmpty ? 0 : shapeBases[t]
2092
+ let P = shapePointers[t]
2093
+ let base = 0
1316
2094
  let x = b[el]
1317
2095
  let position = (x > 0 ? (x < 1 ? x : 1) : 0) * Float(points - 1)
1318
2096
  let i0 = Int(position.rounded(.down))
@@ -1326,13 +2104,13 @@ public final class DSPMachine: @unchecked Sendable {
1326
2104
  // between samples; silent past either end. Where it plays is a whole sample and the fraction past it,
1327
2105
  // so it keeps time.
1328
2106
  let s = S + Int(code[at + 7])
1329
- let A = sampleAudio
1330
2107
  for fk in 0..<fc {
1331
2108
  for l in 0..<lanes {
1332
2109
  let el = fk * V + l
1333
2110
  let t = Self.index(a[el], sampleLengths.count)
1334
2111
  let length = sampleLengths.isEmpty ? 2 : sampleLengths[t]
1335
- let base = sampleBases.isEmpty ? 0 : sampleBases[t]
2112
+ let A = samplePointers[t]
2113
+ let base = 0
1336
2114
  if s[2 * L + l] != trigger[l] {
1337
2115
  s[2 * L + l] = trigger[l]
1338
2116
  let start = c[el]
@@ -1363,13 +2141,13 @@ public final class DSPMachine: @unchecked Sendable {
1363
2141
  // tableread(table, phase, freq, position): wavetable's read, at a phase the program keeps (wrapped to
1364
2142
  // 0 … 1), at the level that cannot alias at freq (worked out again only when freq changes).
1365
2143
  let s = S + Int(code[at + 7])
1366
- let T = tableSamples
1367
2144
  let e = register(5)
1368
2145
  // The table is a constant register's, the same in every lane.
1369
2146
  let index = a[0]
1370
2147
  let t = index >= 0 && Int(exactly: index.rounded(.down)).map { $0 < tableCount } == true ? Int(index) : 0
1371
2148
  let frames = tableFrames.isEmpty ? 1 : tableFrames[t]
1372
- let base = tableBases.isEmpty ? 0 : tableBases[t]
2149
+ let T = tablePointers[t]
2150
+ let base = 0
1373
2151
  for fk in 0..<fc {
1374
2152
  for l in 0..<lanes {
1375
2153
  let el = fk * V + l
@@ -1408,13 +2186,13 @@ public final class DSPMachine: @unchecked Sendable {
1408
2186
  case 79:
1409
2187
  // loop(sample, rate, start): play's, but round and round: past either end it comes in at the other.
1410
2188
  let s = S + Int(code[at + 7])
1411
- let A = sampleAudio
1412
2189
  for fk in 0..<fc {
1413
2190
  for l in 0..<lanes {
1414
2191
  let el = fk * V + l
1415
2192
  let t = Self.index(a[el], sampleLengths.count)
1416
2193
  let length = sampleLengths.isEmpty ? 2 : sampleLengths[t]
1417
- let base = sampleBases.isEmpty ? 0 : sampleBases[t]
2194
+ let A = samplePointers[t]
2195
+ let base = 0
1418
2196
  if s[2 * L + l] != trigger[l] {
1419
2197
  s[2 * L + l] = trigger[l]
1420
2198
  let start = c[el]
@@ -1443,13 +2221,13 @@ public final class DSPMachine: @unchecked Sendable {
1443
2221
  let s = S + Int(code[at + 7])
1444
2222
  let lengthIn = register(5)
1445
2223
  let pitchIn = register(6)
1446
- let A = sampleAudio
1447
2224
  for fk in 0..<fc {
1448
2225
  for l in 0..<lanes {
1449
2226
  let el = fk * V + l
1450
2227
  let t = Self.index(a[el], sampleLengths.count)
1451
2228
  let length = sampleLengths.isEmpty ? 2 : sampleLengths[t]
1452
- let base = sampleBases.isEmpty ? 0 : sampleBases[t]
2229
+ let A = samplePointers[t]
2230
+ let base = 0
1453
2231
  let ratio = sampleRatios.isEmpty ? 1 : sampleRatios[t]
1454
2232
  let density = c[el]
1455
2233
  var countdown = s[l]
@@ -1508,10 +2286,10 @@ public final class DSPMachine: @unchecked Sendable {
1508
2286
  // modsum(routes): Σ amount × source over the list, route by route. An amount is a parameter, the same
1509
2287
  // in every lane, so a route at 0 is passed over once for all of them and costs nothing.
1510
2288
  each { d[$0] = 0 }
1511
- let index = Self.index(a[0], routeStarts.count - 1)
1512
- guard index < routeStarts.count - 1 else { break }
1513
- var k = routeStarts[index]
1514
- let end = routeStarts[index + 1]
2289
+ let index = Self.index(a[0], routeCount)
2290
+ guard index < routeCount else { break }
2291
+ var k = index * DSPProgram.routesMax
2292
+ let end = k + routeCounts[index]
1515
2293
  while k < end {
1516
2294
  let amount = R + routeRegisters[k + 1]
1517
2295
  let source = R + routeRegisters[k] + (routeState[k] ? 0 : shift)
@@ -1555,7 +2333,57 @@ public final class DSPMachine: @unchecked Sendable {
1555
2333
  default:
1556
2334
  break
1557
2335
  }
1558
- at += 12
1559
2336
  }
1560
2337
  }
1561
2338
  }
2339
+
2340
+ /// Memory a machine reads (a table's samples, a shape's points, a sample's audio), made off the audio thread and
2341
+ /// swapped into a machine whole (`replaceTable` …). After a swap it holds what the machine let go of: freed with it,
2342
+ /// off the audio thread.
2343
+ /// A fixed number of values in memory of their own, each changed in place: what the machine reads as it renders and
2344
+ /// swaps when data is replaced, with no exclusivity checks (as an array property changed in place would need).
2345
+ struct Slots<Element> {
2346
+ let base: UnsafeMutablePointer<Element>
2347
+ let count: Int
2348
+
2349
+ init(_ values: [Element]) {
2350
+ count = values.count
2351
+ base = .allocate(capacity: Swift.max(1, values.count))
2352
+ base.initialize(from: values, count: values.count)
2353
+ }
2354
+
2355
+ var isEmpty: Bool { count == 0 }
2356
+
2357
+ subscript(i: Int) -> Element {
2358
+ get { base[i] }
2359
+ nonmutating set { base[i] = newValue }
2360
+ }
2361
+
2362
+ func deallocate() {
2363
+ base.deinitialize(count: count)
2364
+ base.deallocate()
2365
+ }
2366
+ }
2367
+
2368
+ public final class DSPBuffer: @unchecked Sendable {
2369
+ public fileprivate(set) var pointer: UnsafeMutablePointer<Float>
2370
+ public fileprivate(set) var count: Int
2371
+
2372
+ /// `values`, copied.
2373
+ public init(_ values: [Float]) {
2374
+ let pointer = UnsafeMutablePointer<Float>.allocate(capacity: Swift.max(1, values.count))
2375
+ pointer.initialize(repeating: 0, count: Swift.max(1, values.count))
2376
+ values.withUnsafeBufferPointer { if let base = $0.baseAddress { pointer.update(from: base, count: values.count) } }
2377
+ self.pointer = pointer
2378
+ count = values.count
2379
+ }
2380
+
2381
+ fileprivate init(taking pointer: UnsafeMutablePointer<Float>, count: Int) {
2382
+ self.pointer = pointer
2383
+ self.count = count
2384
+ }
2385
+
2386
+ deinit {
2387
+ pointer.deallocate()
2388
+ }
2389
+ }