Skip to content

Instantly share code, notes, and snippets.

@faiface
Created July 31, 2017 20:11
Show Gist options
  • Save faiface/b9a2055a779582201b4302460d6bb710 to your computer and use it in GitHub Desktop.
Save faiface/b9a2055a779582201b4302460d6bb710 to your computer and use it in GitHub Desktop.
package main
import (
"fmt"
"math"
"os"
"time"
"golang.org/x/image/colornames"
"github.com/faiface/beep"
"github.com/faiface/beep/mp3"
"github.com/faiface/beep/speaker"
"github.com/faiface/pixel"
"github.com/faiface/pixel/imdraw"
"github.com/faiface/pixel/pixelgl"
)
func newPositional(sr beep.SampleRate, speedOfSound float64, s beep.Streamer, position func(dt time.Duration) (x, y float64)) beep.Streamer {
return &positional{
s: s,
position: position,
space: make([]float64, int(float64(sr)/speedOfSound*math.Hypot(position(0)))),
sr: sr,
speedOfSound: speedOfSound,
}
}
type positional struct {
s beep.Streamer
position func(dt time.Duration) (x, y float64)
space []float64
sr beep.SampleRate
speedOfSound float64
}
func (p *positional) Stream(samples [][2]float64) (n int, ok bool) {
sn, _ := p.s.Stream(samples)
prevLen := len(p.space)
currDst := math.Hypot(p.position(p.sr.D(sn)))
currLen := p.sr.N(time.Duration(currDst / p.speedOfSound * float64(time.Second)))
needAdd := currLen + sn - prevLen
var buf [512][2]float64
resampled := beep.ResampleRatio(3, float64(sn)/float64(needAdd), streamSlice(samples[:sn]))
for {
rn, rok := resampled.Stream(buf[:])
if !rok {
break
}
for i := range buf[:rn] {
val := (buf[i][0] + buf[i][1]) / 2 / currDst / currDst
p.space = append(p.space, val)
}
}
if len(p.space) == 0 {
return 0, false
}
for i := range samples {
if len(p.space) == 0 {
break
}
samples[i][0] = p.space[0]
samples[i][1] = p.space[0]
p.space = p.space[1:]
n++
}
return n, true
}
func (p *positional) Err() error {
return p.s.Err()
}
func streamSlice(p [][2]float64) beep.Streamer {
return beep.StreamerFunc(func(samples [][2]float64) (n int, ok bool) {
if len(p) == 0 {
return 0, false
}
n = copy(samples, p)
p = p[n:]
return n, true
})
}
func onlyLeft(s beep.Streamer) beep.Streamer {
return beep.StreamerFunc(func(samples [][2]float64) (n int, ok bool) {
n, ok = s.Stream(samples)
for i := range samples[:n] {
samples[i][1] = 0
}
return n, ok
})
}
func onlyRight(s beep.Streamer) beep.Streamer {
return beep.StreamerFunc(func(samples [][2]float64) (n int, ok bool) {
n, ok = s.Stream(samples)
for i := range samples[:n] {
samples[i][0] = 0
}
return n, ok
})
}
func run() {
f, _ := os.Open("04 Harder, Better, Faster, Stronger.mp3")
s, format, _ := mp3.Decode(f)
buf := beep.NewBuffer(format)
buf.Append(s)
s.Close()
fmt.Println("done appending")
sr := format.SampleRate
speaker.Init(sr, sr.N(time.Second/30))
speaker.UnderrunCallback(func() {
fmt.Println("UNDERRUN")
})
var (
pos = pixel.V(0, 1)
target = pixel.V(0, 1)
maxSpeed = 1.0
)
left := newPositional(sr, 340.29, buf.Streamer(0, buf.Len()), func(dt time.Duration) (x, y float64) {
if pos.To(target).Len() <= maxSpeed*dt.Seconds() {
pos = target
} else {
pos = pos.Add(pos.To(target).Unit().Scaled(maxSpeed * dt.Seconds()))
}
return +0.11 + pos.X, pos.Y
})
right := newPositional(sr, 340.29, buf.Streamer(0, buf.Len()), func(dt time.Duration) (x, y float64) {
return -0.11 + pos.X, pos.Y
})
speaker.Play(onlyLeft(left), onlyRight(right))
cfg := pixelgl.WindowConfig{
Title: "Positional",
Bounds: pixel.R(-400, -400, 400, 400),
VSync: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
imd := imdraw.New(nil)
for !win.Closed() {
cam := pixel.IM.Scaled(pixel.ZV, 40)
win.SetMatrix(cam)
if win.Pressed(pixelgl.MouseButtonLeft) {
speaker.Lock()
target = cam.Unproject(win.MousePosition())
speaker.Unlock()
}
if win.JustPressed(pixelgl.KeyEqual) {
maxSpeed += 1.0
}
if win.JustPressed(pixelgl.KeyMinus) {
maxSpeed -= 1.0
}
imd.Clear()
imd.Color = colornames.Orchid
speaker.Lock()
imd.Push(pos)
speaker.Unlock()
imd.Circle(1, 0)
imd.Color = colornames.Darkseagreen
imd.Push(pixel.ZV)
imd.Circle(0.5, 0)
win.SetTitle(fmt.Sprintf("%s | max speed: %v", cfg.Title, maxSpeed))
win.Clear(colornames.Floralwhite)
imd.Draw(win)
win.Update()
}
}
func main() {
pixelgl.Run(run)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment