Skip to content

Instantly share code, notes, and snippets.

@zhaostu
Last active August 2, 2018 06:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save zhaostu/fb197be2a465efc35b8e to your computer and use it in GitHub Desktop.
Save zhaostu/fb197be2a465efc35b8e to your computer and use it in GitHub Desktop.
PortAudio example
package main
import (
"math"
"math/cmplx"
)
func isPowerOfTwo(n int) bool {
for n&1 == 0 && n > 1 {
n >>= 1
}
return (n == 1)
}
func NaiveDFT(input []complex128) []complex128 {
n := len(input)
result := make([]complex128, n, n)
for k := 0; k < n; k++ {
wk := cmplx.Rect(1, 2*math.Pi*float64(k)/float64(n))
w := complex(1, 0)
for i := 0; i < n; i++ {
result[k] += input[i] * w
w *= wk
}
}
return result
}
func RecursiveFFT(input []complex128) []complex128 {
n := len(input)
if !isPowerOfTwo(n) {
panic("input must be padded already.")
}
if n == 1 {
return input
}
halfLen := n / 2
wn := cmplx.Rect(1, 2*math.Pi/float64(n))
w := complex(1, 0)
a0 := make([]complex128, halfLen, halfLen)
a1 := make([]complex128, halfLen, halfLen)
for i := 0; i < halfLen; i++ {
a0[i] = input[i*2]
a1[i] = input[i*2+1]
}
y0 := RecursiveFFT(a0)
y1 := RecursiveFFT(a1)
result := make([]complex128, n, n)
for i := 0; i < halfLen; i++ {
v := w * y1[i]
result[i] = y0[i] + v
result[i+halfLen] = y0[i] - v
w *= wn
}
return result
}
func bitReverse(n, bits int) int {
m := 0
for i := 0; i < bits; i++ {
m = (m << 1) | (n & 1)
n >>= 1
}
return m
}
func IterativeFFT(input []complex128) []complex128 {
n := len(input)
if !isPowerOfTwo(n) {
panic("input must be padded already.")
}
// Calculate number of bits.
bits := 0
for i := n; i > 1; i >>= 1 {
bits += 1
}
// Bit reverse copy
a := make([]complex128, n, n)
for i := range input {
a[bitReverse(i, bits)] = input[i]
}
// log2(m) iterations
for m := 2; m <= n; m <<= 1 {
wm := cmplx.Rect(1, 2*math.Pi/float64(m))
w := complex(1, 0)
for i := 0; i < m/2; i++ {
for g := 0; g < n; g += m {
u := a[g+i]
v := w * a[g+i+m/2]
a[g+i] = u + v
a[g+i+m/2] = u - v
}
w *= wm
}
}
return a
}
package main
import (
"fmt"
"math"
"math/cmplx"
"github.com/gonum/plot"
"github.com/gonum/plot/plotter"
"github.com/gonum/plot/plotutil"
)
func main() {
length := 128
waveLength := 16
x := make([]complex128, length, length)
for i := 0; i < length; i++ {
x[i] = complex(math.Sin(2*math.Pi*float64(i)/float64(waveLength))+
math.Sin(2*math.Pi*float64(i)/float64(waveLength*2)+math.Pi/4), 0)
}
y := RecursiveFFT(x)
// Make a plot using plotinum.
p, err := plot.New()
if err != nil {
panic(err)
}
p.Title.Text = "Fast Fourier Transform"
p.X.Label.Text = "X"
p.Y.Label.Text = "Y"
raw := make(plotter.XYs, length)
amp := make(plotter.XYs, length)
phase := make(plotter.XYs, length)
for i := 0; i < length; i++ {
raw[i].X = float64(i)
raw[i].Y = real(x[i])
amp[i].X = float64(i)
amp[i].Y = cmplx.Abs(y[i]) / float64(length)
phase[i].X = float64(i)
if amp[i].Y > 0.00001 {
phase[i].Y = cmplx.Phase(y[i])
fmt.Println(i, amp[i].Y, phase[i].Y)
}
}
err = plotutil.AddLines(p,
"Raw", raw,
"Amp", amp,
"Phase", phase)
if err != nil {
panic(err)
}
// Save the plot to a PNG file.
if err := p.Save(800, 600, "fft.png"); err != nil {
panic(err)
}
}
package main
import (
"fmt"
"os"
"time"
"github.com/gordonklaus/portaudio"
)
func main() {
if err := portaudio.Initialize(); err != nil {
fmt.Printf("Error initializing PortAudio: %v", err)
os.Exit(1)
}
defer portaudio.Terminate()
version := portaudio.Version()
versionText := portaudio.VersionText()
fmt.Printf("PortAudio version '%s' (%d)\n", versionText, version)
hostApis, err := portaudio.HostApis()
if err != nil {
fmt.Printf("Error finding host APIs: %v\n", err)
os.Exit(1)
}
fmt.Printf("Available host APIs are:\n")
for _, apiInfo := range hostApis {
fmt.Printf("\t%d\t%v\t%v\n", apiInfo.Type, apiInfo.Name, len(apiInfo.Devices))
for _, deviceInfo := range apiInfo.Devices {
fmt.Printf("\t >\t%v\t%v\t%v\t%v\n", deviceInfo.Name, deviceInfo.MaxInputChannels,
deviceInfo.MaxOutputChannels, deviceInfo.DefaultSampleRate)
}
if apiInfo.DefaultInputDevice != nil {
fmt.Printf("\t\tDefault input device is: %v\n", apiInfo.DefaultInputDevice.Name)
}
if apiInfo.DefaultOutputDevice != nil {
fmt.Printf("\t\tDefault output device is: %v\n", apiInfo.DefaultOutputDevice.Name)
}
}
defaultHostApi, err := portaudio.DefaultHostApi()
if err != nil {
fmt.Printf("Error finding default host API: %v\n", err)
os.Exit(1)
}
if defaultHostApi != nil {
fmt.Printf("\tDefault host API is: %v\n", defaultHostApi.Name)
}
streamParams := portaudio.HighLatencyParameters(defaultHostApi.DefaultInputDevice, nil)
file, err := os.Create("recording.dat")
if err != nil {
fmt.Printf("Error opening file for writing: %v.\n", err)
}
stream, err := portaudio.OpenStream(streamParams, func(in [][]int32, timeinfo portaudio.StreamCallbackTimeInfo) {
for i := range in[0] {
fmt.Fprintln(file, in[0][i])
}
})
stream.Start()
time.Sleep(time.Second * 10)
stream.Stop()
file.Close()
}
package main
import (
"fmt"
"math"
"os"
"time"
"github.com/gordonklaus/portaudio"
)
func main() {
if err := portaudio.Initialize(); err != nil {
fmt.Printf("Error initializing PortAudio: %v", err)
os.Exit(1)
}
defer portaudio.Terminate()
version := portaudio.Version()
versionText := portaudio.VersionText()
fmt.Printf("PortAudio version '%s' (%d)\n", versionText, version)
hostApis, err := portaudio.HostApis()
if err != nil {
fmt.Printf("Error finding host APIs: %v\n", err)
os.Exit(1)
}
fmt.Printf("Available host APIs are:\n")
for _, apiInfo := range hostApis {
fmt.Printf("\t%d\t%v\t%v\n", apiInfo.Type, apiInfo.Name, len(apiInfo.Devices))
for _, deviceInfo := range apiInfo.Devices {
fmt.Printf("\t >\t%v\t%v\t%v\t%v\n", deviceInfo.Name, deviceInfo.MaxInputChannels,
deviceInfo.MaxOutputChannels, deviceInfo.DefaultSampleRate)
}
if apiInfo.DefaultInputDevice != nil {
fmt.Printf("\t\tDefault input device is: %v\n", apiInfo.DefaultInputDevice.Name)
}
if apiInfo.DefaultOutputDevice != nil {
fmt.Printf("\t\tDefault output device is: %v\n", apiInfo.DefaultOutputDevice.Name)
}
}
defaultHostApi, err := portaudio.DefaultHostApi()
if err != nil {
fmt.Printf("Error finding default host API: %v\n", err)
os.Exit(1)
}
if defaultHostApi != nil {
fmt.Printf("\tDefault host API is: %v\n", defaultHostApi.Name)
}
streamParams := portaudio.HighLatencyParameters(nil, defaultHostApi.DefaultOutputDevice)
var t float64 = 0
var l int32
recording := make([]int32, 0, 44100)
sample := 0
file, err := os.Open("recording.dat")
for {
n, _ := fmt.Fscanf(file, "%d", &l)
if n == 0 {
break
}
recording = append(recording, l)
}
if err != nil {
fmt.Printf("Cannot open file for playing, using sine wave: %v\n", err)
}
stream, err := portaudio.OpenStream(streamParams, func(out [][]int32,
timeinfo portaudio.StreamCallbackTimeInfo) {
if file != nil {
for i := range out[0] {
out[0][i] = recording[sample]
out[1][i] = recording[sample]
sample = (sample + 1) % len(recording)
}
} else {
for i := range out[0] {
out[0][i] = int32(math.Sin(t*2*math.Pi*440) * math.MaxInt32)
t += 1.0 / 44100.0
out[1][i] = int32(math.Sin(t*2*math.Pi*659) * math.MaxInt32)
t += 1.0 / 44100.0
}
}
})
stream.Start()
time.Sleep(time.Second * 10)
stream.Stop()
if file != nil {
file.Close()
}
}
package main
import (
"github.com/gordonklaus/portaudio"
"flag"
"fmt"
"github.com/mkb218/gosndfile/sndfile"
"os"
"time"
)
const BUFFER_FRAMES = 8192
func progressBar(current, total time.Duration) {
currentStr := fmt.Sprintf("%02d:%02d", int(current.Minutes()), int(current.Seconds())%60)
totalStr := fmt.Sprintf("%02d:%02d", int(total.Minutes()), int(total.Seconds())%60)
fmt.Printf("\r%s / %s", currentStr, totalStr)
}
func main() {
flag.Parse()
if len(flag.Args()) == 0 {
flag.Usage()
os.Exit(1)
}
if err := portaudio.Initialize(); err != nil {
fmt.Printf("Error initializing PortAudio: %v\n", err)
os.Exit(1)
}
defer portaudio.Terminate()
for _, fileName := range flag.Args() {
// Open the audio file.
var info sndfile.Info
audio, err := sndfile.Open(fileName, sndfile.Read, &info)
if err != nil {
fmt.Printf("Could not open file %v: %v\n", fileName, err)
os.Exit(1)
}
fmt.Printf("\nPlaying %s\n", fileName)
// Buffer
out := make([]int32, BUFFER_FRAMES*info.Channels)
// Opening stream
stream, err := portaudio.OpenDefaultStream(0, int(info.Channels), float64(info.Samplerate),
len(out), &out)
if err != nil {
fmt.Printf("Error opening output stream: %v\n", err)
os.Exit(1)
}
totalDuration := time.Duration(info.Frames/int64(info.Samplerate)) * time.Second
stream.Start()
for remaining := info.Frames; remaining > 0; remaining -= BUFFER_FRAMES {
if remaining < BUFFER_FRAMES {
out = out[:remaining*int64(info.Channels)]
}
_, err := audio.ReadFrames(out)
if err != nil {
fmt.Printf("Error reading file: %v\n", err)
os.Exit(1)
}
stream.Write()
currentDuration := time.Duration((info.Frames-remaining)/int64(info.Samplerate)) * time.Second
progressBar(currentDuration, totalDuration)
}
stream.Stop()
stream.Close()
audio.Close()
}
}
@brydavis
Copy link

brydavis commented Aug 2, 2018

Thanks for the sample code...
What's the best way to interpret the output of RecursiveFFT?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment