Skip to content

Commit

Permalink
Add support for playing SFX on any channel
Browse files Browse the repository at this point in the history
When Sfx() is run with channel = -1 then Pi picks any available channel.
  • Loading branch information
elgopher committed Sep 3, 2023
1 parent 93ae2df commit ecb20cb
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 4 deletions.
40 changes: 39 additions & 1 deletion audio/synth.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ func (c *channel) moveToNextNote(sfx SoundEffect) {
func (s *Synthesizer) Sfx(sfxNo int, ch Channel, offset, length int) {
fmt.Println("Sfx is not implemented yet. Sorry...")

s.stopSfx(sfxNo)

if ch == ChannelAny {
ch = s.findAvailableChannel()
}

if ch < 0 || ch > Channel3 {
return
}
Expand All @@ -111,6 +117,7 @@ func (s *Synthesizer) Sfx(sfxNo int, ch Channel, offset, length int) {

sfx := s.GetSfx(sfxNo)

s.channels[ch].sfxNo = sfxNo
s.channels[ch].frame = 0
s.channels[ch].noteNo = offset
s.channels[ch].notesToGo = length
Expand All @@ -122,13 +129,44 @@ func (s *Synthesizer) Sfx(sfxNo int, ch Channel, offset, length int) {
s.channels[ch].oscillator.FreqHz = pitchToFreq(note0.Pitch)
}

func (s *Synthesizer) stopSfx(no int) {
for i, c := range s.channels {
if c.playing && c.sfxNo == no {
c.playing = false
s.channels[i] = c
return
}
}
}

func (s *Synthesizer) findAvailableChannel() Channel {
for i, c := range s.channels {
if !c.playing {
return Channel(i)
}
}

return Channel3
}

func (s *Synthesizer) Music(patterNo int, fadeMs int, channelMask byte) {
fmt.Println("Music is not implemented yet. Sorry...")
}

func (s *Synthesizer) Stat() Stat {
fmt.Println("Stat is not implemented yet. Sorry...")
return Stat{}

stat := Stat{}
for i, c := range s.channels {
if c.playing {
stat.Sfx[i] = c.sfxNo
stat.Note[i] = c.noteNo
} else {
stat.Sfx[i] = -1
stat.Note[i] = -1
}
}
return stat
}

const maxSfxNo = 63
Expand Down
67 changes: 64 additions & 3 deletions audio/synth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,12 @@ func TestSynthesizer_Sfx(t *testing.T) {
singleChannelBuffer := generateSamples(validEffect, 1)

s := audio.Synthesizer{}
s.SetSfx(0, validEffect)
for ch := 0; ch < maxChannels; ch++ {
s.SetSfx(ch, validEffect)
}
// when
for ch := 0; ch < maxChannels; ch++ {
s.Sfx(0, audio.Channel(ch), 0, 1)
s.Sfx(ch, audio.Channel(ch), 0, 1)
}
// then
allChannelBuffer := make([]float64, 1)
Expand All @@ -331,7 +333,7 @@ func TestSynthesizer_Sfx(t *testing.T) {
assertAllValuesBetween(t, -4.0, 4.0, allChannelBuffer)
})

t.Run("should stop playing on a given channel", func(t *testing.T) {
t.Run("should stop playing on a given channel when sfx is -1", func(t *testing.T) {
for channelNo := audio.Channel(0); channelNo < maxChannels; channelNo++ {
testName := fmt.Sprintf("channel %d", channelNo)

Expand Down Expand Up @@ -464,6 +466,65 @@ func TestSynthesizer_Sfx(t *testing.T) {
assertDifferentShape(t, buffer1, buffer2)
})

t.Run("should play on any available channel", func(t *testing.T) {
synth := &audio.Synthesizer{}
var e audio.SoundEffect
e.Speed = 1
e.Notes[0].Volume = audio.VolumeLoudest
const sfxNo = 3
synth.SetSfx(sfxNo, e)

synth.Sfx(0, audio.Channel0, 0, 1)
synth.Sfx(1, audio.Channel1, 0, 1)
synth.Sfx(2, audio.Channel2, 0, 1)
// when
synth.Sfx(sfxNo, audio.ChannelAny, 0, 1)
// then
stat := synth.Stat()
assert.Equal(t, sfxNo, stat.Sfx[3])
assertNotSilence(t, readSamples(synth, durationOfNoteWhenSpeedIsOne))
})

t.Run("when no channels are available play on channel 3", func(t *testing.T) {
synth := &audio.Synthesizer{}
var e audio.SoundEffect
e.Speed = 1
e.Notes[0].Volume = audio.VolumeLoudest
const sfxNo = 4
synth.SetSfx(sfxNo, e)

synth.Sfx(0, audio.Channel0, 0, 1)
synth.Sfx(1, audio.Channel1, 0, 1)
synth.Sfx(2, audio.Channel2, 0, 1)
synth.Sfx(3, audio.Channel3, 0, 1)
// when
synth.Sfx(sfxNo, audio.ChannelAny, 0, 1)
// then
stat := synth.Stat()
assert.Equal(t, sfxNo, stat.Sfx[3])
assertNotSilence(t, readSamples(synth, durationOfNoteWhenSpeedIsOne))
})

t.Run("should stop playing sfx on a different channel", func(t *testing.T) {
synth := &audio.Synthesizer{}
var e audio.SoundEffect
e.Speed = 1
e.Notes[0].Volume = audio.VolumeLoudest
synth.SetSfx(0, e)

synth.Sfx(0, audio.Channel0, 1, 1)
// when
synth.Sfx(0, audio.Channel1, 0, 1)
// then
stat := synth.Stat()
assert.Equal(t, -1, stat.Sfx[0])
assert.Equal(t, 0, stat.Sfx[1])
// and
signal := readSamples(synth, durationOfNoteWhenSpeedIsOne)
expectedSignal := generateSamples(e, durationOfNoteWhenSpeedIsOne)
assert.Equal(t, expectedSignal, signal)
})

sfxOffsetLengthTest(t)
sfxLoopTest(t)
sfxLengthTest(t)
Expand Down

0 comments on commit ecb20cb

Please sign in to comment.