You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
numberstation/numberstation.go

144 lines
3.3 KiB

package main
import (
"bytes"
"embed"
"fmt"
"strings"
"time"
"flag"
"github.com/chzyer/readline"
"github.com/gopxl/beep"
"github.com/gopxl/beep/speaker"
"github.com/gopxl/beep/wav"
)
//go:embed sound/*
var soundFS embed.FS
const (
pauseBetweenCharacters = 500 * time.Millisecond
longPause = 1600 * time.Millisecond
)
func main() {
// Define and parse the -r flag
repeat := flag.Bool("r", false, "Repeat each section separated by space, '-', or '_'")
flag.Parse()
// Create a map to store sound data for each character
soundMap := make(map[rune][]byte)
// Load all sound files into the map
for _, c := range "abcdefghijklmnopqrstuvwxyz0123456789" {
filename := fmt.Sprintf("sound/sam%c.wav", c)
data, err := soundFS.ReadFile(filename)
if err == nil {
soundMap[c] = data
}
}
// Configure readline settings
rl, err := readline.NewEx(&readline.Config{
Prompt: "> ", // Use a simple prompt for each line input
InterruptPrompt: "^C",
EOFPrompt: "exit",
FuncFilterInputRune: uppercaseFilter, // Filter to show uppercase
})
if err != nil {
fmt.Printf("Error initializing readline: %s\n", err)
return
}
defer rl.Close()
// Inform the user about how to enter input
fmt.Println("Enter text to be read (A-Z and 0-9 only). Press Enter on a blank line to process:")
var inputBuilder strings.Builder
for {
line, err := rl.Readline()
if err != nil {
if err.Error() == "EOF" {
fmt.Println("Exiting.")
return
}
fmt.Printf("Error reading input: %s\n", err)
return
}
// Check if the user entered a blank line to finish input
if strings.TrimSpace(line) == "" {
break
}
// Append the line to the input builder
inputBuilder.WriteString(line + "\n")
}
// Get the complete input as a string
input := inputBuilder.String()
input = strings.ToLower(strings.TrimSpace(input))
// Split the input into sections and process each section
sections := strings.FieldsFunc(input, func(r rune) bool {
return r == ' ' || r == '-' || r == '_'
})
fmt.Println("Playback starting...")
for _, section := range sections {
// Determine the number of times to read each section
numRepeats := 1
if *repeat {
numRepeats = 2
}
// Read and optionally repeat each section
for i := 0; i < numRepeats; i++ {
for _, c := range section {
if soundData, ok := soundMap[c]; ok {
playSound(soundData)
} else {
fmt.Printf("Invalid character (skipping): %c\n", c)
}
time.Sleep(pauseBetweenCharacters)
}
// Pause between repeats if repeating
if *repeat && i == 0 {
time.Sleep(longPause)
}
}
// Long pause between sections
time.Sleep(longPause)
}
fmt.Println("Playback finished. Exiting.")
}
// Function to play sound
func playSound(soundData []byte) {
streamer, format, err := wav.Decode(bytes.NewReader(soundData))
if err != nil {
fmt.Printf("Could not decode sound data: %s\n", err)
return
}
defer streamer.Close()
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
done := make(chan bool)
speaker.Play(beep.Seq(streamer, beep.Callback(func() {
done <- true
})))
<-done
}
// Filter function to convert all typed input to uppercase for display
func uppercaseFilter(r rune) (rune, bool) {
if r >= 'a' && r <= 'z' {
return r - ('a' - 'A'), true // Convert to uppercase
}
return r, true // Keep other characters unchanged
}