Skip to content

Commit fb60bf8

Browse files
authored
pcmDriver stop methods (#270)
* pcmDriver stop methods * code rev * update version * sdl2 mixer callback, small improvement * code rev * sonarcloud code rev * code rev * PCMDriverTest init | TestIMusicDriver -> TestIAudioDriver * PCMDriverTest play/stop basic scenarios * code rev * sonarcloud code rev
1 parent c6db69d commit fb60bf8

File tree

10 files changed

+208
-40
lines changed

10 files changed

+208
-40
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ if(DEFINED ENV{VCPKG_ROOT} AND NOT DEFINED CMAKE_TOOLCHAIN_FILE)
55
endif()
66

77

8-
project ("sdl2-hyper-sonic-drivers" VERSION 0.13.2 DESCRIPTION "SDL2 based Hyper-Sonic Drivers for emulating old soundcards")
8+
project ("sdl2-hyper-sonic-drivers" VERSION 0.13.3 DESCRIPTION "SDL2 based Hyper-Sonic Drivers for emulating old soundcards")
99
include (TestBigEndian)
1010
TEST_BIG_ENDIAN(IS_BIG_ENDIAN)
1111
if(IS_BIG_ENDIAN)

sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/audio/sdl2/Mixer.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -257,11 +257,9 @@ namespace HyperSonicDrivers::audio::sdl2
257257
int16_t* buf = std::bit_cast<int16_t*>(samples);
258258
// we store stereo, 16-bit samples (div 2 for stereo and 2 from 8 to 16 bits)
259259
assert(len % 4 == 0);
260-
len >>= 1;
261260
// zero the buf (size of 2ch stereo: len*2 of 16 bits)
262-
memset(buf, 0, len * sizeof(int16_t));
263-
len >>= 1; // size of the stereo 16 bits buffer.
264-
261+
memset(buf, 0, len);
262+
len >>= 2;
265263
// mix all channels
266264
size_t res = 0;
267265
for (const auto& ch : m_channels)

sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/audio/streams/PCMStream.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ namespace HyperSonicDrivers::audio::streams
3838
return m_curPos == m_sound->dataSize;
3939
}
4040

41-
std::weak_ptr<PCMSound> PCMStream::getSound() const noexcept
41+
std::shared_ptr<PCMSound> PCMStream::getSound() const noexcept
4242
{
4343
return m_sound;
4444
}

sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/audio/streams/PCMStream.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace HyperSonicDrivers::audio::streams
1818
uint32_t getRate() const override;
1919
bool endOfData() const override;
2020

21-
std::weak_ptr<PCMSound> getSound() const noexcept;
21+
std::shared_ptr<PCMSound> getSound() const noexcept;
2222
private:
2323
std::shared_ptr<PCMSound> m_sound;
2424
uint32_t m_curPos = 0;

sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/PCMDriver.cpp

Lines changed: 72 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ namespace HyperSonicDrivers::drivers
88
PCMDriver::PCMDriver(const std::shared_ptr<audio::IMixer>& mixer, const uint8_t max_channels) :
99
max_streams(std::min(mixer->max_channels, max_channels)), m_mixer(mixer)
1010
{
11-
m_PCMStreams.resize(max_streams);
1211
}
1312

