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 }