From 9682b8231db7435dc419bf9b93ddf22f52d08601 Mon Sep 17 00:00:00 2001 From: faiface Date: Mon, 4 Mar 2019 13:21:56 +0100 Subject: [PATCH] examples: add Doppler Stereo Room --- examples/doppler-stereo-room/README.md | 18 ++ examples/doppler-stereo-room/main.go | 238 ++++++++++++++++++++ examples/doppler-stereo-room/screenshot.png | Bin 0 -> 20284 bytes 3 files changed, 256 insertions(+) create mode 100644 examples/doppler-stereo-room/README.md create mode 100644 examples/doppler-stereo-room/main.go create mode 100644 examples/doppler-stereo-room/screenshot.png diff --git a/examples/doppler-stereo-room/README.md b/examples/doppler-stereo-room/README.md new file mode 100644 index 0000000..49e8d7d --- /dev/null +++ b/examples/doppler-stereo-room/README.md @@ -0,0 +1,18 @@ +# Doppler Stereo Room + +**Use headphones for this one!** + +There are two speakers on the screen: green and blue. The green speaker plays the left stereo channel and the blue speaker plays the right. + +The black square is your head. + +You can move the speakers around as you like. + +The 3D effect is achieved merely by [delaying the sound](https://en.wikipedia.org/wiki/Sound_localization) in one of the ears. Volume is always the same in both ears. + +Things for you to try: + +- Move both speakers to the same side of your head. +- Move one of the speakers past your head at a fast speed. + +![Screenshot](screenshot.png) diff --git a/examples/doppler-stereo-room/main.go b/examples/doppler-stereo-room/main.go new file mode 100644 index 0000000..f093e82 --- /dev/null +++ b/examples/doppler-stereo-room/main.go @@ -0,0 +1,238 @@ +package main + +import ( + "fmt" + "math" + "os" + "time" + "unicode" + + "github.com/faiface/beep" + "github.com/faiface/beep/effects" + "github.com/faiface/beep/mp3" + "github.com/faiface/beep/speaker" + "github.com/gdamore/tcell" +) + +func multiplyChannels(left, right float64, 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] *= left + samples[i][1] *= right + } + return n, ok + }) +} + +type movingStreamer struct { + x, y float64 + velX, velY float64 + leftDoppler beep.Streamer + rightDoppler beep.Streamer +} + +func newMovingStreamer(sr beep.SampleRate, x, y float64, streamer beep.Streamer) *movingStreamer { + ms := &movingStreamer{x: x, y: y} + + const metersPerSecond = 343 + samplesPerSecond := float64(sr) + samplesPerMeter := samplesPerSecond / metersPerSecond + + leftEar, rightEar := beep.Dup(streamer) + leftEar = multiplyChannels(1, 0, leftEar) + rightEar = multiplyChannels(0, 1, rightEar) + + const earDistance = 0.16 + ms.leftDoppler = effects.Doppler(2, samplesPerMeter, leftEar, func(delta int) float64 { + dt := sr.D(delta).Seconds() + ms.x += ms.velX * dt + ms.y += ms.velY * dt + return math.Max(0.25, math.Hypot(ms.x+earDistance/2, ms.y)) + }) + ms.rightDoppler = effects.Doppler(2, samplesPerMeter, rightEar, func(delta int) float64 { + return math.Max(0.25, math.Hypot(ms.x-earDistance/2, ms.y)) + }) + + return ms +} + +func (ms *movingStreamer) play() { + speaker.Play(ms.leftDoppler, ms.rightDoppler) +} + +func drawCircle(screen tcell.Screen, x, y float64, style tcell.Style) { + width, height := screen.Size() + centerX, centerY := float64(width)/2, float64(height)/2 + + lx, ly := int(centerX+(x-0.25)*2), int(centerY+y) + screen.SetContent(lx, ly, tcell.RuneBlock, nil, style) + + rx, ry := int(centerX+(x+0.25)*2), int(centerY+y) + screen.SetContent(rx, ry, tcell.RuneBlock, nil, style) +} + +func drawTextLine(screen tcell.Screen, x, y int, s string, style tcell.Style) { + for _, r := range s { + screen.SetContent(x, y, r, nil, style) + x++ + } +} + +func drawHelp(screen tcell.Screen, style tcell.Style) { + drawTextLine(screen, 0, 0, "Welcome to the Doppler Stereo Room!", style) + drawTextLine(screen, 0, 1, "Press [ESC] to quit.", style) + + drawTextLine(screen, 0, 2, "Move the", style) + drawTextLine(screen, 9, 2, "LEFT", style.Background(tcell.ColorGreen).Foreground(tcell.ColorWhiteSmoke)) + drawTextLine(screen, 14, 2, "speaker with WASD.", style) + + drawTextLine(screen, 0, 3, "Move the", style) + drawTextLine(screen, 9, 3, "RIGHT", style.Background(tcell.ColorBlue).Foreground(tcell.ColorWhiteSmoke)) + drawTextLine(screen, 15, 3, "speaker with IJKL.", style) + + drawTextLine(screen, 0, 4, "Press to start moving, press again to stop. Use [SHIFT] to move fast.", style) +} + +var directions = map[rune]struct{ lx, ly, rx, ry float64 }{ + 'a': {-1, 0, 0, 0}, + 'd': {+1, 0, 0, 0}, + 'w': {0, -1, 0, 0}, + 's': {0, +1, 0, 0}, + 'j': {0, 0, -1, 0}, + 'l': {0, 0, +1, 0}, + 'i': {0, 0, 0, -1}, + 'k': {0, 0, 0, +1}, +} + +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: %s song.mp3\n", os.Args[0]) + os.Exit(1) + } + f, err := os.Open(os.Args[1]) + if err != nil { + report(err) + } + streamer, format, err := mp3.Decode(f) + if err != nil { + report(err) + } + defer streamer.Close() + + speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/30)) + + leftCh, rightCh := beep.Dup(streamer) + + leftCh = effects.Mono(multiplyChannels(1, 0, leftCh)) + rightCh = effects.Mono(multiplyChannels(0, 1, rightCh)) + + leftMS := newMovingStreamer(format.SampleRate, -2, 0, leftCh) + rightMS := newMovingStreamer(format.SampleRate, +2, 0, rightCh) + + leftMS.play() + rightMS.play() + + screen, err := tcell.NewScreen() + if err != nil { + report(err) + } + err = screen.Init() + if err != nil { + report(err) + } + defer screen.Fini() + + frames := time.Tick(time.Second / 30) + events := make(chan tcell.Event) + go func() { + for { + events <- screen.PollEvent() + } + }() + +loop: + for { + select { + case <-frames: + speaker.Lock() + lx, ly := leftMS.x, leftMS.y + rx, ry := rightMS.x, rightMS.y + speaker.Unlock() + + style := tcell.StyleDefault. + Background(tcell.ColorWhiteSmoke). + Foreground(tcell.ColorBlack) + + screen.Clear() + screen.Fill(' ', style) + drawHelp(screen, style) + drawCircle(screen, 0, 0, style.Foreground(tcell.ColorBlack)) + drawCircle(screen, lx*2, ly*2, style.Foreground(tcell.ColorGreen)) + drawCircle(screen, rx*2, ry*2, style.Foreground(tcell.ColorBlue)) + screen.Show() + + case event := <-events: + switch event := event.(type) { + case *tcell.EventKey: + if event.Key() == tcell.KeyESC { + break loop + } + + if event.Key() != tcell.KeyRune { + break + } + + const ( + slowSpeed = 2.0 + fastSpeed = 16.0 + ) + + speaker.Lock() + + speed := slowSpeed + if unicode.ToLower(event.Rune()) != event.Rune() { + speed = fastSpeed + } + + dir := directions[unicode.ToLower(event.Rune())] + + if dir.lx != 0 { + if leftMS.velX == dir.lx*speed { + leftMS.velX = 0 + } else { + leftMS.velX = dir.lx * speed + } + } + if dir.ly != 0 { + if leftMS.velY == dir.ly*speed { + leftMS.velY = 0 + } else { + leftMS.velY = dir.ly * speed + } + } + if dir.rx != 0 { + if rightMS.velX == dir.rx*speed { + rightMS.velX = 0 + } else { + rightMS.velX = dir.rx * speed + } + } + if dir.ry != 0 { + if rightMS.velY == dir.ry*speed { + rightMS.velY = 0 + } else { + rightMS.velY = dir.ry * speed + } + } + + speaker.Unlock() + } + } + } +} + +func report(err error) { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) +} diff --git a/examples/doppler-stereo-room/screenshot.png b/examples/doppler-stereo-room/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..c405eb59a5ec20a443954be3ffb18aa2ce820fdd GIT binary patch literal 20284 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 K%;i@7_P+qvxxgj> literal 0 HcmV?d00001