From c840e140f77df03cd7c236b103497a128a50b883 Mon Sep 17 00:00:00 2001 From: Ziemas Date: Fri, 31 Jul 2020 03:50:27 +0200 Subject: [PATCH] Stereo panning for sound effects (#654) Randomly pan event sounds Adapted from ebiten example --- d2common/d2interface/sound_effect.go | 1 + d2core/d2audio/ebiten/ebiten_sound_effect.go | 46 +++++++++++++++++++- d2core/d2audio/sound_engine.go | 5 +++ d2core/d2audio/sound_environment.go | 9 +++- 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/d2common/d2interface/sound_effect.go b/d2common/d2interface/sound_effect.go index 945b0765..176884ea 100644 --- a/d2common/d2interface/sound_effect.go +++ b/d2common/d2interface/sound_effect.go @@ -4,6 +4,7 @@ package d2interface type SoundEffect interface { Play() Stop() + SetPan(pan float64) IsPlaying() bool SetVolume(volume float64) } diff --git a/d2core/d2audio/ebiten/ebiten_sound_effect.go b/d2core/d2audio/ebiten/ebiten_sound_effect.go index 057e23c5..c40d8085 100644 --- a/d2core/d2audio/ebiten/ebiten_sound_effect.go +++ b/d2core/d2audio/ebiten/ebiten_sound_effect.go @@ -2,6 +2,7 @@ package ebiten import ( "log" + "math" "github.com/hajimehoshi/ebiten/audio" "github.com/hajimehoshi/ebiten/audio/wav" @@ -10,10 +11,45 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" ) +type panStream struct { + audio.ReadSeekCloser + pan float64 // -1: left; 0: center; 1: right +} + +func NewPanStreamFromReader(src audio.ReadSeekCloser) *panStream { + return &panStream{ + ReadSeekCloser: src, + pan: 0, + } +} + +func (s *panStream) Read(p []byte) (n int, err error) { + n, err = s.ReadSeekCloser.Read(p) + if err != nil { + return + } + + ls := math.Min(s.pan*-1+1, 1) + rs := math.Min(s.pan+1, 1) + + for i := 0; i < len(p); i += 4 { + lc := int16(float64(int16(p[i])|int16(p[i+1])<<8) * ls) + rc := int16(float64(int16(p[i+2])|int16(p[i+3])<<8) * rs) + + p[i] = byte(lc) + p[i+1] = byte(lc >> 8) + p[i+2] = byte(rc) + p[i+3] = byte(rc >> 8) + } + + return +} + // SoundEffect represents an ebiten implementation of a sound effect type SoundEffect struct { player *audio.Player volumeScale float64 + panStream *panStream } // CreateSoundEffect creates a new instance of ebiten's sound effect implementation. @@ -49,9 +85,11 @@ func CreateSoundEffect(sfx string, context *audio.Context, volume float64, loop if loop { s := audio.NewInfiniteLoop(d, d.Length()) - player, err = audio.NewPlayer(context, s) + result.panStream = NewPanStreamFromReader(s) + player, err = audio.NewPlayer(context, result.panStream) } else { - player, err = audio.NewPlayer(context, d) + result.panStream = NewPanStreamFromReader(d) + player, err = audio.NewPlayer(context, result.panStream) } if err != nil { @@ -66,6 +104,10 @@ func CreateSoundEffect(sfx string, context *audio.Context, volume float64, loop return result } +func (v *SoundEffect) SetPan(pan float64) { + v.panStream.pan = pan +} + func (v *SoundEffect) SetVolume(volume float64) { v.player.SetVolume(volume * v.volumeScale) } diff --git a/d2core/d2audio/sound_engine.go b/d2core/d2audio/sound_engine.go index 35cdb579..c6d52931 100644 --- a/d2core/d2audio/sound_engine.go +++ b/d2core/d2audio/sound_engine.go @@ -56,6 +56,11 @@ func (s *Sound) update(elapsed float64) { } } +// SetPan sets the stereo pan, range -1 to 1 +func (s *Sound) SetPan(pan float64) { + s.effect.SetPan(pan) +} + // Play the sound func (s *Sound) Play() { log.Println("starting sound", s.entry.Handle) diff --git a/d2core/d2audio/sound_environment.go b/d2core/d2audio/sound_environment.go index 85ee2235..dd6b9be8 100644 --- a/d2core/d2audio/sound_environment.go +++ b/d2core/d2audio/sound_environment.go @@ -1,6 +1,8 @@ package d2audio import ( + "math/rand" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" ) @@ -50,6 +52,11 @@ func (s *SoundEnvironment) Advance(elapsed float64) { s.eventTimer -= elapsed if s.eventTimer < 0 { s.eventTimer = float64(s.environment.EventDelay) / 25 - s.engine.PlaySoundID(s.environment.DayEvent) + + snd := s.engine.PlaySoundID(s.environment.DayEvent) + if snd != nil { + pan := (rand.Float64() * 2) - 1 + snd.SetPan(pan) + } } }