Skip to content

Commit 2fc811e

Browse files
committed
feat: audio panning
Tested with opus and fluidsynth Closes #2585
1 parent 29a8515 commit 2fc811e

13 files changed

Lines changed: 166 additions & 48 deletions

src/audio.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ AudioInterface& Audio() {
3333
return default_;
3434
}
3535

36-
void EmptyAudio::BGM_Play(Filesystem_Stream::InputStream, int, int, int) {
36+
void EmptyAudio::BGM_Play(Filesystem_Stream::InputStream, int, int, int, int) {
3737
bgm_starttick = Player::GetFrames();
3838
playing = true;
3939
}

src/audio.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ struct AudioInterface {
6161
* @param volume volume.
6262
* @param pitch pitch.
6363
* @param fadein fadein.
64+
* @param balance balance (0 - 100)
6465
*/
65-
virtual void BGM_Play(Filesystem_Stream::InputStream stream, int volume, int pitch, int fadein) = 0;
66+
virtual void BGM_Play(Filesystem_Stream::InputStream stream, int volume, int pitch, int fadein, int balance) = 0;
6667

6768
/**
6869
* Stops the currently playing background music.
@@ -129,8 +130,9 @@ struct AudioInterface {
129130
* @param se se to play.
130131
* @param volume volume.
131132
* @param pitch pitch.
133+
* @param balance balance (0 - 100)
132134
*/
133-
virtual void SE_Play(std::unique_ptr<AudioSeCache> se, int volume, int pitch) = 0;
135+
virtual void SE_Play(std::unique_ptr<AudioSeCache> se, int volume, int pitch, int balance) = 0;
134136

135137
/**
136138
* Stops the currently playing sound effect.
@@ -162,7 +164,7 @@ struct AudioInterface {
162164
struct EmptyAudio : public AudioInterface {
163165
public:
164166
explicit EmptyAudio(const Game_ConfigAudio& cfg);
165-
void BGM_Play(Filesystem_Stream::InputStream, int, int, int) override;
167+
void BGM_Play(Filesystem_Stream::InputStream, int, int, int, int) override;
166168
void BGM_Pause() override {}
167169
void BGM_Resume() override {}
168170
void BGM_Stop() override;
@@ -173,7 +175,7 @@ struct EmptyAudio : public AudioInterface {
173175
void BGM_Volume(int) override {}
174176
void BGM_Pitch(int) override {};
175177
std::string BGM_GetType() const override { return {}; };
176-
void SE_Play(std::unique_ptr<AudioSeCache>, int, int) override {}
178+
void SE_Play(std::unique_ptr<AudioSeCache>, int, int, int) override {}
177179
void SE_Stop() override {}
178180
void Update() override {}
179181
void vGetConfig(Game_ConfigAudio& cfg) const override;

src/audio_decoder.cpp

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,16 +175,17 @@ void AudioDecoder::Update(std::chrono::microseconds delta) {
175175

176176
volume += static_cast<float>(std::chrono::duration_cast<std::chrono::microseconds>(delta).count()) * delta_volume_step;
177177
volume = Utils::Clamp(static_cast<float>(volume), 0.0f, 100.0f);
178-
log_volume = AdjustVolume(volume);
178+
SetLogVolume();
179179
}
180180

181-
int AudioDecoder::GetVolume() const {
182-
return static_cast<int>(log_volume);
181+
std::pair<int, int> AudioDecoder::GetVolume() const {
182+
auto& [left, right] = log_volume;
183+
return std::pair(static_cast<int>(left), static_cast<int>(right));
183184
}
184185

185186
void AudioDecoder::SetVolume(int new_volume) {
186187
volume = Utils::Clamp(static_cast<float>(new_volume), 0.0f, 100.0f);
187-
log_volume = AdjustVolume(volume);
188+
SetLogVolume();
188189
}
189190

190191
void AudioDecoder::SetFade(int end, std::chrono::milliseconds duration) {
@@ -200,6 +201,11 @@ void AudioDecoder::SetFade(int end, std::chrono::milliseconds duration) {
200201
delta_volume_step = (static_cast<float>(fade_volume_end) - volume) / fade_time.count();
201202
}
202203

204+
void AudioDecoder::SetBalance(int balance) {
205+
AudioDecoderBase::SetBalance(balance);
206+
SetLogVolume();
207+
}
208+
203209
int AudioDecoder::GetSamplesizeForFormat(AudioDecoderBase::Format format) {
204210
switch (format) {
205211
case AudioDecoderBase::Format::S8:
@@ -217,3 +223,15 @@ int AudioDecoder::GetSamplesizeForFormat(AudioDecoderBase::Format format) {
217223
assert(false && "Bad format");
218224
return -1;
219225
}
226+
227+
void AudioDecoder::SetLogVolume() {
228+
int balance = GetBalance();
229+
float left_gain = 1.f, right_gain = 1.f;
230+
if (balance <= 50) {
231+
right_gain = balance / 50.f;
232+
} else {
233+
left_gain = (100 - balance) / 50.f;
234+
}
235+
log_volume.first = AdjustVolume(volume * left_gain);
236+
log_volume.second = AdjustVolume(volume * right_gain);
237+
}

src/audio_decoder.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class AudioDecoder : public AudioDecoderBase {
7171
*
7272
* @return current volume (from 0 - 100)
7373
*/
74-
int GetVolume() const final override;
74+
std::pair<int, int> GetVolume() const final override;
7575

7676
/**
7777
* Sets the volume of the audio decoder.
@@ -100,14 +100,21 @@ class AudioDecoder : public AudioDecoderBase {
100100
* @param delta Time in us since the last call of this function.
101101
*/
102102
void Update(std::chrono::microseconds delta) final override;
103+
104+
/**
105+
* Sets the pan/balance of the audio decoder.
106+
*/
107+
void SetBalance(int new_balance) override;
103108
private:
104109
bool paused = false;
105110
float volume = 0.0f;
106-
float log_volume = 0.0f; // as used by RPG_RT
111+
std::pair<float, float> log_volume = {0.0f, 0.0f}; // as used by RPG_RT
107112

108113
int fade_volume_end = 0;
109114
std::chrono::microseconds fade_time = std::chrono::microseconds(0);
110115
float delta_volume_step = 0.0f;
116+
117+
void SetLogVolume();
111118
};
112119

113120
#endif

src/audio_decoder_base.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ int AudioDecoderBase::GetLoopCount() const {
7373
return loop_count;
7474
}
7575

76+
int AudioDecoderBase::GetBalance() const {
77+
return balance;
78+
}
79+
80+
void AudioDecoderBase::SetBalance(int new_balance) {
81+
balance = Utils::Clamp(new_balance, 0, 100);
82+
}
83+
7684
bool AudioDecoderBase::WasInited() const {
7785
return true;
7886
}

src/audio_decoder_base.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,18 @@ class AudioDecoderBase {
107107
*/
108108
virtual int GetLoopCount() const;
109109

110+
/**
111+
* Gets the current balance of the audio decoder.
112+
* The value is in the range 0 - 100, where 0 is full left, 100 is full right
113+
*/
114+
virtual int GetBalance() const;
115+
116+
/**
117+
* Sets the balance of the audio decoder.
118+
* The value is clamped to the range 0 - 100.
119+
*/
120+
virtual void SetBalance(int new_balance);
121+
110122
// Functions to be implemented by the audio decoder
111123
/**
112124
* Assigns a stream to the audio decoder.
@@ -131,9 +143,9 @@ class AudioDecoderBase {
131143
* Gets the current volume of the audio decoder.
132144
* Fades are considered.
133145
*
134-
* @return current volume (from 0 - 100)
146+
* @return left-right pair of current volume (from 0 - 100)
135147
*/
136-
virtual int GetVolume() const = 0;
148+
virtual std::pair<int, int> GetVolume() const = 0;
137149

138150
/**
139151
* Sets the current volume of the audio decoder.
@@ -282,6 +294,7 @@ class AudioDecoderBase {
282294

283295
bool looping = false;
284296
int loop_count = 0;
297+
int balance = 50;
285298

286299
int Decode(uint8_t* buffer, int length, int recursion_depth);
287300
};

src/audio_decoder_midi.cpp

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "audio_decoder_midi.h"
2222
#include "midisequencer.h"
2323
#include "output.h"
24+
#include "utils.h"
2425

2526
using namespace std::chrono_literals;
2627

@@ -38,6 +39,7 @@ constexpr int samples_per_play = 64 / sample_divider;
3839

3940
static const uint8_t midi_set_reg_param_upper = 0x6;
4041
static const uint8_t midi_control_volume = 0x7;
42+
static const uint8_t midi_control_pan = 0xA;
4143
static const uint8_t midi_event_control_change = 0xB;
4244
static const uint8_t midi_set_reg_param_lower = 0x26;
4345
static const uint8_t midi_control_all_sound_off = 0x78;
@@ -66,6 +68,10 @@ static uint32_t midimsg_volume(uint8_t channel, uint8_t volume) {
6668
return midimsg_make(midi_event_control_change, channel, midi_control_volume, volume);
6769
}
6870

71+
static uint32_t midimsg_pan(uint8_t channel, uint8_t pan) {
72+
return midimsg_make(midi_event_control_change, channel, midi_control_pan, pan);
73+
}
74+
6975
static uint32_t midimsg_reset_all_controller(uint8_t channel) {
7076
return midimsg_make(midi_event_control_change, channel, midi_control_reset_all_controller, 0);
7177
}
@@ -175,14 +181,15 @@ void AudioDecoderMidi::Resume() {
175181
}
176182
}
177183

178-
int AudioDecoderMidi::GetVolume() const {
184+
std::pair<int, int> AudioDecoderMidi::GetVolume() const {
179185
// When handled by Midi messages fake a 100 otherwise the volume is adjusted twice
180186

181187
if (!mididec->SupportsMidiMessages()) {
182-
return static_cast<int>(log_volume);
188+
const auto& [left, right] = log_volume;
189+
return std::pair(static_cast<int>(left), static_cast<int>(right));
183190
}
184191

185-
return 100;
192+
return {100, 100};
186193
}
187194

188195
void AudioDecoderMidi::SetVolume(int new_volume) {
@@ -195,9 +202,7 @@ void AudioDecoderMidi::SetVolume(int new_volume) {
195202
mididec->SendMidiMessage(msg);
196203
}
197204

198-
if (!mididec->SupportsMidiMessages()) {
199-
log_volume = AdjustVolume(volume * 100.0f);
200-
}
205+
SetLogVolume();
201206
}
202207

203208
void AudioDecoderMidi::SetFade(int end, std::chrono::milliseconds duration) {
@@ -214,6 +219,19 @@ void AudioDecoderMidi::SetFade(int end, std::chrono::milliseconds duration) {
214219
delta_volume_step = (fade_volume_end - volume) / fade_steps;
215220
}
216221

222+
void AudioDecoderMidi::SetBalance(int new_balance) {
223+
AudioDecoderBase::SetBalance(new_balance);
224+
SetLogVolume();
225+
226+
uint8_t pan = Utils::Clamp(last_known_midi_pan + ((GetBalance() - 50) * 2), 0, 127);
227+
for (int channel = 0; channel < 16; channel++) {
228+
uint32_t msg = midimsg_pan(channel, pan);
229+
mididec->SendMidiMessage(msg);
230+
}
231+
232+
Output::Warning("setting balance to {}", new_balance);
233+
}
234+
217235
bool AudioDecoderMidi::Seek(std::streamoff offset, std::ios_base::seekdir origin) {
218236
assert(!tempo.empty());
219237

@@ -251,9 +269,7 @@ void AudioDecoderMidi::Update(std::chrono::microseconds delta) {
251269
}
252270
if (fade_steps > 0 && mtime - last_fade_mtime > 0.1s) {
253271
volume = Utils::Clamp<float>(volume + delta_volume_step, 0.0f, 1.0f);
254-
if (!mididec->SupportsMidiMessages()) {
255-
log_volume = AdjustVolume(volume * 100.0f);
256-
}
272+
SetLogVolume();
257273
for (int i = 0; i < 16; i++) {
258274
uint32_t msg = midimsg_volume(i, static_cast<uint8_t>(channel_volumes[i] * volume * global_volume));
259275
mididec->SendMidiMessage(msg);
@@ -392,6 +408,12 @@ void AudioDecoderMidi::midi_message(int, uint_least32_t message) {
392408
channel_volumes[channel] = value2;
393409
// Send the modified volume to midiout
394410
message = midimsg_volume(channel, static_cast<uint8_t>(value2 * volume * global_volume));
411+
} else if (event_type == midi_event_control_change && value1 == midi_control_pan) {
412+
// See #2585 for details on MIDI values processed by RPG_RT
413+
last_known_midi_pan = value2;
414+
uint8_t pan = Utils::Clamp(value2 + ((GetBalance() - 50) * 2), 0, 127);
415+
Output::Warning("want={} changed={} balance={}", value2, pan, GetBalance());
416+
message = midimsg_pan(channel, pan);
395417
}
396418
if (midimsg_validate(message)) {
397419
mididec->SendMidiMessage(message);
@@ -480,3 +502,17 @@ int AudioDecoderMidi::MidiTempoData::GetSamples(std::chrono::microseconds mtime_
480502
int ticks_since_last = static_cast<int>(ticks_per_us * delta.count());
481503
return samples + static_cast<int>(ticks_since_last * samples_per_tick);
482504
}
505+
506+
void AudioDecoderMidi::SetLogVolume() {
507+
if (!mididec->SupportsMidiMessages()) {
508+
int balance = GetBalance();
509+
float left_gain = 1.f, right_gain = 1.f;
510+
if (balance <= 50) {
511+
right_gain = balance / 50.f;
512+
} else {
513+
left_gain = (100 - balance) / 50.f;
514+
}
515+
log_volume.first = AdjustVolume(volume * left_gain);
516+
log_volume.second = AdjustVolume(volume * right_gain);
517+
}
518+
}

src/audio_decoder_midi.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class AudioDecoderMidi final : public AudioDecoderBase, public midisequencer::ou
5656
*
5757
* @return current volume (from 0 - 100)
5858
*/
59-
int GetVolume() const override;
59+
std::pair<int, int> GetVolume() const override;
6060

6161
/**
6262
* Sets the volume of the MIDI devices by sending MIDI messages
@@ -156,6 +156,11 @@ class AudioDecoderMidi final : public AudioDecoderBase, public midisequencer::ou
156156
*/
157157
bool IsPaused() const;
158158

159+
/**
160+
* Sets the balance of the MIDI decoder.
161+
*/
162+
void SetBalance(int new_balance) override;
163+
159164
std::vector<uint8_t> file_buffer;
160165
size_t file_buffer_pos = 0;
161166
private:
@@ -177,7 +182,7 @@ class AudioDecoderMidi final : public AudioDecoderBase, public midisequencer::ou
177182
bool paused = false;
178183
float volume = 0.0f;
179184
float global_volume = 1.0f; // only used by midiout
180-
float log_volume = 0.0f; // as used by RPG_RT, for Midi decoder without event support
185+
std::pair<float, float> log_volume = {0.0f, 0.0f}; // as used by RPG_RT, for Midi decoder without event support
181186
bool loops_to_end = false;
182187

183188
int fade_steps = 0;
@@ -210,6 +215,9 @@ class AudioDecoderMidi final : public AudioDecoderBase, public midisequencer::ou
210215
// Contains one entry per tempo change (latest on top)
211216
// When looping all entries after the loop point are dropped
212217
std::vector<MidiTempoData> tempo;
218+
219+
void SetLogVolume();
220+
int last_known_midi_pan = 64;
213221
};
214222

215223
#endif

0 commit comments

Comments
 (0)