aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/aoapplication.cpp92
-rw-r--r--src/aoapplication.h15
-rw-r--r--src/aoblipplayer.cpp47
-rw-r--r--src/aoblipplayer.h14
-rw-r--r--src/aomusicplayer.cpp330
-rw-r--r--src/aomusicplayer.h47
-rw-r--r--src/aosfxplayer.cpp63
-rw-r--r--src/aosfxplayer.h10
-rw-r--r--src/courtroom.cpp31
-rw-r--r--src/courtroom.h2
-rw-r--r--src/lobby.cpp2
-rw-r--r--src/networkmanager.cpp6
-rw-r--r--src/networkmanager.h2
-rw-r--r--src/widgets/aooptionsdialog.cpp32
-rw-r--r--src/widgets/aooptionsdialog.h1
15 files changed, 279 insertions, 415 deletions
diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp
index 774816bb..ecfd97aa 100644
--- a/src/aoapplication.cpp
+++ b/src/aoapplication.cpp
@@ -26,6 +26,13 @@ AOApplication::AOApplication(QObject *parent)
message_handler_context = this;
original_message_handler = qInstallMessageHandler(message_handler);
+
+ mus_decoders[0] = ma_decoding_backend_libopus;
+ mus_decoders[1] = ma_decoding_backend_libvorbis;
+ mus_decoder_config = ma_decoder_config_init_default();
+ mus_decoder_config.pCustomBackendUserData = nullptr;
+ mus_decoder_config.customBackendCount = 2;
+ mus_decoder_config.ppCustomBackendVTables = mus_decoders;
}
AOApplication::~AOApplication()
@@ -175,24 +182,6 @@ void AOApplication::call_settings_menu()
delete l_dialog;
}
-// Callback for when BASS device is lost
-// Only actually used for music syncs
-void CALLBACK AOApplication::BASSreset(HSTREAM handle, DWORD channel, DWORD data, void *user)
-{
- Q_UNUSED(handle);
- Q_UNUSED(channel);
- Q_UNUSED(data);
- Q_UNUSED(user);
- doBASSreset();
-}
-
-void AOApplication::doBASSreset()
-{
- BASS_Free();
- BASS_Init(-1, 48000, BASS_DEVICE_LATENCY, nullptr, nullptr);
- load_bass_plugins();
-}
-
void AOApplication::server_connected()
{
qInfo() << "Established connection to server.";
@@ -203,35 +192,33 @@ void AOApplication::server_connected()
courtroom_loaded = false;
}
-void AOApplication::initBASS()
+void AOApplication::initAudio()
{
- BASS_SetConfig(BASS_CONFIG_DEV_DEFAULT, 1);
- BASS_Free();
- // Change the default audio output device to be the one the user has given
- // in his config.ini file for now.
- unsigned int a = 0;
- BASS_DEVICEINFO info;
-
- if (Options::getInstance().audioOutputDevice() == "default")
+ ma_context ctx;
+ if (ma_context_init(nullptr, 0, nullptr, &ctx) != MA_SUCCESS)
{
- BASS_Init(-1, 48000, BASS_DEVICE_LATENCY, nullptr, nullptr);
- load_bass_plugins();
+ qCritical("Failed to initialize audio context.");
}
- else
+ // TODO: Support multiple devices
+
+ ma_resource_manager_config rm_config = ma_resource_manager_config_init();
+ rm_config.decodedFormat = ma_format_f32;
+ rm_config.decodedChannels = 2;
+ rm_config.decodedSampleRate = 48000;
+ ma_decoding_backend_vtable *decoders[] = {ma_decoding_backend_libopus, ma_decoding_backend_libvorbis};
+ rm_config.ppCustomDecodingBackendVTables = decoders;
+ rm_config.customDecodingBackendCount = sizeof(decoders) / sizeof(decoders[0]);
+ rm_config.pCustomDecodingBackendUserData = nullptr;
+ if (ma_resource_manager_init(&rm_config, &audio_rm) != MA_SUCCESS)
{
- for (a = 0; BASS_GetDeviceInfo(a, &info); a++)
- {
- if (Options::getInstance().audioOutputDevice() == info.name)
- {
- BASS_SetDevice(a);
- BASS_Init(static_cast<int>(a), 48000, BASS_DEVICE_LATENCY, nullptr, nullptr);
- load_bass_plugins();
- qInfo() << info.name << "was set as the default audio output device.";
- return;
- }
- }
- BASS_Init(-1, 48000, BASS_DEVICE_LATENCY, nullptr, nullptr);
- load_bass_plugins();
+ qCritical("Failed to initialize audio resource manager.");
+ }
+
+ ma_engine_config engine_config = ma_engine_config_init();
+ engine_config.pResourceManager = &audio_rm;
+ if (ma_engine_init(&engine_config, &audio_engine) != MA_SUCCESS)
+ {
+ qCritical("Failed to initialize audio engine.");
}
}
@@ -262,22 +249,3 @@ void AOApplication::centerOrMoveWidgetOnPrimaryScreen(QWidget *widget)
widget->move(point->x(), point->y());
}
}
-
-#if (defined(_WIN32) || defined(_WIN64))
-void AOApplication::load_bass_plugins()
-{
- BASS_PluginLoad("bassopus.dll", 0);
-}
-#elif defined __APPLE__
-void AOApplication::load_bass_plugins()
-{
- BASS_PluginLoad("libbassopus.dylib", 0);
-}
-#elif (defined(LINUX) || defined(__linux__))
-void AOApplication::load_bass_plugins()
-{
- BASS_PluginLoad("libbassopus.so", 0);
-}
-#else
-#error This operating system is unsupported for BASS plugins.
-#endif
diff --git a/src/aoapplication.h b/src/aoapplication.h
index 5e67fc86..3773f16e 100644
--- a/src/aoapplication.h
+++ b/src/aoapplication.h
@@ -7,7 +7,9 @@
#include "serverdata.h"
#include "widgets/aooptionsdialog.h"
-#include <bass.h>
+#include "miniaudio.h"
+#include "miniaudio_libvorbis.h"
+#include "miniaudio_libopus.h"
#include <QColor>
#include <QCryptographicHash>
@@ -330,19 +332,22 @@ public:
bool pointExistsOnScreen(QPoint point);
void centerOrMoveWidgetOnPrimaryScreen(QWidget *widget);
- void initBASS();
- static void load_bass_plugins();
- static void CALLBACK BASSreset(HSTREAM handle, DWORD channel, DWORD data, void *user);
- static void doBASSreset();
+ void initAudio();
QElapsedTimer demo_timer;
DemoServer *demo_server = nullptr;
+ ma_engine audio_engine;
+ // Vorbis and Opus decoders.
+ ma_decoding_backend_vtable *mus_decoders[2];
+ ma_decoder_config mus_decoder_config;
+
private:
QVector<ServerInfo> server_list;
QHash<size_t, QString> asset_lookup_cache;
QHash<size_t, QString> dir_listing_cache;
QSet<size_t> dir_listing_exist_cache;
+ ma_resource_manager audio_rm;
public Q_SLOTS:
void server_connected();
diff --git a/src/aoblipplayer.cpp b/src/aoblipplayer.cpp
index 3a13d787..f6531f5c 100644
--- a/src/aoblipplayer.cpp
+++ b/src/aoblipplayer.cpp
@@ -4,9 +4,14 @@ AOBlipPlayer::AOBlipPlayer(AOApplication *ao_app)
: ao_app(ao_app)
{}
+AOBlipPlayer::~AOBlipPlayer()
+{
+ ma_sound_uninit(&m_stream[0]);
+}
+
void AOBlipPlayer::setVolume(int value)
{
- m_volume = value;
+ m_volume = value / 100.0f;
updateInternalVolume();
}
@@ -18,37 +23,37 @@ void AOBlipPlayer::setMuted(bool enabled)
void AOBlipPlayer::setBlip(QString blip)
{
+ if (m_initialized)
+ {
+ ma_sound_uninit(&m_stream[0]);
+ m_initialized = false;
+ }
+ // ma_sound_init_copy?
QString path = ao_app->get_sfx_suffix(ao_app->get_sounds_path(blip));
- for (int i = 0; i < STREAM_COUNT; ++i)
+ ma_result r = ma_sound_init_from_file(&ao_app->audio_engine, qPrintable(path), MA_SOUND_FLAG_ASYNC | MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_NO_SPATIALIZATION | MA_SOUND_FLAG_NO_PITCH, nullptr, nullptr, &m_stream[0]);
+ if (r == MA_SUCCESS)
{
- BASS_StreamFree(m_stream[i]);
-
- if (path.endsWith(".opus"))
- {
- m_stream[i] = BASS_OPUS_StreamCreateFile(FALSE, path.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE);
- }
- else
- {
- m_stream[i] = BASS_StreamCreateFile(FALSE, path.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE);
- }
+ m_initialized = true;
+ updateInternalVolume();
+ }
+ else
+ {
+ qWarning() << "Failed to init blip" << path << "error" << r;
}
-
- updateInternalVolume();
}
void AOBlipPlayer::playBlip()
{
- HSTREAM stream = m_stream[m_cycle];
- BASS_ChannelSetDevice(stream, BASS_GetDevice());
- BASS_ChannelPlay(stream, false);
- m_cycle = ++m_cycle % STREAM_COUNT;
+ if (m_initialized)
+ {
+ ma_sound_start(&m_stream[0]);
+ }
}
void AOBlipPlayer::updateInternalVolume()
{
- float volume = m_muted ? 0.0f : (m_volume * 0.01);
- for (int i = 0; i < STREAM_COUNT; ++i)
+ if (m_initialized)
{
- BASS_ChannelSetAttribute(m_stream[i], BASS_ATTRIB_VOL, volume);
+ ma_sound_set_volume(&m_stream[0], m_muted ? 0.0f : qBound(0.0f, m_volume, 1.0f));
}
}
diff --git a/src/aoblipplayer.h b/src/aoblipplayer.h
index 92b43d29..d85cc36f 100644
--- a/src/aoblipplayer.h
+++ b/src/aoblipplayer.h
@@ -2,9 +2,6 @@
#include "aoapplication.h"
-#include <bass.h>
-#include <bassopus.h>
-
#include <QDebug>
#include <QElapsedTimer>
#include <QWidget>
@@ -17,6 +14,7 @@ public:
static constexpr int STREAM_COUNT = 5;
AOBlipPlayer(AOApplication *ao_app);
+ ~AOBlipPlayer();
void setVolume(int value);
void setMuted(bool enabled);
@@ -25,13 +23,13 @@ public:
void playBlip();
+ void updateInternalVolume();
+
private:
AOApplication *ao_app;
- int m_volume = 0;
+ float m_volume = 0.0f;
bool m_muted = false;
- HSTREAM m_stream[STREAM_COUNT]{};
- int m_cycle = 0;
-
- void updateInternalVolume();
+ bool m_initialized = false;
+ ma_sound m_stream[STREAM_COUNT]{};
};
diff --git a/src/aomusicplayer.cpp b/src/aomusicplayer.cpp
index c4a562cb..766effac 100644
--- a/src/aomusicplayer.cpp
+++ b/src/aomusicplayer.cpp
@@ -1,12 +1,14 @@
#include "aomusicplayer.h"
#include "file_functions.h"
+#include "networkmanager.h"
#include "options.h"
-#include <bass.h>
-
#include <QDebug>
+#include <QFileInfo>
#include <QFuture>
+#include <QNetworkReply>
+#include <QNetworkRequest>
#include <QWidget>
AOMusicPlayer::AOMusicPlayer(AOApplication *ao_app)
@@ -15,271 +17,179 @@ AOMusicPlayer::AOMusicPlayer(AOApplication *ao_app)
AOMusicPlayer::~AOMusicPlayer()
{
- for (int n_stream = 0; n_stream < STREAM_COUNT; ++n_stream)
+ for (int i = 0; i < STREAM_COUNT; ++i)
{
- BASS_ChannelStop(m_stream_list[n_stream]);
+ stop(i);
}
}
-QString AOMusicPlayer::playStream(QString song, int streamId, bool loopEnabled, int effectFlags)
+void AOMusicPlayer::setMuted(bool enabled)
{
- if (!ensureValidStreamId(streamId))
+ m_muted = enabled;
+ for (int i = 0; i < STREAM_COUNT; ++i)
{
- return "[ERROR] Invalid Channel";
+ setStreamVolume(m_volume[i], i);
}
+}
- quint32 flags = BASS_STREAM_AUTOFREE;
- if (loopEnabled)
+void AOMusicPlayer::setStreamVolume(int value, int streamId)
+{
+ if (!ensureValidStreamId(streamId))
{
- flags |= BASS_SAMPLE_LOOP;
+ qWarning().noquote() << QObject::tr("Invalid stream ID '%2'").arg(streamId);
+ return;
}
- QString f_path = song;
- HSTREAM newstream;
- if (f_path.startsWith("http"))
- {
- if (!Options::getInstance().streamingEnabled())
- {
- BASS_ChannelStop(m_stream_list[streamId]);
- return QObject::tr("[MISSING] Streaming disabled.");
- }
- QUrl l_url = QUrl(f_path);
- newstream = BASS_StreamCreateURL(l_url.toEncoded().toStdString().c_str(), 0, flags, nullptr, 0);
- }
- else
- {
- flags |= BASS_STREAM_PRESCAN | BASS_UNICODE | BASS_ASYNCFILE;
+ m_volume[streamId] = m_muted ? 0.0f : qBound(0.0f, value / 100.0f, 1.0f);
+ ma_sound_set_volume(&m_stream[streamId].sound, m_volume[streamId]);
+}
- f_path = ao_app->get_real_path(ao_app->get_music_path(song));
- newstream = BASS_StreamCreateFile(FALSE, f_path.utf16(), 0, 0, flags);
- }
+bool AOMusicPlayer::ensureValidStreamId(int streamId)
+{
+ return streamId >= 0 && streamId < STREAM_COUNT;
+}
- int error = BASS_ErrorGetCode();
- if (Options::getInstance().audioOutputDevice() != "default")
+void AOMusicPlayer::play_from_url(const QString &url, int id, bool looping, int effect_flags)
+{
+ auto &s = m_stream[id];
+ stop(id);
+ if (!Options::getInstance().streamingEnabled())
{
- BASS_ChannelSetDevice(m_stream_list[streamId], BASS_GetDevice());
+ emit track_ready(QStringLiteral("[DISABLED] Streaming disabled"));
+ return;
}
- m_loop_start[streamId] = 0;
- m_loop_end[streamId] = 0;
+ // Reusing network manager that already exists in ao_app.
+ s.reply = ao_app->net_manager->get_audio_url(QUrl(url));
- QString d_path = f_path + ".txt";
- if (loopEnabled && file_exists(d_path)) // Contains loop/etc. information file
+ QVariant len = s.reply->header(QNetworkRequest::ContentLengthHeader);
+ bool ok = false;
+ qint64 content_length = len.toLongLong(&ok);
+ static const qint64 max_stream_size = 256 * 1024 * 1024;
+ if (ok && content_length > max_stream_size)
{
- QStringList lines = ao_app->read_file(d_path).split("\n");
- bool seconds_mode = false;
- foreach (QString line, lines)
- {
- QStringList args = line.split("=");
- if (args.size() < 2)
- {
- continue;
- }
- QString arg = args[0].trimmed();
- if (arg == "seconds")
- {
- if (args[1].trimmed() == "true")
- {
- seconds_mode = true; // Use new epic behavior
- continue;
- }
-
- continue;
- }
-
- float sample_rate;
- BASS_ChannelGetAttribute(newstream, BASS_ATTRIB_FREQ, &sample_rate);
-
- // Grab number of bytes for sample size
- int sample_size = 16 / 8;
-
- // number of channels (stereo/mono)
- int num_channels = 2;
-
- // Calculate the bytes for loop_start/loop_end to use with the sync proc
- QWORD bytes;
- if (seconds_mode)
- {
- bytes = BASS_ChannelSeconds2Bytes(newstream, args[1].trimmed().toDouble());
- }
- else
- {
- bytes = static_cast<QWORD>(args[1].trimmed().toUInt() * sample_size * num_channels);
- }
- if (arg == "loop_start")
- {
- m_loop_start[streamId] = bytes;
- }
- else if (arg == "loop_length")
- {
- m_loop_end[streamId] = m_loop_start[streamId] + bytes;
- }
- else if (arg == "loop_end")
- {
- m_loop_end[streamId] = bytes;
- }
- }
- qDebug() << "Found data file for song" << song << "length" << BASS_ChannelGetLength(newstream, BASS_POS_BYTE) << "loop start" << m_loop_start[streamId] << "loop end" << m_loop_end[streamId];
+ s.reply->abort();
+ s.reply->deleteLater();
+ emit track_ready(QStringLiteral("[ERROR] Stream exceeds maximum size, refusing to download"));
+ return;
}
-
- if (BASS_ChannelIsActive(m_stream_list[streamId]) == BASS_ACTIVE_PLAYING)
+ if (ok && content_length > 0)
{
- DWORD oldstream = m_stream_list[streamId];
-
- if (effectFlags & SYNC_POS)
+ m_stream[id].buffer.reserve(content_length);
+ }
+ connect(s.reply, &QIODevice::readyRead, this, [this, id] {
+ auto &s = m_stream[id];
+ QByteArray chunk = s.reply->readAll();
+ if (s.buffer.size() + chunk.size() > max_stream_size)
{
- BASS_ChannelLock(oldstream, true);
- // Sync it with the new sample
- BASS_ChannelSetPosition(newstream, BASS_ChannelGetPosition(oldstream, BASS_POS_BYTE), BASS_POS_BYTE);
- BASS_ChannelLock(oldstream, false);
+ s.reply->abort();
+ s.reply->deleteLater();
+ s.reply = nullptr;
+ s.buffer.clear();
+ emit track_ready(QStringLiteral("[ERROR] Attempted to download beyond buffer size, aborting"));
+ return;
}
+ s.buffer.append(chunk);
+ });
+ connect(s.reply, &QNetworkReply::finished, this, [this, id, looping] {
+ auto &s = m_stream[id];
+ on_url_download_finished(id, s.reply, looping);
+ });
+}
- if ((effectFlags & FADE_OUT) && m_volume[streamId] > 0)
- {
- // Fade out the other sample and stop it (due to -1)
- BASS_ChannelSlideAttribute(oldstream, BASS_ATTRIB_VOL | BASS_SLIDE_LOG, -1, 4000);
- }
- else
- {
- BASS_ChannelStop(oldstream); // Stop the sample since we don't need it anymore
- }
- }
- else
- {
- BASS_ChannelStop(m_stream_list[streamId]);
- }
+void AOMusicPlayer::on_url_download_finished(int id, QNetworkReply *reply, bool looping)
+{
+ auto &s = m_stream[id];
- m_stream_list[streamId] = newstream;
- BASS_ChannelPlay(newstream, false);
- if (effectFlags & FADE_IN)
+ if (reply->error() != QNetworkReply::NoError)
{
- // Fade in our sample
- BASS_ChannelSetAttribute(newstream, BASS_ATTRIB_VOL, 0);
- BASS_ChannelSlideAttribute(newstream, BASS_ATTRIB_VOL, static_cast<float>(m_volume[streamId] / 100.0f), 1000);
- }
- else
- {
- this->setStreamVolume(m_volume[streamId], streamId);
- }
-
- BASS_ChannelSetSync(newstream, BASS_SYNC_DEV_FAIL, 0, ao_app->BASSreset, 0);
-
- this->setStreamLooping(loopEnabled, streamId); // Have to do this here due to any
- // crossfading-related changes, etc.
-
- bool is_stop = (song == "~stop.mp3");
- QString p_song_clear = QUrl(song).fileName();
- p_song_clear = p_song_clear.left(p_song_clear.lastIndexOf('.'));
-
- if (is_stop && streamId == 0)
- { // don't send text on channels besides 0
- return QObject::tr("None");
- }
-
- if (error == BASS_ERROR_HANDLE)
- { // Cheap hack to see if file missing
- return QObject::tr("[MISSING] %1").arg(p_song_clear);
+ reply->deleteLater();
+ emit track_ready(QStringLiteral("[ERROR] Unable to stream audio due to network error"));
+ return;
}
- if (song.startsWith("http") && streamId == 0)
+ if (ma_decoder_init_memory(s.buffer.data(), s.buffer.size(), &ao_app->mus_decoder_config, &s.decoder) != MA_SUCCESS)
{
- return QObject::tr("[STREAM] %1").arg(p_song_clear);
+ reply->deleteLater();
+ emit track_ready(QStringLiteral("[ERROR] Invalid audio format (stream decoding failed)"));
+ return;
}
+ s.has_decoder = true;
- if (streamId == 0)
+ ma_uint32 flags = m_flags | (looping ? MA_SOUND_FLAG_LOOPING : 0);
+ if (ma_sound_init_from_data_source(&ao_app->audio_engine, &s.decoder, flags, nullptr, &s.sound) != MA_SUCCESS)
{
- return p_song_clear;
+ reply->deleteLater();
+ emit track_ready(QStringLiteral("[ERROR] Failed to initialize audio"));
+ return;
}
- return "";
+ start_playback(id);
+
+ QString track_path = QUrl(reply->url()).path();
+ reply->deleteLater();
+ emit track_ready(QString("[STREAM] %1").arg(QFileInfo(track_path).completeBaseName()));
}
-void AOMusicPlayer::setMuted(bool enabled)
+void AOMusicPlayer::start_playback(int id)
{
- m_muted = enabled;
- // Update all volume based on the mute setting
- for (int n_stream = 0; n_stream < STREAM_COUNT; ++n_stream)
- {
- setStreamVolume(m_volume[n_stream], n_stream);
- }
+ auto &s = m_stream[id];
+ ma_sound_set_volume(&s.sound, m_volume[id]);
+ ma_sound_start(&s.sound);
+ s.state = Stream::playing;
}
-void AOMusicPlayer::setStreamVolume(int value, int streamId)
+void AOMusicPlayer::stop(int id)
{
- if (!ensureValidStreamId(streamId))
+ auto &s = m_stream[id];
+
+ if (s.state == Stream::playing)
{
- qWarning().noquote() << QObject::tr("Invalid stream ID '%2'").arg(streamId);
- return;
+ ma_sound_stop(&s.sound);
+ ma_sound_uninit(&s.sound);
}
- m_volume[streamId] = value;
- // If muted, volume will always be 0
- float volume = (m_volume[streamId] / 100.0f) * !m_muted;
- if (streamId < 0)
+ if (s.has_decoder)
{
- for (int n_stream = 0; n_stream < STREAM_COUNT; ++n_stream)
- {
- BASS_ChannelSetAttribute(m_stream_list[n_stream], BASS_ATTRIB_VOL, volume);
- }
+ ma_decoder_uninit(&s.decoder);
+ s.has_decoder = false;
}
- else
+
+ if (s.reply)
{
- BASS_ChannelSetAttribute(m_stream_list[streamId], BASS_ATTRIB_VOL, volume);
+ s.reply->abort();
+ s.reply->deleteLater();
+ s.reply = nullptr;
}
-}
-void CALLBACK loopProc(HSYNC handle, DWORD channel, DWORD data, void *user)
-{
- Q_UNUSED(handle);
- Q_UNUSED(data);
- QWORD loop_start = *(static_cast<unsigned *>(user));
- BASS_ChannelLock(channel, true);
- BASS_ChannelSetPosition(channel, loop_start, BASS_POS_BYTE);
- BASS_ChannelLock(channel, false);
+ s.buffer.clear();
+ s.state = Stream::standby;
}
-void AOMusicPlayer::setStreamLooping(bool enabled, int streamId)
+void AOMusicPlayer::play_from_file(const QString &path, int id, bool looping, int effect_flags)
{
- if (!ensureValidStreamId(streamId))
- {
- qWarning().noquote() << QObject::tr("Invalid stream ID '%2'").arg(streamId);
- return;
- }
+ auto &s = m_stream[id];
+ stop(id);
+
+ ma_uint32 flags = m_flags | (looping ? MA_SOUND_FLAG_LOOPING : 0);
- if (!enabled)
+ bool is_stop = (path == "~stop.mp3");
+ // Temporary is_stop stub.
+ // Fades and effects are to be considered when implemented.
+ if (is_stop)
{
- if (BASS_ChannelFlags(m_stream_list[streamId], 0, 0) & BASS_SAMPLE_LOOP)
- {
- BASS_ChannelFlags(m_stream_list[streamId], 0,
- BASS_SAMPLE_LOOP); // remove the LOOP flag
- }
- BASS_ChannelRemoveSync(m_stream_list[streamId], m_loop_sync[streamId]);
- m_loop_sync[streamId] = 0;
+ emit track_ready(QStringLiteral("None"));
return;
}
- BASS_ChannelFlags(m_stream_list[streamId], BASS_SAMPLE_LOOP,
- BASS_SAMPLE_LOOP); // set the LOOP flag
- if (m_loop_sync[streamId] != 0)
+ QString f_path = ao_app->get_real_path(ao_app->get_music_path(path));
+ if (ma_sound_init_from_file(&ao_app->audio_engine, qPrintable(f_path), flags, nullptr, nullptr, &s.sound) != MA_SUCCESS)
{
- BASS_ChannelRemoveSync(m_stream_list[streamId],
- m_loop_sync[streamId]); // remove the sync
- m_loop_sync[streamId] = 0;
+ emit track_ready(QStringLiteral("[ERROR] Failed to initialize sound"));
+ return;
}
- if (m_loop_start[streamId] < m_loop_end[streamId])
- {
- // Loop when the endpoint is reached.
- m_loop_sync[streamId] = BASS_ChannelSetSync(m_stream_list[streamId], BASS_SYNC_POS | BASS_SYNC_MIXTIME, m_loop_end[streamId], loopProc, &m_loop_start[streamId]);
- }
- else
- {
- // Loop when the end of the file is reached.
- m_loop_sync[streamId] = BASS_ChannelSetSync(m_stream_list[streamId], BASS_SYNC_END | BASS_SYNC_MIXTIME, 0, loopProc, &m_loop_start[streamId]);
- }
-}
+ start_playback(id);
-bool AOMusicPlayer::ensureValidStreamId(int streamId)
-{
- return (streamId >= 0 && streamId < STREAM_COUNT);
+ emit track_ready(QFileInfo(f_path).completeBaseName());
}
diff --git a/src/aomusicplayer.h b/src/aomusicplayer.h
index 707d64ad..b45c22fe 100644
--- a/src/aomusicplayer.h
+++ b/src/aomusicplayer.h
@@ -2,17 +2,20 @@
#include "aoapplication.h"
-#include <QFutureWatcher>
+#include <QNetworkAccessManager>
+#include <QPointer>
-class AOMusicPlayer
+class AOMusicPlayer : public QObject
{
+ Q_OBJECT
+
public:
// 0 = music
// 1 = ambience
static constexpr int STREAM_COUNT = 2;
explicit AOMusicPlayer(AOApplication *ao_app);
- virtual ~AOMusicPlayer();
+ ~AOMusicPlayer();
void setMuted(bool enabled);
@@ -21,18 +24,46 @@ public:
void setStreamVolume(int value, int streamId);
void setStreamLooping(bool enabled, int streamId);
- QFutureWatcher<QString> m_watcher;
+ void play_from_url(const QString &url, int id, bool looping, int flags);
+ void play_from_file(const QString &path, int id, bool looping, int flags);
private:
+ struct Stream
+ {
+ ma_sound sound;
+ QByteArray buffer;
+ ma_decoder decoder;
+ // I've had use-after-free crashes on `stop` due to QNetworkReply's lifetime
+ // being unclear (specifically, calling `abort` on it). QPointer is a
+ // bandaid fix.
+ QPointer<QNetworkReply> reply;
+ bool has_decoder;
+ enum
+ {
+ standby,
+ playing,
+ } state = standby;
+ };
+
+ // Default flags for music: STREAM decodes incrementally in 2-second chunks,
+ // the other two disable 3D positioning and Doppler effect.
+ const ma_uint32 m_flags = MA_SOUND_FLAG_STREAM | MA_SOUND_FLAG_NO_SPATIALIZATION | MA_SOUND_FLAG_NO_PITCH;
+
AOApplication *ao_app;
+ float m_volume[STREAM_COUNT]{};
bool m_muted = false;
-
- int m_volume[STREAM_COUNT]{};
- HSTREAM m_stream_list[STREAM_COUNT]{};
- HSYNC m_loop_sync[STREAM_COUNT]{};
+ Stream m_stream[STREAM_COUNT]{};
+ ma_pcm_rb m_audio_ring;
quint32 m_loop_start[STREAM_COUNT]{};
quint32 m_loop_end[STREAM_COUNT]{};
bool ensureValidStreamId(int streamId);
+
+ void on_url_download_finished(int id, QNetworkReply *reply, bool looping);
+ void start_playback(int id);
+ void stop(int id);
+
+signals:
+ void track_ready(QString label);
};
diff --git a/src/aosfxplayer.cpp b/src/aosfxplayer.cpp
index ac3b8513..00fac678 100644
--- a/src/aosfxplayer.cpp
+++ b/src/aosfxplayer.cpp
@@ -6,14 +6,23 @@ AOSfxPlayer::AOSfxPlayer(AOApplication *ao_app)
: ao_app(ao_app)
{}
-int AOSfxPlayer::volume()
+AOSfxPlayer::~AOSfxPlayer()
{
- return m_volume;
+ for (int i = 0; i < STREAM_COUNT; ++i)
+ {
+ ma_sound_uninit(&m_stream[i]);
+ }
}
void AOSfxPlayer::setVolume(int value)
{
- m_volume = value;
+ m_volume = value / 100.0f;
+ updateInternalVolume();
+}
+
+void AOSfxPlayer::setMuted(bool enabled)
+{
+ m_muted = enabled;
updateInternalVolume();
}
@@ -21,7 +30,7 @@ void AOSfxPlayer::play(QString path)
{
for (int i = 0; i < STREAM_COUNT; ++i)
{
- if (BASS_ChannelIsActive(m_stream[i]) == BASS_ACTIVE_PLAYING)
+ if (ma_sound_is_playing(&m_stream[i]))
{
m_current_stream_id = (i + 1) % STREAM_COUNT;
}
@@ -31,21 +40,15 @@ void AOSfxPlayer::play(QString path)
break;
}
}
-
- if (path.endsWith(".opus"))
- {
- m_stream[m_current_stream_id] = BASS_OPUS_StreamCreateFile(FALSE, path.utf16(), 0, 0, BASS_STREAM_AUTOFREE | BASS_UNICODE | BASS_ASYNCFILE);
- }
- else
+ ma_sound_uninit(&m_stream[m_current_stream_id]);
+ if (ma_sound_init_from_file(&ao_app->audio_engine, qPrintable(path), MA_SOUND_FLAG_ASYNC | MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_NO_SPATIALIZATION | MA_SOUND_FLAG_NO_PITCH, nullptr, nullptr, &m_stream[m_current_stream_id]) != MA_SUCCESS)
{
- m_stream[m_current_stream_id] = BASS_StreamCreateFile(FALSE, path.utf16(), 0, 0, BASS_STREAM_AUTOFREE | BASS_UNICODE | BASS_ASYNCFILE);
+ return;
}
updateInternalVolume();
- BASS_ChannelSetDevice(m_stream[m_current_stream_id], BASS_GetDevice());
- BASS_ChannelPlay(m_stream[m_current_stream_id], false);
- BASS_ChannelSetSync(m_stream[m_current_stream_id], BASS_SYNC_DEV_FAIL, 0, ao_app->BASSreset, 0);
+ ma_sound_start(&m_stream[m_current_stream_id]);
}
void AOSfxPlayer::findAndPlaySfx(QString sfx)
@@ -81,7 +84,7 @@ void AOSfxPlayer::stopAllLoopingStream()
{
for (int i = 0; i < STREAM_COUNT; ++i)
{
- if (BASS_ChannelFlags(m_stream[i], 0, 0) & BASS_SAMPLE_LOOP)
+ if (ma_sound_is_looping(&m_stream[i]))
{
stop(i);
}
@@ -97,22 +100,14 @@ void AOSfxPlayer::stop(int streamId)
return;
}
- BASS_ChannelStop(m_stream[streamId]);
-}
-
-void AOSfxPlayer::setMuted(bool toggle)
-{
- m_muted = toggle;
- // Update the audio volume
- updateInternalVolume();
+ ma_sound_stop(&m_stream[streamId]);
}
void AOSfxPlayer::updateInternalVolume()
{
- float volume = m_muted ? 0.0f : (m_volume * 0.01);
for (int i = 0; i < STREAM_COUNT; ++i)
{
- BASS_ChannelSetAttribute(m_stream[i], BASS_ATTRIB_VOL, volume);
+ ma_sound_set_volume(&m_stream[i], m_muted ? 0.0f : qBound(0.0f, m_volume, 1.0f));
}
}
@@ -125,23 +120,7 @@ void AOSfxPlayer::setLooping(bool toggle, int streamId)
return;
}
- m_looping = toggle;
- if (BASS_ChannelFlags(m_stream[streamId], 0, 0) & BASS_SAMPLE_LOOP)
- {
- if (m_looping == false)
- {
- BASS_ChannelFlags(m_stream[streamId], 0,
- BASS_SAMPLE_LOOP); // remove the LOOP flag
- }
- }
- else
- {
- if (m_looping == true)
- {
- BASS_ChannelFlags(m_stream[streamId], BASS_SAMPLE_LOOP,
- BASS_SAMPLE_LOOP); // set the LOOP flag
- }
- }
+ ma_sound_set_looping(&m_stream[streamId], toggle);
}
int AOSfxPlayer::maybeFetchCurrentStreamId(int streamId)
diff --git a/src/aosfxplayer.h b/src/aosfxplayer.h
index 7669f0de..b86e00b8 100644
--- a/src/aosfxplayer.h
+++ b/src/aosfxplayer.h
@@ -2,9 +2,6 @@
#include "aoapplication.h"
-#include <bass.h>
-#include <bassopus.h>
-
#include <QDebug>
#include <QWidget>
@@ -14,8 +11,8 @@ public:
static constexpr int STREAM_COUNT = 5;
AOSfxPlayer(AOApplication *ao_app);
+ ~AOSfxPlayer();
- int volume();
void setVolume(int value);
void play(QString path);
@@ -33,10 +30,9 @@ public:
private:
AOApplication *ao_app;
- int m_volume = 0;
+ float m_volume = 0.0f;
bool m_muted = false;
- bool m_looping = true;
- HSTREAM m_stream[STREAM_COUNT]{};
+ ma_sound m_stream[STREAM_COUNT]{};
int m_current_stream_id = 0;
int maybeFetchCurrentStreamId(int streamId);
diff --git a/src/courtroom.cpp b/src/courtroom.cpp
index 7646794d..9d872a28 100644
--- a/src/courtroom.cpp
+++ b/src/courtroom.cpp
@@ -4,6 +4,7 @@
#include "moderation_functions.h"
#include "options.h"
+#include <QFileInfo>
#include <QtConcurrent/QtConcurrent>
// #define DEBUG_TRANSITION
@@ -16,7 +17,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app)
setWindowFlags((this->windowFlags() | Qt::CustomizeWindowHint) & ~Qt::WindowMaximizeButtonHint);
setObjectName("courtroom");
- ao_app->initBASS();
+ ao_app->initAudio();
keepalive_timer = new QTimer(this);
keepalive_timer->start(45000);
@@ -33,7 +34,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app)
music_player = new AOMusicPlayer(ao_app);
music_player->setMuted(true);
- connect(&music_player->m_watcher, &QFutureWatcher<QString>::finished, this, &Courtroom::update_ui_music_name, Qt::QueuedConnection);
+ connect(music_player, &AOMusicPlayer::track_ready, this, [this](QString label) { ui_music_name->setText(label); });
sfx_player = new AOSfxPlayer(ao_app);
sfx_player->setMuted(true);
@@ -538,6 +539,8 @@ Courtroom::~Courtroom()
delete sfx_player;
delete objection_player;
delete blip_player;
+
+ ma_engine_uninit(&ao_app->audio_engine);
}
void Courtroom::on_application_state_changed(Qt::ApplicationState state)
@@ -4720,8 +4723,7 @@ void Courtroom::handle_song(QStringList *p_contents)
int effect_flags = 0; // No effects by default - vanilla functionality
QString f_song = f_contents.at(0);
- QString f_song_clear = QUrl(f_song).fileName();
- f_song_clear = f_song_clear.left(f_song_clear.lastIndexOf('.'));
+ QString f_song_clear = QFileInfo(f_song).completeBaseName();
int n_char = f_contents.at(1).toInt(&ok);
if (!ok)
@@ -4791,24 +4793,17 @@ void Courtroom::handle_song(QStringList *p_contents)
{
// Current song UI only displays the song playing, not other channels.
// Any other music playing is irrelevant.
- if (music_player->m_watcher.isRunning())
- {
- music_player->m_watcher.cancel();
- }
- ui_music_name->setText(tr("[LOADING] %1").arg(f_song_clear));
+ ui_music_name->setText("[LOADING]");
}
- music_player->m_watcher.setFuture(QtConcurrent::run([=, this]() -> QString { return music_player->playStream(f_song, channel, looping, effect_flags); }));
-}
-
-void Courtroom::update_ui_music_name()
-{
- QString result = music_player->m_watcher.result();
- if (result.isEmpty())
+ if (f_song.startsWith("http"))
{
- return;
+ music_player->play_from_url(f_song, channel, looping, effect_flags);
+ }
+ else
+ {
+ music_player->play_from_file(f_song, channel, looping, effect_flags);
}
- ui_music_name->setText(result);
}
void Courtroom::handle_wtce(QString p_wtce, int variant)
diff --git a/src/courtroom.h b/src/courtroom.h
index dc8cdf3a..095d0ea1 100644
--- a/src/courtroom.h
+++ b/src/courtroom.h
@@ -818,8 +818,6 @@ public Q_SLOTS:
void on_reload_theme_clicked();
- void update_ui_music_name();
-
private Q_SLOTS:
void start_chat_ticking();
void play_sfx();
diff --git a/src/lobby.cpp b/src/lobby.cpp
index 05dd9ce4..424f634b 100644
--- a/src/lobby.cpp
+++ b/src/lobby.cpp
@@ -302,7 +302,7 @@ void Lobby::on_about_clicked()
"is copyright (c) 2016-2022 Attorney Online developers. Open-source "
"licenses apply. All other assets are the property of their "
"respective owners."
- "<p>Running on Qt version %2 with the BASS audio engine.<br>"
+ "<p>Running on Qt version %2 with the miniaudio audio engine.<br>"
"APNG plugin loaded: %3"
"<p>Built on %4")
.arg(ao_app->get_version_string())
diff --git a/src/networkmanager.cpp b/src/networkmanager.cpp
index 1202fec8..02d5961e 100644
--- a/src/networkmanager.cpp
+++ b/src/networkmanager.cpp
@@ -204,3 +204,9 @@ void NetworkManager::handle_server_packet(AOPacket packet)
#endif
ao_app->server_packet_received(packet);
}
+
+QNetworkReply *NetworkManager::get_audio_url(const QUrl &url)
+{
+ QNetworkRequest req(url);
+ return http->get(req);
+}
diff --git a/src/networkmanager.h b/src/networkmanager.h
index 8f2d8d42..31b046d1 100644
--- a/src/networkmanager.h
+++ b/src/networkmanager.h
@@ -40,6 +40,8 @@ public Q_SLOTS:
void request_document(MSDocumentType document_type, const std::function<void(QString)> &cb);
void send_heartbeat();
+ QNetworkReply *get_audio_url(const QUrl &url);
+
Q_SIGNALS:
void server_connected(bool state);
diff --git a/src/widgets/aooptionsdialog.cpp b/src/widgets/aooptionsdialog.cpp
index 81d5eb70..eeab53b2 100644
--- a/src/widgets/aooptionsdialog.cpp
+++ b/src/widgets/aooptionsdialog.cpp
@@ -7,7 +7,6 @@
#include "networkmanager.h"
#include "options.h"
-#include <bass.h>
#include <QCollator>
#include <QDoubleSpinBox>
@@ -26,16 +25,8 @@ AOOptionsDialog::AOOptionsDialog(AOApplication *p_ao_app, QWidget *parent)
void AOOptionsDialog::populateAudioDevices()
{
ui_audio_device_combobox->clear();
- if (needsDefaultAudioDevice())
- {
- ui_audio_device_combobox->addItem("default", "default");
- }
-
- BASS_DEVICEINFO info;
- for (int a = 0; BASS_GetDeviceInfo(a, &info); a++)
- {
- ui_audio_device_combobox->addItem(info.name, info.name);
- }
+ ui_audio_device_combobox->addItem("Device selection not implemented");
+ ui_audio_device_combobox->setEnabled(false);
}
template <>
@@ -621,22 +612,3 @@ void AOOptionsDialog::timestampCbChanged(int state)
{
ui_log_timestamp_format_combobox->setDisabled(state == 0);
}
-
-#if (defined(_WIN32) || defined(_WIN64))
-bool AOOptionsDialog::needsDefaultAudioDevice()
-{
- return true;
-}
-#elif (defined(LINUX) || defined(__linux__))
-bool AOOptionsDialog::needsDefaultAudioDevice()
-{
- return false;
-}
-#elif defined __APPLE__
-bool AOOptionsDialog::needsDefaultAudioDevice()
-{
- return true;
-}
-#else
-#error This operating system is not supported.
-#endif
diff --git a/src/widgets/aooptionsdialog.h b/src/widgets/aooptionsdialog.h
index b89607dc..ab59916b 100644
--- a/src/widgets/aooptionsdialog.h
+++ b/src/widgets/aooptionsdialog.h
@@ -125,7 +125,6 @@ private:
bool asset_cache_dirty = false;
- bool needsDefaultAudioDevice();
void populateAudioDevices();
void updateValues();