package sound import "math" const rate = 48000 type wave uint8 const ( sine wave = iota triangle ) // note is one tone sweeping from f0 to f1 Hz. type note struct { at, dur float64 f0, f1 float64 gain float64 wave wave } var scores = [cueCount][]note{ Tap: {{0, 0.025, 1800, 1200, 0.5, sine}}, Toggle: {{0, 0.03, 880, 880, 0.4, sine}, {0.035, 0.03, 1320, 1320, 0.4, sine}}, Select: {{0, 0.05, 1046, 1046, 0.4, sine}}, Tick: {{0, 0.008, 3000, 3000, 0.3, sine}}, Open: {{0, 0.09, 500, 900, 0.4, sine}}, Close: {{0, 0.09, 900, 500, 0.4, sine}}, Pop: {{0, 0.04, 400, 1200, 0.5, sine}}, Drop: {{0, 0.08, 700, 200, 0.5, sine}}, Success: {{0, 0.12, 523, 523, 0.3, sine}, {0.07, 0.12, 659, 659, 0.3, sine}, {0.14, 0.18, 784, 784, 0.3, sine}}, Warning: {{0, 0.09, 660, 660, 0.35, triangle}, {0.13, 0.09, 660, 660, 0.35, triangle}}, Error: {{0, 0.08, 220, 180, 0.4, triangle}, {0.1, 0.08, 220, 180, 0.4, triangle}}, Notify: {{0, 0.25, 784, 784, 0.3, sine}, {0.1, 0.3, 1046, 1046, 0.3, sine}}, Send: {{0, 0.12, 300, 1500, 0.35, triangle}}, Delete: {{0, 0.14, 600, 150, 0.4, triangle}}, Swipe: {{0, 0.07, 1200, 400, 0.25, sine}}, } // synth renders a cue's notes to mono samples. func synth(c Cue) []float32 { score := scores[c] if len(score) == 0 { panic("sound: no score for " + c.String()) } end := 0.0 for _, n := range score { end = max(end, n.at+n.dur) } out := make([]float32, int(end*rate)+1) for _, n := range score { start, frames := int(n.at*rate), int(n.dur*rate) phase := 0.0 for i := range frames { t := float64(i) / rate f := n.f0 + (n.f1-n.f0)*t/n.dur phase += 2 * math.Pi * f / rate out[start+i] += float32(n.gain * envelope(t, n.dur) * shape(n.wave, phase)) } } return out } // envelope: 3 ms attack, decay, 2 ms fade. func envelope(t, dur float64) float64 { e := math.Exp(-5 * t / dur) if t < 0.003 { e *= t / 0.003 } if left := dur - t; left < 0.002 { e *= left / 0.002 } return e } func shape(w wave, phase float64) float64 { if w == triangle { return 2 / math.Pi * math.Asin(math.Sin(phase)) } return math.Sin(phase) }