// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package sound import ( "bytes" "encoding/binary" "math" "time" ) // tone_wav generates mono, 16-bit PCM at 22050 Hz for the existing Player. // Callers supply a positive frequency/duration, amplitude in [0, 1], and a // nonnegative edge ramp. A zero ramp preserves an unsmoothed sine tone. // Playback volume, pan, queuing, and cancellation belong to the sound service. func tone_wav(frequency float64, duration time.Duration, amplitude float64, ramp time.Duration) []byte { const sampleRate = 22050 sampleCount := int(float64(sampleRate) * duration.Seconds()) rampSamples := int(float64(sampleRate) * ramp.Seconds()) dataSize := sampleCount * 2 buffer := bytes.NewBuffer(make([]byte, 0, 44+dataSize)) buffer.WriteString("RIFF") _ = binary.Write(buffer, binary.LittleEndian, uint32(36+dataSize)) buffer.WriteString("WAVEfmt ") _ = binary.Write(buffer, binary.LittleEndian, uint32(16)) _ = binary.Write(buffer, binary.LittleEndian, uint16(1)) _ = binary.Write(buffer, binary.LittleEndian, uint16(1)) _ = binary.Write(buffer, binary.LittleEndian, uint32(sampleRate)) _ = binary.Write(buffer, binary.LittleEndian, uint32(sampleRate*2)) _ = binary.Write(buffer, binary.LittleEndian, uint16(2)) _ = binary.Write(buffer, binary.LittleEndian, uint16(16)) buffer.WriteString("data") _ = binary.Write(buffer, binary.LittleEndian, uint32(dataSize)) for sample := 0; sample < sampleCount; sample++ { envelope := 1.0 if rampSamples > 0 { envelope = min(1.0, float64(min(sample, sampleCount-1-sample))/float64(rampSamples)) } value := int16(math.Sin(2*math.Pi*frequency*float64(sample)/sampleRate) * amplitude * envelope * math.MaxInt16) _ = binary.Write(buffer, binary.LittleEndian, value) } return buffer.Bytes() }