1413
bool PCMDriver::isPlaying() const noexcept
1514
{
16-
for(const auto& ss: m_PCMStreams)
15+
for (const auto& [stream, _] : m_PCMStreams_channels)
1716
{
18-
if (isPCMStreamPlaying_(ss))
17+
if (isPCMStreamPlaying_(stream))
1918
return true;
2019
}
2120

@@ -24,48 +23,97 @@ namespace HyperSonicDrivers::drivers
2423

2524
bool PCMDriver::isPlaying(const std::shared_ptr<audio::PCMSound>& sound) const noexcept
2625
{
27-
// TODO:
28-
// should map channelId to check directly in the mixer?
29-
// how to find a free slot then?
30-
// does we need to really track it?
31-
// probably using a map instead of a vector is ok,
32-
// no need to define nether max-channels.
33-
// but that is because if wanting to reserve some channels for something
34-
// else that is not PCM related...
35-
// anyway... it could be achieved having the mixer a "lock or reserved channel"
36-
// feature or something that that one won't be used unless
37-
// it is for the resources that has been reserved for.....
38-
for(const auto& ss : m_PCMStreams)
26+
for (const auto& [stream, _] : m_PCMStreams_channels)
3927
{
40-
if (ss->getSound().lock() == sound)
41-
return isPCMStreamPlaying_(ss);
28+
if (stream->getSound() == sound)
29+
return isPCMStreamPlaying_(stream);
4230
}
4331

4432
return false;
4533
}
4634

4735
std::optional<uint8_t> PCMDriver::play(const std::shared_ptr<audio::PCMSound>& sound, const uint8_t volume, const int8_t pan)
4836
{
49-
// find first free slot
50-
auto it = std::ranges::find_if_not(m_PCMStreams, isPCMStreamPlaying_);
51-
if (it == m_PCMStreams.end())
37+
releaseEndedStreams_();
38+
if (m_PCMStreams_channels.size() == max_streams)
5239
return std::nullopt;
5340

54-
*it = std::make_shared<PCMStream>(sound);
41+
auto s = std::make_shared<PCMStream>(sound);
5542

5643
auto channelId = m_mixer->play(
5744
sound->group,
58-
*it,
45+
s,
5946
volume,
6047
pan
6148
);
6249

63-
if (!channelId.has_value())
64-
*it = nullptr;
50+
if (channelId.has_value())
51+
m_PCMStreams_channels[s] = channelId.value();
6552

6653
return channelId;
6754
}
6855

56+
void PCMDriver::stop(const uint8_t channel_id, const bool releaseEndedStreams) noexcept
57+
{
58+
auto it = std::ranges::find_if(
59+
m_PCMStreams_channels,
60+
[channel_id](std::pair<const std::shared_ptr<audio::streams::PCMStream>&, const int> p) {
61+
return channel_id == p.second;
62+
}
63+
);
64+
65+
if (it == m_PCMStreams_channels.end())
66+
return;
67+
68+
if (!(it->first)->isEnded())
69+
m_mixer->reset(channel_id);
70+
71+
if (releaseEndedStreams)
72+
{
73+
m_PCMStreams_channels.erase(it);
74+
releaseEndedStreams_();
75+
}
76+
}
77+
78+
void PCMDriver::stop(const std::shared_ptr<audio::PCMSound>& sound, const bool releaseEndedStreams)
79+
{
80+
auto it = std::ranges::find_if(
81+
m_PCMStreams_channels,
82+
[&sound](std::pair<const std::shared_ptr<audio::streams::PCMStream>&, const int> p) {
83+
return p.first != nullptr && sound == p.first->getSound();
84+
}
85+
);
86+
87+
if (it == m_PCMStreams_channels.end())
88+
return;
89+
90+
stop(it->second, releaseEndedStreams);
91+
}
92+
93+
void PCMDriver::stop() noexcept
94+
{
95+
for (const auto& [_, ch_id] : m_PCMStreams_channels)
96+
stop(ch_id, false);
97+
98+
releaseStreams_();
99+
}
100+
101+
void PCMDriver::releaseEndedStreams_() noexcept
102+
{
103+
for (auto it = m_PCMStreams_channels.begin(); it != m_PCMStreams_channels.end();)
104+
{
105+
if (!isPCMStreamPlaying_(it->first))
106+
it = m_PCMStreams_channels.erase(it);
107+
else
108+
++it;
109+
}
110+
}
111+
112+
void PCMDriver::releaseStreams_() noexcept
113+
{
114+
m_PCMStreams_channels.clear();
115+
}
116+
69117
inline bool PCMDriver::isPCMStreamPlaying_(const std::shared_ptr<audio::streams::PCMStream>& stream) noexcept
70118
{
71119
return stream != nullptr && !stream->isEnded();

sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/PCMDriver.hpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#pragma once
22

3-
#include <memory>
43
#include <cstdint>
5-
#include <vector>
4+
#include <memory>
5+
#include <map>
66
#include <optional>
77
#include <HyperSonicDrivers/audio/IMixer.hpp>
88
#include <HyperSonicDrivers/audio/streams/PCMStream.hpp>
@@ -30,11 +30,18 @@ namespace HyperSonicDrivers::drivers
3030
const uint8_t volume = audio::mixer::Channel_max_volume,
3131
const int8_t pan = 0
3232
);
33+
void stop(const uint8_t channel_id, const bool releaseEndedStreams = true) noexcept;
34+
void stop(const std::shared_ptr<audio::PCMSound>& sound, const bool releaseEndedStreams = true);
35+
void stop() noexcept;
36+
3337

3438
const uint8_t max_streams;
3539
private:
3640
std::shared_ptr<audio::IMixer> m_mixer;
37-
std::vector<std::shared_ptr<audio::streams::PCMStream>> m_PCMStreams;
41+
std::map<std::shared_ptr<audio::streams::PCMStream>, uint8_t> m_PCMStreams_channels;
42+
43+
void releaseEndedStreams_() noexcept;
44+
void releaseStreams_() noexcept;
3845

3946
static bool isPCMStreamPlaying_(const std::shared_ptr<audio::streams::PCMStream>& stream) noexcept;
4047
};

