package audio import ( "bytes" "encoding/binary" "math" ) const sampleRate = 48000 // PipeWire native rate // seg is one tone segment: linear frequency sweep with pluck envelope type seg struct { f0, f1 float64 // Hz dur float64 // seconds amp float64 // 0..1 square bool } // effectSegs defines each effect's tone sequence — the tuning surface var effectSegs = [EffectCount][]seg{ EffectPickup: {{988, 988, 0.055, 0.5, false}, {1319, 1319, 0.11, 0.5, false}}, // B5→E6 coin EffectMagnet: {{523, 1319, 0.10, 0.45, false}, {784, 1976, 0.16, 0.4, false}}, // rising double sweep — reads as a suction, distinct from the coin EffectShield: {{659, 988, 0.07, 0.45, false}, {1319, 1319, 0.16, 0.40, false}}, // chime up into a held tone — armor EffectShieldBreak: {{1200, 300, 0.06, 0.50, true}, {700, 180, 0.14, 0.45, true}}, // square shatter; brighter and shorter than the death crunch EffectBoost: {{392, 1568, 0.16, 0.50, false}, {1568, 1568, 0.10, 0.35, false}}, // fast sweep up — tempo kick EffectDeflect: {{1200, 1600, 0.025, 0.20, true}, {1600, 900, 0.035, 0.15, true}}, // fires once per walled chord traversed under a Boost — short, dry, low EffectMoveRow: {{300, 620, 0.07, 0.35, false}}, // whoosh up EffectMoveLane: {{440, 470, 0.04, 0.28, false}}, // tick EffectHitWall: {{140, 50, 0.30, 0.6, true}}, // crunch drop EffectLevelClear: { // C-major arpeggio {523, 523, 0.09, 0.45, false}, {659, 659, 0.09, 0.45, false}, {784, 784, 0.09, 0.45, false}, {1047, 1047, 0.18, 0.45, false}, }, } // synth renders segments to 16-bit mono PCM func synth(segs []seg) []int16 { var out []int16 for _, s := range segs { n := int(s.dur * sampleRate) phase := 0.0 for i := range n { t := float64(i) / float64(n) f := s.f0 + (s.f1-s.f0)*t phase += 2 * math.Pi * f / sampleRate v := math.Sin(phase) if s.square { if v >= 0 { v = 1 } else { v = -1 } } env := math.Min(t*24, 1) * math.Exp(-4.2*t) // fast attack, pluck decay out = append(out, int16(v*env*s.amp*32767)) } } return out } // wavBytes wraps PCM in a minimal RIFF/WAVE container (PCM16 mono) func wavBytes(samples []int16) []byte { var b bytes.Buffer dataLen := uint32(len(samples) * 2) b.WriteString("RIFF") binary.Write(&b, binary.LittleEndian, 36+dataLen) b.WriteString("WAVEfmt ") binary.Write(&b, binary.LittleEndian, uint32(16)) binary.Write(&b, binary.LittleEndian, uint16(1)) // PCM binary.Write(&b, binary.LittleEndian, uint16(1)) // mono binary.Write(&b, binary.LittleEndian, uint32(sampleRate)) binary.Write(&b, binary.LittleEndian, uint32(sampleRate*2)) // byte rate binary.Write(&b, binary.LittleEndian, uint16(2)) // block align binary.Write(&b, binary.LittleEndian, uint16(16)) // bits/sample b.WriteString("data") binary.Write(&b, binary.LittleEndian, dataLen) binary.Write(&b, binary.LittleEndian, samples) return b.Bytes() }