@swiftbrowser/native 0.21.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.
@@ -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,458 @@ 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
+ for k in 0..<list.count {
713
+ let r = Int(list.pointer[k])
714
+ routeRegisters[start + k] = r * width
715
+ routeState[start + k] = stateMask[r] != 0
716
+ }
717
+ routeCounts[index] = list.count
718
+ routedMask.update(repeating: 0, count: program.registers)
719
+ for i in 0..<routeCount {
720
+ for k in 0..<routeCounts[i] { routedMask[routeRegisters[i * DSPProgram.routesMax + k] / width] = 1 }
721
+ }
722
+ rerouted = true
723
+ return true
724
+ }
725
+
726
+
727
+ /// The `count` instructions in `staging` into `out`, ordered so each comes after what writes what it reads (not
728
+ /// state), and a state register's readers before what writes it (they read the frame before's); program order
729
+ /// where neither says (as machine.ts orders). False on a cycle.
730
+ private func order(_ count: Int, into out: UnsafeMutablePointer<Int32>) -> Bool {
731
+ let W = Int32(width)
732
+ let registerCount = program.registers
733
+ for i in 0..<count {
734
+ let slot = staging + i * 12
735
+ let d = Int(slot[1] / W)
736
+ if (slot[8] >> 1) & 1 == 1 {
737
+ stateWriterNext[i] = stateWriterFirst[d]
738
+ stateWriterFirst[d] = Int32(i)
739
+ } else {
740
+ writerOf[d] = Int32(i)
741
+ }
742
+ waiting[i] = 0
743
+ }
744
+ // Each register's readers (reading it not as state), in program order: counted, summed to where each ends,
745
+ // then placed back to front so each ends where it starts.
746
+ readerStarts.update(repeating: 0, count: registerCount + 1)
747
+ for i in 0..<count { forEachRead(staging + i * 12) { r, state in if !state { readerStarts[r] += 1 } } }
748
+ var total: Int32 = 0
749
+ for r in 0..<registerCount {
750
+ total += readerStarts[r]
751
+ readerStarts[r] = total
752
+ }
753
+ readerStarts[registerCount] = total
754
+ for i in stride(from: count - 1, through: 0, by: -1) {
755
+ forEachRead(staging + i * 12) { r, state in
756
+ guard !state else { return }
757
+ readerStarts[r] -= 1
758
+ readers[Int(readerStarts[r])] = Int32(i)
759
+ }
760
+ }
761
+ for i in 0..<count {
762
+ forEachRead(staging + i * 12) { r, state in
763
+ if state {
764
+ var w = stateWriterFirst[r]
765
+ while w >= 0 {
766
+ if Int(w) != i { waiting[Int(w)] += 1 }
767
+ w = stateWriterNext[Int(w)]
768
+ }
769
+ } else if writerOf[r] >= 0, Int(writerOf[r]) != i {
770
+ waiting[i] += 1
771
+ }
772
+ }
773
+ }
774
+ // The ready instructions, least first (a binary heap), so program order holds where nothing else decides.
775
+ var size = 0
776
+ func push(_ i: Int32) {
777
+ var k = size
778
+ heap[k] = i
779
+ size += 1
780
+ while k > 0, heap[(k - 1) / 2] > heap[k] {
781
+ (heap[k], heap[(k - 1) / 2]) = (heap[(k - 1) / 2], heap[k])
782
+ k = (k - 1) / 2
783
+ }
784
+ }
785
+ func pop() -> Int {
786
+ let top = heap[0]
787
+ size -= 1
788
+ heap[0] = heap[size]
789
+ var k = 0
790
+ while true {
791
+ let l = 2 * k + 1
792
+ var m = k
793
+ if l < size, heap[l] < heap[m] { m = l }
794
+ if l + 1 < size, heap[l + 1] < heap[m] { m = l + 1 }
795
+ if m == k { break }
796
+ (heap[k], heap[m]) = (heap[m], heap[k])
797
+ k = m
798
+ }
799
+ return Int(top)
800
+ }
801
+ func done(_ j: Int) {
802
+ waiting[j] -= 1
803
+ if waiting[j] == 0 { push(Int32(j)) }
804
+ }
805
+ for i in 0..<count where waiting[i] == 0 { push(Int32(i)) }
806
+ var placed = 0
807
+ while size > 0 {
808
+ let i = pop()
809
+ let slot = staging + i * 12
810
+ (out + placed * 12).update(from: slot, count: 12)
811
+ placed += 1
812
+ forEachRead(slot) { r, state in
813
+ guard state else { return }
814
+ var w = stateWriterFirst[r]
815
+ while w >= 0 {
816
+ if Int(w) != i { done(Int(w)) }
817
+ w = stateWriterNext[Int(w)]
818
+ }
819
+ }
820
+ let d = Int(slot[1] / W)
821
+ if (slot[8] >> 1) & 1 == 0, Int(writerOf[d]) == i {
822
+ for k in Int(readerStarts[d])..<Int(readerStarts[d + 1]) where Int(readers[k]) != i { done(Int(readers[k])) }
823
+ }
824
+ }
825
+ // The tables back to none, for the next time.
826
+ for i in 0..<count {
827
+ let d = Int(staging[i * 12 + 1] / W)
828
+ writerOf[d] = -1
829
+ stateWriterFirst[d] = -1
830
+ }
831
+ return placed == count
832
+ }
833
+
834
+ /// An envelope's curve exponent as read: held to 1/16 … 16, 1 for NaN (as machine.ts reads it).
835
+ @inline(__always) private static func exponent(_ x: Float) -> Double {
836
+ x >= 0.0625 ? Double(x <= 16 ? x : 16) : x < 0.0625 ? 0.0625 : 1
837
+ }
838
+
839
+ /// Each register the instruction at `slot` reads (its operands, an envelope's curves, and a modsum's routes), and
840
+ /// whether it is state.
841
+ private func forEachRead(_ slot: UnsafeMutablePointer<Int32>, _ body: (Int, Bool) -> Void) {
842
+ let W = Int32(width)
843
+ let op = DSPOpcode(rawValue: slot[0])!
844
+ for k in 0..<op.arity { body(Int(slot[2 + k] / W), (slot[8] >> (2 + k)) & 1 == 1) }
845
+ if op == .adsr, slot[11] > 0 {
846
+ let curve = Int(slot[11]) - 1
847
+ for k in 0..<3 { body(curveRegisters[curve * 3 + k] / width, false) }
848
+ }
849
+ if op == .modsum {
850
+ let index = Self.index(registers[Int(slot[2])], routeCount)
851
+ if index < routeCount {
852
+ let start = index * DSPProgram.routesMax
853
+ for k in start..<(start + routeCounts[index]) { body(routeRegisters[k] / width, routeState[k]) }
854
+ }
855
+ }
856
+ }
857
+
858
+ /// What runs, into `aliveNow` and `readNow`, with what reads a result or a choice reading what `renameTable` says:
859
+ /// from the outputs back, each instruction outside a region that is off whose register something running reads,
860
+ /// and what it reads (a state register read, what writes it). Again until nothing more runs, since a choice or
861
+ /// state may read what comes after (as machine.ts finds it).
862
+ private func liveness() {
863
+ let W = Int32(width)
864
+ readNow.update(repeating: 0, count: program.registers)
865
+ readNow[program.output] = 1
866
+ if let right = program.outputRight { readNow[right] = 1 }
867
+ for r in program.master?.outputs ?? [] { readNow[r] = 1 }
868
+ for s in 0..<3 { aliveNow[s].update(repeating: 0, count: streamCounts[s]) }
869
+ /// One pass over a stream, back to front: whether anything more runs.
870
+ func sweep(_ code: UnsafeMutablePointer<Int32>, _ count: Int, _ gates: [Int32]?, _ marks: UnsafeMutablePointer<UInt8>) -> Bool {
871
+ var more = false
872
+ for i in stride(from: count - 1, through: 0, by: -1) {
873
+ let slot = code + i * 12
874
+ guard marks[i] == 0, readNow[Int(slot[1] / W)] != 0 else { continue }
875
+ if let gates, gates[i] >= 0, groupOn[Int(gates[i])] == 0 { continue }
876
+ marks[i] = 1
877
+ more = true
878
+ forEachRead(slot) { r, _ in
879
+ let renamed = renameTable[r]
880
+ readNow[renamed >= 0 ? Int(renamed / W) : r] = 1
881
+ }
882
+ }
883
+ return more
884
+ }
885
+ var more = true
886
+ while more {
887
+ more = sweep(fullMaster, fullMasterCount, masterGates, aliveNow[2])
888
+ more = sweep(fullSample, fullSampleCount, sampleGates, aliveNow[1]) || more
889
+ more = sweep(fullBlock, blockCount, nil, aliveNow[0]) || more
890
+ }
891
+ }
892
+
893
+ /// What comes to run again (it did not when the streams were last made) starts over: a builtin as when a voice
894
+ /// starts, a state register at its first value. Nothing does when the program loads. Then what runs now is what ran.
895
+ private func revive() {
896
+ let counts = streamCounts
897
+ if made {
898
+ for s in 0..<3 {
899
+ for i in 0..<counts[s] where aliveNow[s][i] != 0 && aliveBefore[s][i] == 0 && streamInstances[s][i] >= 0 {
900
+ let instance = Int(streamInstances[s][i])
901
+ if instanceMaster[instance] { resetInstance(instance, 0) } else { for l in 0..<program.voices { resetInstance(instance, l) } }
902
+ }
903
+ }
904
+ for entry in program.state where readNow[entry.register] != 0 && readBefore[entry.register] == 0 {
905
+ if entry.master { registers[entry.register * width] = entry.initial } else { for l in 0..<program.voices { registers[entry.register * width + l] = entry.initial } }
906
+ }
907
+ }
908
+ for s in 0..<3 { aliveBefore[s].update(from: aliveNow[s], count: counts[s]) }
909
+ readBefore.update(from: readNow, count: program.registers)
910
+ made = true
911
+ }
912
+
913
+ /// A stream's instructions that run (`alive`), their operands renamed, into `staging`: how many instructions.
914
+ private func compact(_ code: UnsafeMutablePointer<Int32>, _ count: Int, _ alive: UnsafeMutablePointer<UInt8>) -> Int {
915
+ let W = Int32(width)
916
+ var kept = 0
917
+ for i in 0..<count {
918
+ if alive[i] == 0 { continue }
919
+ let at = code + i * 12
920
+ let slot = staging + kept * 12
921
+ slot.update(from: at, count: 12)
922
+ for k in 0..<DSPOpcode(rawValue: at[0])!.arity {
923
+ let r = renameTable[Int(at[2 + k] / W)]
924
+ if r >= 0 { slot[2 + k] = r }
925
+ }
926
+ kept += 1
927
+ }
928
+ return kept
929
+ }
930
+
931
+ /// A stream's schedule over the `count` instructions at `source`, into `out`: those reading or writing state, and
932
+ /// those both computed from state and needed by it this frame, run frame by frame (the serial part, returned); what
933
+ /// they need comes before, what needs them after; program order kept within each part (as machine.ts schedules).
934
+ private func reschedule(_ count: Int, from source: UnsafeMutablePointer<Int32>, into out: UnsafeMutablePointer<Int32>) -> Range<Int> {
935
+ let W = Int32(width)
936
+ let touches: UInt8 = 1, downstream: UInt8 = 2, upstream: UInt8 = 4
937
+ let tainted: UInt8 = 1, needed: UInt8 = 2
938
+ for i in 0..<count {
939
+ let slot = source + i * 12
940
+ var marks: UInt8 = (slot[8] >> 1) & 1 == 1 ? touches : 0
941
+ var fromState = false
942
+ forEachRead(slot) { r, state in
943
+ if state { marks |= touches }
944
+ if registerMarks[r] & tainted != 0 { fromState = true }
945
+ }
946
+ if marks & touches != 0 || fromState {
947
+ marks |= downstream
948
+ if (slot[8] >> 1) & 1 == 0 { registerMarks[Int(slot[1] / W)] |= tainted }
949
+ }
950
+ instructionMarks[i] = marks
951
+ }
952
+ for i in stride(from: count - 1, through: 0, by: -1) {
953
+ let slot = source + i * 12
954
+ guard instructionMarks[i] & touches != 0 || registerMarks[Int(slot[1] / W)] & needed != 0 else { continue }
955
+ instructionMarks[i] |= upstream
956
+ forEachRead(slot) { r, state in if !state { registerMarks[r] |= needed } }
957
+ }
958
+ var kept = 0
302
959
  var from = 0
303
960
  var to = 0
961
+ for part in 0..<3 {
962
+ if part == 1 { from = kept }
963
+ if part == 2 { to = kept }
964
+ for i in 0..<count {
965
+ let marks = instructionMarks[i]
966
+ let serial = marks & touches != 0 || (marks & downstream != 0 && marks & upstream != 0)
967
+ guard (part == 0 && !serial && marks & downstream == 0) || (part == 1 && serial) || (part == 2 && !serial && marks & downstream != 0) else { continue }
968
+ (out + kept * 12).update(from: source + i * 12, count: 12)
969
+ kept += 1
970
+ }
971
+ }
972
+ // The marks back to none, for the next time.
973
+ for i in 0..<count {
974
+ let slot = source + i * 12
975
+ registerMarks[Int(slot[1] / W)] = 0
976
+ forEachRead(slot) { r, _ in registerMarks[r] = 0 }
977
+ }
978
+ return from..<to
979
+ }
980
+
981
+ private static func decode(
982
+ _ code: [Int32], width: Int, state: Set<Int>, offsets: [Int], delays: (bases: [Int], lengths: [Int]), curves: [Int: Int]
983
+ ) -> (UnsafeMutablePointer<Int32>, Int) {
984
+ var out: [Int32] = []
304
985
  var pc = 0
305
986
  while pc < code.count {
306
- if pc == serial?.lowerBound { from = out.count / 12 }
307
- if pc == serial?.upperBound { to = out.count / 12 }
308
987
  let op = DSPOpcode(rawValue: code[pc])!
309
988
  var slot = [Int32](repeating: 0, count: 12)
310
989
  slot[0] = op.rawValue
@@ -323,41 +1002,77 @@ public final class DSPMachine: @unchecked Sendable {
323
1002
  out += slot
324
1003
  pc += 2 + op.arity + (op.builtin == nil ? 0 : 1)
325
1004
  }
326
- if pc == serial?.lowerBound { from = out.count / 12 }
327
- if pc == serial?.upperBound { to = out.count / 12 }
328
1005
  let pointer = UnsafeMutablePointer<Int32>.allocate(capacity: max(12, out.count))
329
1006
  pointer.initialize(repeating: 0, count: max(12, out.count))
330
1007
  out.withUnsafeBufferPointer { pointer.update(from: $0.baseAddress!, count: out.count) }
331
- return (pointer, out.count / 12, from..<to)
1008
+ return (pointer, out.count / 12)
332
1009
  }
333
1010
 
334
1011
  /// 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))
1012
+ private static func zeros(_ count: Int) -> UnsafeMutablePointer<Float> {
1013
+ let pointer = UnsafeMutablePointer<Float>.allocate(capacity: Swift.max(1, count))
1014
+ pointer.initialize(repeating: 0, count: Swift.max(1, count))
1015
+ return pointer
1016
+ }
1017
+
1018
+ private static func copy(_ values: [Float]) -> UnsafeMutablePointer<Float> {
1019
+ let pointer = zeros(values.count)
1020
+ values.withUnsafeBufferPointer { pointer.update(from: $0.baseAddress!, count: values.count) }
1021
+ return pointer
1022
+ }
1023
+
1024
+ /// A wavetable's samples as the machine reads them (as machine.ts `tableLevels` makes them), off the audio thread,
1025
+ /// for `replaceTable`.
1026
+ public static func tableLevels(frames: [[Float]], cosines: [[Float]]? = nil) -> DSPBuffer {
1027
+ let (pointer, count) = levels(frames: frames, cosines: cosines)
1028
+ return DSPBuffer(taking: pointer, count: count)
1029
+ }
1030
+
1031
+ /// Replaces table `index`'s samples (`tableLevels`) from the next block, swapping them with `levels`, which then
1032
+ /// holds what the machine let go of, to free off the audio thread. False (nothing swapped) where they do not fit.
1033
+ public func replaceTable(_ index: Int, levels: DSPBuffer, frames: Int) -> Bool {
1034
+ guard index >= 0, index < tableCount, frames >= 1, levels.count == frames * DSPProgram.tableLevels * DSPProgram.tableFrame else { return false }
1035
+ var total = frames
1036
+ for i in 0..<tableFrames.count where i != index { total += tableFrames[i] }
1037
+ guard total <= DSPProgram.tableFramesMax else { return false }
1038
+ swap(&tablePointers[index], &levels.pointer)
1039
+ swap(&tableSizes[index], &levels.count)
1040
+ tableFrames[index] = frames
1041
+ return true
1042
+ }
1043
+
1044
+ /// Replaces shape `index`'s points from the next block, swapping them with `points` (as `replaceTable`).
1045
+ public func replaceShape(_ index: Int, points: DSPBuffer) -> Bool {
1046
+ guard index >= 0, index < program.shapes.count, (2...DSPProgram.shapePointsMax).contains(points.count) else { return false }
1047
+ var total = points.count
1048
+ for i in 0..<shapeLengths.count where i != index { total += shapeLengths[i] }
1049
+ guard total <= DSPProgram.shapesPointsMax else { return false }
1050
+ swap(&shapePointers[index], &points.pointer)
1051
+ swap(&shapeLengths[index], &points.count)
1052
+ return true
347
1053
  }
348
1054
 
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]) {
1055
+ /// Replaces sample `index`'s audio, at `rate` frames a second, from the next block, swapping it with `audio` (as
1056
+ /// `replaceTable`); a sound playing it goes on from where it is, inside the new length.
1057
+ public func replaceSample(_ index: Int, audio: DSPBuffer, rate: Double) -> Bool {
1058
+ guard index >= 0, index < program.samples.count, rate >= 1000, rate <= 384_000, audio.count >= 2 else { return false }
1059
+ var total = audio.count
1060
+ for i in 0..<sampleLengths.count where i != index { total += sampleLengths[i] }
1061
+ guard total <= DSPProgram.samplesMax else { return false }
1062
+ swap(&samplePointers[index], &audio.pointer)
1063
+ swap(&sampleLengths[index], &audio.count)
1064
+ sampleRatios[index] = Float(rate / Double(Float(sampleRate)))
1065
+ sampleSprays[index] = UInt32(Swift.max(1, (rate * 0.02).rounded(.down)))
1066
+ return true
1067
+ }
1068
+
1069
+ /// A table's samples and how many floats: each level one with the harmonics past 512, 256 … dropped, 2048 samples,
1070
+ /// each its frames side by side.
1071
+ private static func levels(frames tableFrames: [[Float]], cosines tableCosines: [[Float]]?) -> (UnsafeMutablePointer<Float>, Int) {
353
1072
  let N = DSPProgram.tableFrame
354
1073
  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] = []
1074
+ let count = Swift.max(1, tableFrames.count) * levels * N
1075
+ let samples = zeros(count)
361
1076
  var cosT = [Double](repeating: 0, count: N / 2)
362
1077
  var sinT = [Double](repeating: 0, count: N / 2)
363
1078
  for j in 0..<(N / 2) {
@@ -366,15 +1081,12 @@ public final class DSPMachine: @unchecked Sendable {
366
1081
  }
367
1082
  var re = [Double](repeating: 0, count: N)
368
1083
  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)
1084
+ do {
1085
+ let count = tableFrames.count
374
1086
  // A level's samples one after another, each its frames side by side: a read between two samples and two
375
1087
  // 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() {
1088
+ func store(_ level: Int, _ n: Int, _ f: Int) -> Int { (level * N + n) * count + f }
1089
+ for (f, amplitudes) in tableFrames.enumerated() {
378
1090
  for level in 0..<levels {
379
1091
  let limit = Swift.min(512 >> level, amplitudes.count)
380
1092
  if level > 0 && limit == Swift.min(512 >> (level - 1), amplitudes.count) {
@@ -394,7 +1106,7 @@ public final class DSPMachine: @unchecked Sendable {
394
1106
  im[N - k] = a / 2
395
1107
  }
396
1108
  // cos(kθ) = (e^{ikθ} + e^{−ikθ}) / 2
397
- if let cosines = table.cosines?[f] {
1109
+ if let cosines = tableCosines?[f] {
398
1110
  for k in 1...limit {
399
1111
  let c = Double(cosines[k - 1])
400
1112
  re[k] = c / 2
@@ -439,9 +1151,8 @@ public final class DSPMachine: @unchecked Sendable {
439
1151
  for n in 0..<N { samples[store(level, n, f)] = Float(re[n]) }
440
1152
  }
441
1153
  }
442
- at += count * levels * N
443
1154
  }
444
- return (samples, bases, frames)
1155
+ return (samples, count)
445
1156
  }
446
1157
 
447
1158
  // MARK: Events
@@ -455,6 +1166,7 @@ public final class DSPMachine: @unchecked Sendable {
455
1166
  public func setParameter(at index: Int, _ value: Float) {
456
1167
  guard index >= 0, index < parameterRegisters.count, value.isFinite else { return }
457
1168
  targets[index] = Swift.min(Swift.max(value, parameterRanges[index].min), parameterRanges[index].max)
1169
+ retargeted = true
458
1170
  }
459
1171
 
460
1172
  /// The transport's tempo, in beats a minute (1 … 999), from the next block.
@@ -544,12 +1256,48 @@ public final class DSPMachine: @unchecked Sendable {
544
1256
  return frames >= Double(DSPProgram.blockFrames) ? DSPProgram.blockFrames : Swift.max(1, Int(frames))
545
1257
  }
546
1258
 
1259
+ /// The mode notes play in now: the voicing's parameter's, else the program's.
1260
+ private func currentMode() -> DSPProgram.Mode? {
1261
+ guard let p = modeParameter else { return program.mode }
1262
+ let m = targets[p].rounded()
1263
+ return m == 1 ? .mono : m == 2 ? .legato : nil
1264
+ }
1265
+
1266
+ /// How many voices play now: the voicing's parameter's (1 … the program's), else the program's.
1267
+ private func voiceLimit() -> Int {
1268
+ guard let p = voicesParameter else { return program.voices }
1269
+ let n = targets[p].rounded()
1270
+ return n.isNaN || n < 1 ? 1 : n >= Float(program.voices) ? program.voices : Int(n)
1271
+ }
1272
+
1273
+ /// At a block's start, where the voicing turned: the voices past those that play now let go, and the held notes a
1274
+ /// mode keeps start again from the one voice that sounds (as machine.ts does).
1275
+ private func revoice() {
1276
+ let mode = currentMode()
1277
+ let modeIndex = mode == .mono ? 1 : mode == .legato ? 2 : 0
1278
+ let limit = mode != nil ? 1 : voiceLimit()
1279
+ guard limit != voicedLimit || modeIndex != voicedMode else { return }
1280
+ let first = voicedLimit < 0
1281
+ voicedLimit = limit
1282
+ voicedMode = modeIndex
1283
+ if first { return }
1284
+ for l in limit..<Self.lanes {
1285
+ laneGate[l] = 0
1286
+ laneHeld[l] = false
1287
+ }
1288
+ stackCount = 0
1289
+ if mode != nil && laneGate[0] > 0.5 {
1290
+ stack[0] = laneNote[0]
1291
+ stackCount = 1
1292
+ }
1293
+ }
1294
+
547
1295
  private func start(_ note: Int32, velocity: Int32) {
548
1296
  if velocity <= 0 {
549
1297
  release(note)
550
1298
  return
551
1299
  }
552
- if let mode = program.mode {
1300
+ if let mode = currentMode() {
553
1301
  // One voice: the newest note, legato while another is held.
554
1302
  unstack(note)
555
1303
  if stackCount == Self.lanes {
@@ -572,7 +1320,7 @@ public final class DSPMachine: @unchecked Sendable {
572
1320
  }
573
1321
  return
574
1322
  }
575
- let voices = program.voices
1323
+ let voices = voiceLimit()
576
1324
  var lane = -1
577
1325
  for l in 0..<voices where lane < 0 && laneNote[l] == note && laneGate[l] > 0.5 { lane = l }
578
1326
  var fresh = false
@@ -602,7 +1350,7 @@ public final class DSPMachine: @unchecked Sendable {
602
1350
  }
603
1351
 
604
1352
  private func release(_ note: Int32) {
605
- if let mode = program.mode {
1353
+ if let mode = currentMode() {
606
1354
  unstack(note)
607
1355
  guard laneNote[0] == note && laneGate[0] > 0.5 else { return }
608
1356
  if stackCount > 0 {
@@ -747,12 +1495,24 @@ public final class DSPMachine: @unchecked Sendable {
747
1495
  let W = width
748
1496
  let R = registers
749
1497
  let sr = sampleRate
750
- // Parameters glide toward their targets, about 20 ms.
1498
+ // Parameters glide toward their targets, about 20 ms, and come to rest on them: one at rest is left as it is.
751
1499
  let alpha = snapped ? Float(1) : 1 - exp(-Float(n) / (0.02 * sr))
752
1500
  snapped = false
753
1501
  for (i, register) in parameterRegisters.enumerated() {
754
1502
  let at = R + register * W
755
- at.update(repeating: at[0] + (targets[i] - at[0]) * alpha, count: W)
1503
+ let now = at[0]
1504
+ let target = targets[i]
1505
+ if now == target { continue }
1506
+ var value = now + (target - now) * alpha
1507
+ if abs(target - value) <= 1e-6 * (1 + abs(target)) { value = target }
1508
+ at.update(repeating: value, count: W)
1509
+ }
1510
+ if retargeted || rerouted {
1511
+ let force = rerouted
1512
+ retargeted = false
1513
+ rerouted = false
1514
+ if !groupParameters.isEmpty || !choiceParameters.isEmpty || force { turnRegions(loading: false, force: force) }
1515
+ if voicesParameter != nil || modeParameter != nil { revoice() }
756
1516
  }
757
1517
  (R + 5 * W).update(repeating: Float(tempo), count: W)
758
1518
  let beatStep = tempo / 60 / Double(sr)
@@ -780,7 +1540,7 @@ public final class DSPMachine: @unchecked Sendable {
780
1540
  R[4 * W + k * V + l] = note >= 0 ? Float(note) : 0
781
1541
  }
782
1542
  }
783
- run(blockCode, 0..<blockCount, lanes: lanes, trigger: trigger, frames: F)
1543
+ run(blockCode, 0..<blockRunning, lanes: lanes, trigger: trigger, frames: F)
784
1544
  }
785
1545
  if master != nil {
786
1546
  // The newest note, the master stage's inputs (registers 7 … 10), in lane 0.
@@ -930,9 +1690,13 @@ public final class DSPMachine: @unchecked Sendable {
930
1690
  }
931
1691
  }
932
1692
  }
933
- var at = range.lowerBound * 12
1693
+ var next = range.lowerBound * 12
934
1694
  let end = range.upperBound * 12
935
- while at < end {
1695
+ while next < end {
1696
+ // A constant for the instruction: a variable the kernels' closures read would be checked for exclusive
1697
+ // access at every read.
1698
+ let at = next
1699
+ next += 12
936
1700
  let op = code[at]
937
1701
  let states = code[at + 8]
938
1702
  // A register at this frame, or a state register's one value.
@@ -1121,11 +1885,18 @@ public final class DSPMachine: @unchecked Sendable {
1121
1885
  let releaseIn = register(6)
1122
1886
  let curve = Int(code[at + 11]) - 1
1123
1887
  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 {
1888
+ // The curves' registers (a knob's, or modulated), read at the chunk's first frame a voice (as machine.ts
1889
+ // reads them), 1/16 … 16, 1 for NaN.
1890
+ let ra = curved ? R + curveRegisters[curve * 3] + shift : R
1891
+ let rd = curved ? R + curveRegisters[curve * 3 + 1] + shift : R
1892
+ let rr = curved ? R + curveRegisters[curve * 3 + 2] + shift : R
1893
+ for l in 0..<lanes {
1894
+ let ea = curved ? Self.exponent(ra[l]) : 1
1895
+ let ed = curved ? Self.exponent(rd[l]) : 1
1896
+ let er = curved ? Self.exponent(rr[l]) : 1
1897
+ // All three 1: no stage bent (what it put out kept all the same, for a curve turned later).
1898
+ let bent = curved && (ea != 1 || ed != 1 || er != 1)
1899
+ for fk in 0..<fc {
1129
1900
  let el = fk * V + l
1130
1901
  let gate = a[el]
1131
1902
  var stage = s[l]
@@ -1142,7 +1913,7 @@ public final class DSPMachine: @unchecked Sendable {
1142
1913
  if curved && stage != before {
1143
1914
  let last = s[8 * L + l]
1144
1915
  if stage == 1 {
1145
- level = last > 0 ? Float(Foundation.pow(Double(last), 1 / ea)) : 0
1916
+ level = last > 0 ? (ea == 1 ? last : Float(Foundation.pow(Double(last), 1 / ea))) : 0
1146
1917
  } else if stage == 4 {
1147
1918
  level = last
1148
1919
  s[9 * L + l] = last
@@ -1182,18 +1953,21 @@ public final class DSPMachine: @unchecked Sendable {
1182
1953
  s[l] = stage
1183
1954
  s[L + l] = level
1184
1955
  var out = level
1185
- if curved {
1956
+ if bent {
1957
+ // An exponent of 1 is no bend: the stage as it is, with no power worked out.
1186
1958
  if stage == 1 {
1187
- out = Float(Foundation.pow(Double(level), ea))
1959
+ out = ea == 1 ? level : Float(Foundation.pow(Double(level), ea))
1188
1960
  } else if stage == 2 {
1189
1961
  let span = 1 - sustain
1190
- out = span > 1e-6 ? sustain + span * Float(Foundation.pow(Double(Swift.max(0, (level - sustain) / span)), ed)) : level
1962
+ let along = Swift.max(0, (level - sustain) / span)
1963
+ out = span > 1e-6 ? sustain + span * (ed == 1 ? along : Float(Foundation.pow(Double(along), ed))) : level
1191
1964
  } else if stage == 4 {
1192
1965
  let from = s[9 * L + l]
1193
- out = from > 0 ? from * Float(Foundation.pow(Double(Swift.min(1, level / from)), er)) : 0
1966
+ let along = Swift.min(1, level / from)
1967
+ out = from > 0 ? from * (er == 1 ? along : Float(Foundation.pow(Double(along), er))) : 0
1194
1968
  }
1195
- s[8 * L + l] = out
1196
1969
  }
1970
+ if curved { s[8 * L + l] = out }
1197
1971
  d[el] = out
1198
1972
  }
1199
1973
  }
@@ -1234,12 +2008,12 @@ public final class DSPMachine: @unchecked Sendable {
1234
2008
  // wavetable(table, freq, position): the level that cannot alias at freq (worked out again only when freq
1235
2009
  // changes), read between samples and between frames.
1236
2010
  let s = S + Int(code[at + 7])
1237
- let T = tableSamples
1238
2011
  // The table is a constant register's, the same in every lane.
1239
2012
  let index = a[0]
1240
2013
  let t = index >= 0 && Int(exactly: index.rounded(.down)).map { $0 < tableCount } == true ? Int(index) : 0
1241
2014
  let frames = tableFrames.isEmpty ? 1 : tableFrames[t]
1242
- let base = tableBases.isEmpty ? 0 : tableBases[t]
2015
+ let T = tablePointers[t]
2016
+ let base = 0
1243
2017
  for fk in 0..<fc {
1244
2018
  for l in 0..<lanes {
1245
2019
  let el = fk * V + l
@@ -1306,13 +2080,13 @@ public final class DSPMachine: @unchecked Sendable {
1306
2080
  }
1307
2081
  case 52:
1308
2082
  // shape(shape, x): x 0 … 1 along it, between points.
1309
- let P = shapePoints
1310
2083
  for fk in 0..<fc {
1311
2084
  for l in 0..<lanes {
1312
2085
  let el = fk * V + l
1313
2086
  let t = Self.index(a[el], shapeLengths.count)
1314
2087
  let points = shapeLengths.isEmpty ? 1 : shapeLengths[t]
1315
- let base = shapeBases.isEmpty ? 0 : shapeBases[t]
2088
+ let P = shapePointers[t]
2089
+ let base = 0
1316
2090
  let x = b[el]
1317
2091
  let position = (x > 0 ? (x < 1 ? x : 1) : 0) * Float(points - 1)
1318
2092
  let i0 = Int(position.rounded(.down))
@@ -1326,13 +2100,13 @@ public final class DSPMachine: @unchecked Sendable {
1326
2100
  // between samples; silent past either end. Where it plays is a whole sample and the fraction past it,
1327
2101
  // so it keeps time.
1328
2102
  let s = S + Int(code[at + 7])
1329
- let A = sampleAudio
1330
2103
  for fk in 0..<fc {
1331
2104
  for l in 0..<lanes {
1332
2105
  let el = fk * V + l
1333
2106
  let t = Self.index(a[el], sampleLengths.count)
1334
2107
  let length = sampleLengths.isEmpty ? 2 : sampleLengths[t]
1335
- let base = sampleBases.isEmpty ? 0 : sampleBases[t]
2108
+ let A = samplePointers[t]
2109
+ let base = 0
1336
2110
  if s[2 * L + l] != trigger[l] {
1337
2111
  s[2 * L + l] = trigger[l]
1338
2112
  let start = c[el]
@@ -1363,13 +2137,13 @@ public final class DSPMachine: @unchecked Sendable {
1363
2137
  // tableread(table, phase, freq, position): wavetable's read, at a phase the program keeps (wrapped to
1364
2138
  // 0 … 1), at the level that cannot alias at freq (worked out again only when freq changes).
1365
2139
  let s = S + Int(code[at + 7])
1366
- let T = tableSamples
1367
2140
  let e = register(5)
1368
2141
  // The table is a constant register's, the same in every lane.
1369
2142
  let index = a[0]
1370
2143
  let t = index >= 0 && Int(exactly: index.rounded(.down)).map { $0 < tableCount } == true ? Int(index) : 0
1371
2144
  let frames = tableFrames.isEmpty ? 1 : tableFrames[t]
1372
- let base = tableBases.isEmpty ? 0 : tableBases[t]
2145
+ let T = tablePointers[t]
2146
+ let base = 0
1373
2147
  for fk in 0..<fc {
1374
2148
  for l in 0..<lanes {
1375
2149
  let el = fk * V + l
@@ -1408,13 +2182,13 @@ public final class DSPMachine: @unchecked Sendable {
1408
2182
  case 79:
1409
2183
  // loop(sample, rate, start): play's, but round and round: past either end it comes in at the other.
1410
2184
  let s = S + Int(code[at + 7])
1411
- let A = sampleAudio
1412
2185
  for fk in 0..<fc {
1413
2186
  for l in 0..<lanes {
1414
2187
  let el = fk * V + l
1415
2188
  let t = Self.index(a[el], sampleLengths.count)
1416
2189
  let length = sampleLengths.isEmpty ? 2 : sampleLengths[t]
1417
- let base = sampleBases.isEmpty ? 0 : sampleBases[t]
2190
+ let A = samplePointers[t]
2191
+ let base = 0
1418
2192
  if s[2 * L + l] != trigger[l] {
1419
2193
  s[2 * L + l] = trigger[l]
1420
2194
  let start = c[el]
@@ -1443,13 +2217,13 @@ public final class DSPMachine: @unchecked Sendable {
1443
2217
  let s = S + Int(code[at + 7])
1444
2218
  let lengthIn = register(5)
1445
2219
  let pitchIn = register(6)
1446
- let A = sampleAudio
1447
2220
  for fk in 0..<fc {
1448
2221
  for l in 0..<lanes {
1449
2222
  let el = fk * V + l
1450
2223
  let t = Self.index(a[el], sampleLengths.count)
1451
2224
  let length = sampleLengths.isEmpty ? 2 : sampleLengths[t]
1452
- let base = sampleBases.isEmpty ? 0 : sampleBases[t]
2225
+ let A = samplePointers[t]
2226
+ let base = 0
1453
2227
  let ratio = sampleRatios.isEmpty ? 1 : sampleRatios[t]
1454
2228
  let density = c[el]
1455
2229
  var countdown = s[l]
@@ -1508,10 +2282,10 @@ public final class DSPMachine: @unchecked Sendable {
1508
2282
  // modsum(routes): Σ amount × source over the list, route by route. An amount is a parameter, the same
1509
2283
  // in every lane, so a route at 0 is passed over once for all of them and costs nothing.
1510
2284
  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]
2285
+ let index = Self.index(a[0], routeCount)
2286
+ guard index < routeCount else { break }
2287
+ var k = index * DSPProgram.routesMax
2288
+ let end = k + routeCounts[index]
1515
2289
  while k < end {
1516
2290
  let amount = R + routeRegisters[k + 1]
1517
2291
  let source = R + routeRegisters[k] + (routeState[k] ? 0 : shift)
@@ -1555,7 +2329,57 @@ public final class DSPMachine: @unchecked Sendable {
1555
2329
  default:
1556
2330
  break
1557
2331
  }
1558
- at += 12
1559
2332
  }
1560
2333
  }
1561
2334
  }
2335
+
2336
+ /// Memory a machine reads (a table's samples, a shape's points, a sample's audio), made off the audio thread and
2337
+ /// swapped into a machine whole (`replaceTable` …). After a swap it holds what the machine let go of: freed with it,
2338
+ /// off the audio thread.
2339
+ /// A fixed number of values in memory of their own, each changed in place: what the machine reads as it renders and
2340
+ /// swaps when data is replaced, with no exclusivity checks (as an array property changed in place would need).
2341
+ struct Slots<Element> {
2342
+ let base: UnsafeMutablePointer<Element>
2343
+ let count: Int
2344
+
2345
+ init(_ values: [Element]) {
2346
+ count = values.count
2347
+ base = .allocate(capacity: Swift.max(1, values.count))
2348
+ base.initialize(from: values, count: values.count)
2349
+ }
2350
+
2351
+ var isEmpty: Bool { count == 0 }
2352
+
2353
+ subscript(i: Int) -> Element {
2354
+ get { base[i] }
2355
+ nonmutating set { base[i] = newValue }
2356
+ }
2357
+
2358
+ func deallocate() {
2359
+ base.deinitialize(count: count)
2360
+ base.deallocate()
2361
+ }
2362
+ }
2363
+
2364
+ public final class DSPBuffer: @unchecked Sendable {
2365
+ public fileprivate(set) var pointer: UnsafeMutablePointer<Float>
2366
+ public fileprivate(set) var count: Int
2367
+
2368
+ /// `values`, copied.
2369
+ public init(_ values: [Float]) {
2370
+ let pointer = UnsafeMutablePointer<Float>.allocate(capacity: Swift.max(1, values.count))
2371
+ pointer.initialize(repeating: 0, count: Swift.max(1, values.count))
2372
+ values.withUnsafeBufferPointer { if let base = $0.baseAddress { pointer.update(from: base, count: values.count) } }
2373
+ self.pointer = pointer
2374
+ count = values.count
2375
+ }
2376
+
2377
+ fileprivate init(taking pointer: UnsafeMutablePointer<Float>, count: Int) {
2378
+ self.pointer = pointer
2379
+ self.count = count
2380
+ }
2381
+
2382
+ deinit {
2383
+ pointer.deallocate()
2384
+ }
2385
+ }