sdl2-hyper-sonic-drivers/test/HyperSonicDrivers/CMakeLists.txt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,15 @@ macro_test(
171171
)
172172

173173
macro_test(
174-
EXE TestIMusicDriver
175-
FILES "drivers/TestIMusicDriver.cpp"
174+
EXE TestIAudioDriver
175+
FILES "drivers/TestIAudioDriver.cpp"
176+
LINKS_PRIVATE hyper-sonic-drivers-static
177+
FIXTURES
178+
)
179+
180+
macro_test(
181+
EXE TestPCMDriver
182+
FILES "drivers/TestPCMDriver.cpp"
176183
LINKS_PRIVATE hyper-sonic-drivers-static
177184
FIXTURES
178185
)

sdl2-hyper-sonic-drivers/test/HyperSonicDrivers/audio/IMixerMock.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace HyperSonicDrivers::audio
1212
{
1313
public:
1414
int rate = 44100;
15+
uint8_t cur_ch = 255;
1516

1617
IMixerMock() : IMixer(32, 44100, 1024) {};
1718
explicit IMixerMock(const int freq) : IMixer(32, freq, 1024) {};
@@ -24,7 +25,7 @@ namespace HyperSonicDrivers::audio
2425
const uint8_t vol,
2526
const int8_t pan
2627
) override {
27-
return std::make_optional(0);
28+
return std::make_optional((++cur_ch) % max_channels);
2829
};
2930

3031
void suspend() noexcept override {};

sdl2-hyper-sonic-drivers/test/HyperSonicDrivers/drivers/TestIMusicDriver.cpp renamed to sdl2-hyper-sonic-drivers/test/HyperSonicDrivers/drivers/TestIAudioDriver.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@
44

