65 lines
2.6 KiB
Go
65 lines
2.6 KiB
Go
// 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"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const progressCuePrefix = "generated-progress-tone:"
|
|
|
|
func ProgressCueName(percentage float64) string {
|
|
return progressCuePrefix + strconv.FormatFloat(percentage, 'g', -1, 64)
|
|
}
|
|
|
|
func progress_percentage(cue string) (float64, bool) {
|
|
if !strings.HasPrefix(cue, progressCuePrefix) {
|
|
return 0, false
|
|
}
|
|
percentage, err := strconv.ParseFloat(strings.TrimPrefix(cue, progressCuePrefix), 64)
|
|
return percentage, err == nil && !math.IsNaN(percentage) && !math.IsInf(percentage, 0) && percentage >= 0 && percentage <= 100
|
|
}
|
|
|
|
// Match NVDA's default ProgressBar event_valueChange and generateBeep:
|
|
// 110 * 2^(percentage/25) Hz, 40 ms padded to the next integer cycle,
|
|
// 44100 Hz stereo PCM, and a sine clipped after multiplying by two.
|
|
// NVDA passes frequency through a C float and defaults each channel to 50%.
|
|
// Reference: nvaccess/nvda a4a89720ccbf3973a0e1e50a76dd8bff38db3d15,
|
|
// source/NVDAObjects/behaviors.py and nvdaHelper/local/beeps.cpp.
|
|
func progress_tone_wav(percentage float64) []byte {
|
|
const sampleRate = 44100
|
|
frequency := float64(float32(110 * math.Pow(2, percentage/25)))
|
|
// sampleRate/hz is evaluated as a float in NVDA's C++ generator.
|
|
cycleLength := float64(float32(sampleRate) / float32(frequency))
|
|
samplesPerCycle := int(cycleLength)
|
|
sampleCount := int(0.040 / (1.0 / sampleRate))
|
|
sampleCount += samplesPerCycle - sampleCount%samplesPerCycle
|
|
dataSize := sampleCount * 4
|
|
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(2))
|
|
_ = binary.Write(buffer, binary.LittleEndian, uint32(sampleRate))
|
|
_ = binary.Write(buffer, binary.LittleEndian, uint32(sampleRate*4))
|
|
_ = binary.Write(buffer, binary.LittleEndian, uint16(4))
|
|
_ = binary.Write(buffer, binary.LittleEndian, uint16(16))
|
|
buffer.WriteString("data")
|
|
_ = binary.Write(buffer, binary.LittleEndian, uint32(dataSize))
|
|
sinFrequency := 2 * math.Pi / cycleLength
|
|
for sample := 0; sample < sampleCount; sample++ {
|
|
value := int16(min(1.0, max(-1.0, math.Sin(float64(sample%sampleRate)*sinFrequency)*2)) * 7000)
|
|
_ = binary.Write(buffer, binary.LittleEndian, value)
|
|
_ = binary.Write(buffer, binary.LittleEndian, value)
|
|
}
|
|
return buffer.Bytes()
|
|
}
|