After commit c018f5fd29 Fenrir is being switched over to go. If you do not want to be part of the testing process, please switch to master.

This commit is contained in:
Storm Dragon committed 2026-10-27 20:30:55 -04:00
1 parent c018f5fd29
commit 0e50de4c76
1047 files changed
+58364 -63747

No files matched your search

+104
View File
@@ -0,0 +1,104 @@
// Fenrir console screen reader.
// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
// Tone synthesis adapted from Cthulhu (Storm Dragon and Angus Kola).
package sound
import "math"
// A small gain lift for progress tones, independent of normal cue volume.
const progressGain = 1.03 * 1.02
// Precompute integer percentages before opening the stream. Admission and the
// audio callback only move pointers; fractional progress rounds to the nearest
// percent. The cache is bounded to 101 short clips (about 1.6 MiB).
func progress_clips() [101]*clip {
var clips [101]*clip
for percentage := range clips {
clips[percentage] = progress_tone(float64(percentage))
}
return clips
}
// Match Cthulhu's NVDA-style tones: 110 * 2^(percent/25) Hz, clipped sine,
// 40 ms padded to an integer cycle, 7000/32768 amplitude per channel. Generate
// at its 44100 Hz rate, then interpolate onto our shared 48000 Hz stream.
func progress_tone(percentage float64) *clip {
return progress_tone_shape(percentage, false)
}
// The triangle variant shares progress's pitch, duration and resampling.
func progress_tone_shape(percentage float64, triangle bool) *clip {
const sourceRate = 44100
frequency := float32(110 * math.Pow(2, percentage/25))
cycleLength := float64(float32(sourceRate) / frequency)
samplesPerCycle := int(cycleLength)
count := sourceRate * 40 / 1000
count += samplesPerCycle - count%samplesPerCycle
source := make([]float32, count)
for index := range source {
phase := float64(index) * 2 * math.Pi / cycleLength
wave := min(1.0, max(-1.0, math.Sin(phase)*2))
if triangle {
wave = 2 / math.Pi * math.Asin(math.Sin(phase))
}
value := int16(wave * 7000)
source[index] = float32(value) / 32768
}
frames := (count*sampleRate + sourceRate - 1) / sourceRate
samples := make([]float32, frames*2)
for frame := range frames {
position := float64(frame) * sourceRate / sampleRate
left := min(int(position), count-1)
right := min(left+1, count-1)
value := source[left] + (source[right]-source[left])*float32(position-math.Floor(position))
samples[frame*2], samples[frame*2+1] = value, value
}
return &clip{samples: samples}
}
// Play_progress coalesces progress separately from ordinary cues. Progress
// never replaces startup/review sounds and never cuts a progress tone short.
func (s *Service) Play_progress(percentage float64) bool {
if s == nil || s.closed.Load() || s.finishing.Load() || math.IsNaN(percentage) || math.IsInf(percentage, 0) || percentage < 0 || percentage > 100 {
return false
}
item := s.tones[int(math.Round(percentage))]
if item == nil {
return false
}
s.progress.Store(item)
return true
}
// Discard_progress drops waiting tones after passive redraws or prompts while
// letting the short tone already playing finish.
func (s *Service) Discard_progress() {
if s != nil {
s.progress.Store(nil)
}
}
// Clear_progress drops pending/playing tones at input, menu capture, and
// session handoff without touching any ordinary sound cue.
func (s *Service) Clear_progress() {
if s == nil {
return
}
s.progress.Store(nil)
s.cancelProgress.Store(true)
}