55
namespace HyperSonicDrivers::drivers
66
{
7-
class IMusicDriverMock : public IAudioDriver
7+
class IAudioDriverMock : public IAudioDriver
88
{
99
public:
10-
IMusicDriverMock(const std::shared_ptr<devices::IDevice>& device) : IAudioDriver(device) {}
10+
IAudioDriverMock(const std::shared_ptr<devices::IDevice>& device) : IAudioDriver(device) {}
1111
void play(const uint8_t track) noexcept override {};
1212
void stop() noexcept override {};
1313
bool isPlaying() const noexcept override { return false; };
1414
};
1515

1616
TEST(IAudioDriver, cstor_nullptr)
1717
{
18-
EXPECT_THROW(IMusicDriverMock md(nullptr), std::runtime_error);
18+
EXPECT_THROW(IAudioDriverMock md(nullptr), std::runtime_error);
1919
}
2020
}
2121

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#include <gtest/gtest.h>
2+
#include <gmock/gmock.h>
3+
#include <HyperSonicDrivers/drivers/PCMDriver.hpp>
4+
#include <HyperSonicDrivers/audio/IMixerMock.hpp>
5+
#include <HyperSonicDrivers/audio/PCMSound.hpp>
6+
#include <HyperSonicDrivers/audio/mixer/ChannelGroup.hpp>
7+
#include <memory>
8+
#include <cstdint>
9+
10+
namespace HyperSonicDrivers::drivers
11+
{
12+
TEST(PCMDriver, play_stop0)
13+
{
14+
auto mixer = audio::make_mixer<audio::IMixerMock>();
15+
auto drv = PCMDriver(mixer);
16+
17+
EXPECT_EQ(drv.max_streams, mixer->max_channels);
18+
19+
auto sound_data = std::make_shared<int16_t[]>(1);
20+
auto sound = std::make_shared<audio::PCMSound>(audio::mixer::eChannelGroup::Plain, true, 44100, 1, sound_data);
21+
auto ch_id = drv.play(sound);
22+
23+
ASSERT_TRUE(drv.isPlaying(sound));
24+
ASSERT_TRUE(drv.isPlaying());
25+
ASSERT_TRUE(ch_id.has_value());
26+
EXPECT_EQ(ch_id.value(), 0);
27+
28+
drv.stop(ch_id.value());
29+
EXPECT_FALSE(drv.isPlaying(sound));
30+
EXPECT_FALSE(drv.isPlaying());
31+
}
32+
33+
TEST(PCMDriver, play_stop1)
34+
{
35+
auto mixer = audio::make_mixer<audio::IMixerMock>();
36+
auto drv = PCMDriver(mixer);
37+
38+
EXPECT_EQ(drv.max_streams, mixer->max_channels);
39+
40+
auto sound_data = std::make_shared<int16_t[]>(1);
41+
auto sound = std::make_shared<audio::PCMSound>(audio::mixer::eChannelGroup::Plain, true, 44100, 1, sound_data);
42+
auto ch_id = drv.play(sound);
43+
44+
ASSERT_TRUE(drv.isPlaying(sound));
45+
ASSERT_TRUE(drv.isPlaying());
46+
ASSERT_TRUE(ch_id.has_value());
47+
EXPECT_EQ(ch_id.value(), 0);
48+
49+
drv.stop(sound);
50+
EXPECT_FALSE(drv.isPlaying(sound));
51+
EXPECT_FALSE(drv.isPlaying());
52+
}
53+
54+
TEST(PCMDriver, play_stop2)
55+
{
56+
auto mixer = audio::make_mixer<audio::IMixerMock>();
57+
auto drv = PCMDriver(mixer);
58+
59+
EXPECT_EQ(drv.max_streams, mixer->max_channels);
60+
61+
auto sound_data = std::make_shared<int16_t[]>(1);
62+
auto sound = std::make_shared<audio::PCMSound>(audio::mixer::eChannelGroup::Plain, true, 44100, 1, sound_data);
63+
auto ch_id = drv.play(sound);
64+
65+
ASSERT_TRUE(drv.isPlaying(sound));
66+
ASSERT_TRUE(drv.isPlaying());
67+
ASSERT_TRUE(ch_id.has_value());
68+
EXPECT_EQ(ch_id.value(), 0);
69+
70+
drv.stop();
71+
EXPECT_FALSE(drv.isPlaying(sound));
72+
EXPECT_FALSE(drv.isPlaying());
73+
}
74+
75+
TEST(PCMDriver, play_stop_complex)
76+
{
77+
auto mixer = audio::make_mixer<audio::IMixerMock>();
78+
auto drv = PCMDriver(mixer);
79+
80+
EXPECT_EQ(drv.max_streams, mixer->max_channels);
81+
82+
auto sound_data = std::make_shared<int16_t[]>(1);
83+
auto sound = std::make_shared<audio::PCMSound>(audio::mixer::eChannelGroup::Plain, true, 44100, 1, sound_data);
84+
auto sound_data2 = std::make_shared<int16_t[]>(1);
85+
auto sound2 = std::make_shared<audio::PCMSound>(audio::mixer::eChannelGroup::Unknown, true, 22050, 1, sound_data2);
86+
87+
auto ch_id = drv.play(sound);
88+
EXPECT_TRUE(drv.play(sound2).has_value());
89+
EXPECT_TRUE(drv.play(sound2).has_value());
90+
EXPECT_TRUE(drv.play(sound2).has_value());
91+
92+
ASSERT_TRUE(drv.isPlaying(sound));
93+
ASSERT_TRUE(drv.isPlaying());
94+
ASSERT_TRUE(ch_id.has_value());
95+
EXPECT_EQ(ch_id.value(), 0);
96+
97+
drv.stop(ch_id.value());
98+
EXPECT_FALSE(drv.isPlaying(sound));
99+
EXPECT_TRUE(drv.isPlaying());
100+
}
101+
}
102+
103+
int main(int argc, char** argv)
104+
{
105+
::testing::InitGoogleTest(&argc, argv);
106+
return RUN_ALL_TESTS();
107+
}

0 commit comments

Comments
 (0)