faiface / beep

A little package that brings sound to any Go application. Suitable for playback and audio-processing.
MIT License
2.08k stars 151 forks source link

wav.Encode() throws fatal error: all goroutines are asleep - deadlock! #96

Closed StephanVerbeeck closed 4 years ago

StephanVerbeeck commented 4 years ago

I was unable to locate example code or comment line that shows/documents how to use the wav encoding functionality provided by "github.com/faiface/beep/wav".

I tried to get it going without any documentation but the code does not seem to be functional? (where is the module test?)

go version go1.14.4 windows/amd64

package main

import (
    "log"
    "math"
    "math/rand"
    "os"
    "time"

    "github.com/faiface/beep"
    "github.com/faiface/beep/mp3"
    "github.com/faiface/beep/speaker"
    "github.com/faiface/beep/wav"
)

func main() {
    generateNoise()
    playNoise()

}

// Noise sound
type Noise struct {
    s       int
    samples [44100]float64
}

// Stream white noise sound
func (n *Noise) Stream(samples [][2]float64) (bufferLen int, ok bool) {
    if n.s == 0 {
        sampleRate := 44100.0
        freq := 2000.0
        samplesPerSinus := sampleRate / freq
        volume := 0.20
        for i := range n.samples {
            angle := math.Mod(float64(i)/samplesPerSinus, 1.0)
            angle *= math.Pi * 2.0 // convert rotations to radius
            value := math.Cos(angle) * volume
            if i < len(n.samples)/2 {
                n.samples[i] = value
            } else {
                n.samples[i] = (rand.Float64()*2 - 1) * volume
            }
        }
    }

    for i := range samples {
        samples[i][0] = n.samples[n.s%len(n.samples)]
        samples[i][1] = n.samples[n.s%len(n.samples)]
        n.s++
    }
    bufferLen = len(samples)
    ok = true
    return
}

// Err function
func (n *Noise) Err() error {
    return nil
}

func playNoise() {
    f, err := os.Open(`D:\DownLoads\4kvideodownloader\audio\alert.mp3`)
    if err != nil {
        log.Fatal(err)
    }

    streamer, format, err := mp3.Decode(f)
    if err != nil {
        log.Fatal(err)
    }
    defer streamer.Close() // also closes the file 'f'

    speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))

    //speaker.Play(streamer)

    done := make(chan bool)
    //speaker.Play(beep.Seq(streamer, beep.Callback(func() {

    loop := beep.Loop(1, streamer)
    fast := beep.ResampleRatio(4, 0.66, loop)
    speaker.Play(beep.Seq(fast, beep.Callback(func() {
        done <- true
    })))

    <-done
}

func generateNoise() {
    f, err := os.Create(`output.wav`)
    if err != nil {
        log.Fatal(err)
    }

    format := beep.Format{
        SampleRate:  44100,
        NumChannels: 2,
        Precision:   2,
    }

    sr := beep.SampleRate(44100)
    done := make(chan bool)
    streamer := beep.Seq(beep.Take(sr.N(5*time.Second), &Noise{}), beep.Callback(func() {
        done <- true
    }))

    audible := false
    if audible {
        speaker.Init(sr, sr.N(time.Second/10))
        speaker.Play(streamer)
    } else {
        err = wav.Encode(f, streamer, format)
        if err != nil {
            log.Fatal(err)
        }
    }
    <-done // wait for stream to be done
}
MarkKremer commented 4 years ago

Hello 👋 ,

Encoding a wav is a synchronous operation. It will also call the streamers synchronously. So when it comes to done <- true in your callback, it waits for another goroutine to receive the data but there is none. The generateNoise is executing the wav.Encode still and it hasn't reached the <-done yet. The speaker is asynchronous so you don't get this error.

StephanVerbeeck commented 4 years ago

The speaker is asynchronous

Thanks!, I have changed my test code accordingly and now it works

I am still not sure if this is not an inconsistency (the decoding of a file for playback being asynchronous while the encoding of a file is synchronous). I can always put the encoding in a GO routine as work-around (but does need some documenting of this fact though).

func generateNoise() {
    f, err := os.Create(`output.wav`)
    if err != nil {
        log.Fatal(err)
    }

    format := beep.Format{
        SampleRate:  44100,
        NumChannels: 2,
        Precision:   2,
    }

    audible := false
    if audible {
        done := make(chan bool)
        streamer := beep.Seq(beep.Take(format.SampleRate.N(5*time.Second), &Noise{}), beep.Callback(func() {
            done <- true
        }))
        speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
        speaker.Play(streamer)
        <-done // wait for stream to be done
    } else {
        streamer := beep.Seq(beep.Take(format.SampleRate.N(5*time.Second), &Noise{}))
        err = wav.Encode(f, streamer, format)
        if err != nil {
            log.Fatal(err)
        }
    }
}