-
-
Save faiface/b9a2055a779582201b4302460d6bb710 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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