// Render starts rendering a sound wave's samples to a screen of given height. func Render(s sounds.Sound, width int, height int) { // Default to 15 samples-per-pixel, sampled 1-in-2. screen := util.NewScreen(width, height, 15) s.Start() // TODO(padster): Currently this generates and renders the samples // as quickly as possible. Better instead to render close to realtime? screen.Render(s.GetSamples(), 2) }
// WriteSoundToFlac creates a file at a path, and writes the given sound in the .flac format. func WriteSoundToFlac(sound s.Sound, path string) error { if !strings.HasSuffix(path, ".flac") { panic("Output file must be .flac") } if _, err := os.Stat(path); err == nil { panic("File already exists! Please delete first") return os.ErrExist } sampleRate := int(s.CyclesPerSecond) depth := 24 fileWriter, err := goflac.NewEncoder(path, 1, depth, sampleRate) if err != nil { fmt.Printf("Error opening file to write to! %v\n", err) panic("Can't write file") } defer fileWriter.Close() // Starts the sound, and accesses its sample stream. sound.Start() samples := sound.GetSamples() defer sound.Stop() // TODO: Make a common utility for this, it's used here and in both CQ and CQI. frameSize := 44100 buffer := make([]float64, frameSize, frameSize) at := 0 for s := range samples { if at == frameSize { writeFrame(fileWriter, buffer) at = 0 } buffer[at] = s at++ } writeFrame(fileWriter, buffer[:at]) return nil }
// WriteSoundToWav creates a file at a path, and writes the given sound in the .wav format. func WriteSoundToWav(s sounds.Sound, path string) error { // Create file first, only if it doesn't exist: if _, err := os.Stat(path); err == nil { panic("File already exists! Please delete first") return os.ErrExist } file, err := os.Create(path) if err != nil { return err } defer func() { file.Close() }() // Create a .wav writer for the file var wf = wav.File{ SampleRate: uint32(sounds.CyclesPerSecond), Channels: 1, SignificantBits: 16, } writer, err := wf.NewWriter(file) if err != nil { return err } defer writer.Close() // Starts the sound, and accesses its sample stream. s.Start() samples := s.GetSamples() defer s.Stop() // Write a single sample at a time, as per the .wav writer API. b := make([]byte, 2) for sample := range samples { toNumber := uint16(sample * normScale) // Inverse the read scaling binary.LittleEndian.PutUint16(b, uint16(toNumber)) writer.WriteSample(b) } return nil }
// playSamples handles the writing of a sound's channel of samples to a pulse stream. func playSamples(s sounds.Sound, sync_ch chan int, pa *PulseMainLoop) { defer func() { sync_ch <- 0 }() // Create a pulse audio context to play the sound. ctx := pa.NewContext("default", 0) if ctx == nil { fmt.Println("Failed to create a new context") return } defer ctx.Dispose() // Create a single-channel pulse audio stream to write the sound to. st := ctx.NewStream("default", &PulseSampleSpec{ Format: SAMPLE_FLOAT32LE, Rate: int(sounds.CyclesPerSecond), Channels: 1, }) if st == nil { fmt.Println("Failed to create a new stream") return } defer st.Dispose() // Starts the sound, and accesses its sample stream. s.Start() samples := s.GetSamples() defer s.Stop() // Continually buffers data from the stream and writes to audio. sampleCount := 0 st.ConnectToSink() for { toAdd := st.WritableSize() if toAdd == 0 { continue } // No buffer - write immediately. // TODO(padster): Play with this to see if chunked writes actually reduce delay. if toAdd > 441 { toAdd = 441 } // TODO: Reuse just one of these? buffer := make([]float32, toAdd) finishedAt := toAdd for i := range buffer { sample, stream_ok := <-samples if !stream_ok { finishedAt = i break } buffer[i] = float32(sample) } if finishedAt == 0 { st.Drain() break } sampleCount += finishedAt st.Write(buffer[0:finishedAt], SEEK_RELATIVE) } fmt.Printf("Samples written: %d\n", sampleCount) }