1
+ // NOTE: Test framework and library:
2
+ // - Preferred: GoogleTest (gtest). This repository's detected setup should be used.
3
+ // - If the project uses Catch2/doctest instead, replace the gtest includes/assertions accordingly while keeping the same test logic.
4
+ //
5
+ // These tests focus on behaviors exposed/altered in the PR diff for IMidiChannelVoice:
6
+ // - getChannelNum()
7
+ // - setVolumes() which updates m_volume and recomputes real volume via calcVolume_().
8
+ // The internal calcVolume_() is validated indirectly by observing effective volume changes via public accessors.
9
+ // If a direct getter for "real volume" does not exist, we provide a minimal test double deriving from IMidiChannelVoice
10
+ // with accessors solely for test visibility, without altering production code paths.
11
+
12
+ #include < cstdint>
13
+ #include < limits>
14
+ #include < type_traits>
15
+
16
+ // Try to include GoogleTest; adjust if your project uses a different framework.
17
+ #include < gtest/gtest.h>
18
+
19
+ #include < HyperSonicDrivers/drivers/midi/IMidiChannelVoice.hpp>
20
+ #include < HyperSonicDrivers/drivers/midi/IMidiChannel.hpp>
21
+
22
+ using HyperSonicDrivers::drivers::midi::IMidiChannelVoice;
23
+ using HyperSonicDrivers::drivers::midi::IMidiChannel;
24
+
25
+ namespace {
26
+
27
+ // Minimal stub for IMidiChannel if it's an interface; if IMidiChannel is a struct with fields `channel` and `volume`,
28
+ // include and use the real one. Where necessary, we define only what tests need and rely on compilation to signal mismatches.
29
+ struct TestMidiChannel : public IMidiChannel {
30
+ // We assume IMidiChannel has public members `uint8_t channel; uint8_t volume;`
31
+ // If they are protected/private, adjust to use provided public setters in IMidiChannel.
32
+ // Initialize with defaults for safety.
33
+ TestMidiChannel (uint8_t ch, uint8_t vol) {
34
+ this ->channel = ch;
35
+ this ->volume = vol;
36
+ }
37
+ };
38
+
39
+ // A thin concrete test double for IMidiChannelVoice to expose volume state for assertions.
40
+ // If IMidiChannelVoice is abstract with other pure virtuals, implement them as no-ops for test purposes.
41
+ class TestMidiChannelVoice : public IMidiChannelVoice {
42
+ public:
43
+ explicit TestMidiChannelVoice (IMidiChannel* ch) {
44
+ m_channel = ch; // accessing protected member if allowed; otherwise use constructor of real class if available.
45
+ m_volume = 127 ;
46
+ m_real_volume = calcVolume_ ();
47
+ }
48
+
49
+ // Expose state for tests
50
+ uint8_t exposedVolume () const { return m_volume; }
51
+ uint8_t exposedRealVolume () const { return m_real_volume; }
52
+
53
+ // IMidiChannelVoice may declare additional pure virtuals; provide trivial implementations as needed.
54
+ protected:
55
+ // If IMidiChannelVoice has protected ctor/dtor semantics, ensure this class matches requirements.
56
+ // Any audio rendering or hardware interactions are intentionally omitted in tests.
57
+ using IMidiChannelVoice::calcVolume_; // grant access within this test double
58
+ };
59
+
60
+ } // namespace
61
+
62
+ // ------------------------------ Tests ------------------------------
63
+
64
+ TEST (IMidiChannelVoiceBehavior, GetChannelNumReflectsUnderlyingChannel) {
65
+ // Arrange
66
+ TestMidiChannel channel (/* ch*/ 5 , /* vol*/ 100 );
67
+ TestMidiChannelVoice voice (&channel);
68
+
69
+ // Act
70
+ auto num = voice.getChannelNum ();
71
+
72
+ // Assert
73
+ EXPECT_EQ (num, 5u ) << " getChannelNum should return IMidiChannel::channel" ;
74
+ }
75
+
76
+ TEST (IMidiChannelVoiceBehavior, SetVolumesUpdatesStoredVolumeAndRecalculatesRealVolumeWithChannelVolumeScaling) {
77
+ // Arrange
78
+ TestMidiChannel channel (/* ch*/ 2 , /* vol*/ 64 ); // 50% channel volume (approx, 64/127)
79
+ TestMidiChannelVoice voice (&channel);
80
+
81
+ // Precondition: max volume => real volume equals channel scaling
82
+ // With initial m_volume=127, expected real = min(127 * 64 / 127, 127) = 64
83
+ EXPECT_EQ (voice.exposedRealVolume (), static_cast <uint8_t >(64 ));
84
+
85
+ // Act
86
+ // Set voice volume to 100, expected real = min(100 * 64 / 127, 127)
87
+ voice.setVolumes (100 );
88
+ const uint8_t expected = static_cast <uint8_t >(std::min<int >(100 * 64 / 127 , 127 ));
89
+
90
+ // Assert
91
+ EXPECT_EQ (voice.exposedVolume (), static_cast <uint8_t >(100 ));
92
+ EXPECT_EQ (voice.exposedRealVolume (), expected);
93
+ }
94
+
95
+ TEST (IMidiChannelVoiceBehavior, RealVolumeIsClampedTo127OnOverflow) {
96
+ // Arrange: Set channel volume to maximum; set voice volume to maximum to test clamp.
97
+ TestMidiChannel channel (/* ch*/ 3 , /* vol*/ 127 );
98
+ TestMidiChannelVoice voice (&channel);
99
+
100
+ // Act
101
+ voice.setVolumes (200 ); // Although API takes uint8_t, values beyond 127 will wrap; ensure test uses capped within 0..255
102
+ // After implicit cast to uint8_t, 200 -> 200-256=-56 -> 200 mod 256 = 200; semantics depend on implementation.
103
+ // For robustness, test with 255 as extreme inside uint8_t range to verify clamp behavior.
104
+ voice.setVolumes (255 );
105
+
106
+ // Expected: min(255 * 127 / 127, 127) = min(255, 127) = 127
107
+ EXPECT_EQ (voice.exposedRealVolume (), static_cast <uint8_t >(127 ));
108
+ }
109
+
110
+ TEST (IMidiChannelVoiceBehavior, ZeroChannelVolumeForcesRealVolumeZeroRegardlessOfVoiceVolume) {
111
+ // Arrange
112
+ TestMidiChannel channel (/* ch*/ 9 , /* vol*/ 0 );
113
+ TestMidiChannelVoice voice (&channel);
114
+
115
+ // Act
116
+ voice.setVolumes (0 );
117
+ EXPECT_EQ (voice.exposedRealVolume (), static_cast <uint8_t >(0 ));
118
+
119
+ voice.setVolumes (127 );
120
+ EXPECT_EQ (voice.exposedRealVolume (), static_cast <uint8_t >(0 ));
121
+
122
+ voice.setVolumes (64 );
123
+ EXPECT_EQ (voice.exposedRealVolume (), static_cast <uint8_t >(0 ));
124
+ }
125
+
126
+ TEST (IMidiChannelVoiceBehavior, ZeroVoiceVolumeProducesZeroRealVolumeForAnyChannelVolume) {
127
+ // Arrange
128
+ TestMidiChannel channel (/* ch*/ 10 , /* vol*/ 127 );
129
+ TestMidiChannelVoice voice (&channel);
130
+
131
+ // Act
132
+ voice.setVolumes (0 );
133
+
134
+ // Assert
135
+ EXPECT_EQ (voice.exposedVolume (), static_cast <uint8_t >(0 ));
136
+ EXPECT_EQ (voice.exposedRealVolume (), static_cast <uint8_t >(0 ));
137
+ }
138
+
139
+ TEST (IMidiChannelVoiceBehavior, MidpointScalingMatchesIntegerDivisionRoundingDown) {
140
+ // Arrange: Channel volume 127 (max), voice 63 should yield 63.
141
+ TestMidiChannel channel (/* ch*/ 4 , /* vol*/ 127 );
142
+ TestMidiChannelVoice voice (&channel);
143
+
144
+ // Act
145
+ voice.setVolumes (63 );
146
+
147
+ // Assert
148
+ EXPECT_EQ (voice.exposedRealVolume (), static_cast <uint8_t >(63 ));
149
+
150
+ // Another case: channel=64 (approx 50%), voice=63 -> floor(63*64/127)=31
151
+ TestMidiChannel channel2 (/* ch*/ 4 , /* vol*/ 64 );
152
+ TestMidiChannelVoice voice2 (&channel2);
153
+ voice2.setVolumes (63 );
154
+ EXPECT_EQ (voice2.exposedRealVolume (), static_cast <uint8_t >( (63 * 64 ) / 127 ));
155
+ }
0 commit comments