diff --git a/audio/synth.go b/audio/synth.go index d1ae539..5e92ded 100644 --- a/audio/synth.go +++ b/audio/synth.go @@ -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 } @@ -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 @@ -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 diff --git a/audio/synth_test.go b/audio/synth_test.go index d9f2596..fe3c850 100644 --- a/audio/synth_test.go +++ b/audio/synth_test.go @@ -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) @@ -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) @@ -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)