import Foundation // Minimal mono 16-bit PCM WAV reader / writer + trim + normalize. // Matches the same shape as pop/hellsine/bin/cut-shakes.mjs so trimmed // samples are interchangeable across the toolchain. enum WavIO { static func load(path: String) -> (samples: [Float], sr: Int)? { guard let buf = try? Data(contentsOf: URL(fileURLWithPath: path)) else { return nil } var dOff = 0 var dLen = 0 var sr = 48000 var bits = 16 var channels = 1 var pos = 12 while pos + 8 <= buf.count { let id = String(data: buf.subdata(in: pos.. 0 else { return nil } let bytesPerSample = bits / 8 let frameSize = bytesPerSample * channels let frames = dLen / frameSize var out = [Float](repeating: 0, count: frames) for i in 0.. Bool { guard let (s, sr) = load(path: path) else { return false } if s.isEmpty { return false } var peak: Float = 0 for v in s { peak = max(peak, abs(v)) } if peak < 1e-4 { return false } let th = peak * Float(thresholdPctOfPeak) var a = 0, b = s.count while a < b && abs(s[a]) < th { a += 1 } while b > a && abs(s[b - 1]) < th { b -= 1 } a = max(0, a - Int(Double(sr) * padHeadMs / 1000)) b = min(s.count, b + Int(Double(sr) * padTailMs / 1000)) var out = Array(s[a.. 0 ? normPeak / outPeak : 1 let fadeN = Int(Double(sr) * fadeMs / 1000) for k in 0.. UInt32 { UInt32(self[o]) | UInt32(self[o + 1]) << 8 | UInt32(self[o + 2]) << 16 | UInt32(self[o + 3]) << 24 } func uInt16LE(at o: Int) -> UInt16 { UInt16(self[o]) | UInt16(self[o + 1]) << 8 } } private extension UInt32 { var leBytes: Data { var v = self.littleEndian return Data(bytes: &v, count: 4) } } private extension UInt16 { var leBytes: Data { var v = self.littleEndian return Data(bytes: &v, count: 2) } }