inLimbo
TUI Music Player that keeps you in Limbo.
 
Loading...
Searching...
No Matches
audio_playback.hpp
Go to the documentation of this file.
1#pragma once
2
3#define MINIAUDIO_IMPLEMENTATION
4#include "miniaudio.h"
5#include <chrono>
6#include <future>
7#include <iostream>
8#include <mutex>
9#include <stdexcept>
10#include <string>
11#include <thread>
12#include <vector>
13#include <sstream>
14
20{
21 std::string name;
22 ma_device_id id;
23};
24
35{
36private:
37 ma_engine engine;
38 ma_sound sound;
39 ma_device device;
40 ma_context context;
41 ma_engine_config engine_config;
42 ma_device_id deviceID;
43 ma_device_config deviceConfig;
44 ma_sound_config soundConfig;
45 bool isPlaying;
46 bool deviceSet;
47 bool wasPaused;
48 uint64_t pausePosition;
49 std::thread playbackThread,
50 audioDeviceThread;
51 std::mutex mtx, devicesMutex;
52 std::vector<AudioDevice> devices;
53
54public:
62 MiniAudioPlayer() : isPlaying(false), wasPaused(false), pausePosition(0), deviceSet(false)
63 {
64 engine_config = ma_engine_config_init();
65 if (ma_engine_init(&engine_config, &engine) != MA_SUCCESS)
66 {
67 throw std::runtime_error("Failed to initialize MiniAudio engine.");
68 }
69
70 deviceConfig = ma_device_config_init(ma_device_type_playback);
71 if (ma_context_init(nullptr, 0, nullptr, &context) != MA_SUCCESS)
72 {
73 throw std::runtime_error("Failed to initialize audio context.");
74 }
75
76 if (ma_device_init(nullptr, &deviceConfig, &device) != MA_SUCCESS)
77 {
78 throw std::runtime_error("Failed to initialize playback device.");
79 }
80
81 if (ma_device_start(&device) != MA_SUCCESS)
82 {
83 ma_device_uninit(&device);
84 throw std::runtime_error("Failed to start playback device.");
85 }
86 }
87
95 {
96 stop();
97 ma_device_uninit(&device);
98 ma_context_uninit(&context);
99 ma_engine_uninit(&engine);
100 }
101
102 auto enumerateDevices() -> std::vector<AudioDevice>
103 {
104 std::vector<AudioDevice> localDevices;
105
106 audioDeviceThread = std::thread(
107 [this, &localDevices]()
108 {
109 try
110 {
111 ma_device_info* playbackDevices;
112 ma_uint32 playbackDeviceCount;
113
114 // Enumerate devices
115 ma_result result = ma_context_get_devices(&context, &playbackDevices,
116 &playbackDeviceCount, nullptr, nullptr);
117 if (result != MA_SUCCESS)
118 {
119 throw std::runtime_error("Failed to enumerate playback devices.");
120 }
121
122 // Safely copy devices
123 {
124 std::lock_guard<std::mutex> lock(devicesMutex);
125 devices.clear();
126 for (ma_uint32 i = 0; i < playbackDeviceCount; ++i)
127 {
128 devices.push_back({playbackDevices[i].name, playbackDevices[i].id});
129 }
130 localDevices = devices;
131 }
132 }
133 catch (const std::exception& e)
134 {
135 std::cerr << "Error in enumerateDevices thread: " << e.what() << "\n";
136 }
137 });
138
139 if (audioDeviceThread.joinable())
140 audioDeviceThread.join(); // Wait for the thread to finish
141 return localDevices;
142 }
143
149 // This does not work will figure some way to fix this
150 /*void setDevice(const ma_device_id& id)*/
151 /*{*/
152 /* std::unique_lock<std::mutex> lock(mtx);*/
153 /**/
154 /* if (!deviceSet) {*/
155 /* std::cerr << "Device not initialized!\n";*/
156 /* return;*/
157 /* }*/
158 /**/
159 /* // Stop current playback*/
160 /* ma_device_stop(&device);*/
161 /**/
162 /* // Set new device*/
163 /* deviceID = id;*/
164 /* deviceConfig.playback.pDeviceID = &deviceID;*/
165 /**/
166 /* // Restart playback*/
167 /* if (ma_device_start(&device) != MA_SUCCESS) {*/
168 /* std::cerr << "Failed to restart audio device after switch.\n";*/
169 /* }*/
170 /*}*/
171
183 auto loadFileAsync(const std::string& filePath, bool reloadNextFile) -> std::future<int>
184 {
185 return std::async(std::launch::async,
186 [this, filePath, reloadNextFile]()
187 {
188 std::unique_lock<std::mutex> lock(mtx);
189
190 if (isPlaying)
191 {
192 stop();
193 }
194
195 // File check is done before loading the file so this seems like an
196 // unnecessary sanity check that can be removed
197 /*if (!std::filesystem::exists(filePath))*/
198 /*{*/
199 /* throw std::runtime_error("File does not exist: " + filePath);*/
200 /*}*/
201
202 if (reloadNextFile)
203 {
204 ma_sound_uninit(&sound);
205 }
206
207 if (deviceSet)
208 {
209 deviceConfig.playback.pDeviceID = &deviceID;
210 }
211
212 if (ma_sound_init_from_file(&engine, filePath.c_str(), MA_SOUND_FLAG_STREAM,
213 nullptr, nullptr, &sound) != MA_SUCCESS)
214 {
215 throw std::runtime_error("Failed to load audio file: " + filePath);
216 }
217
218 return 0; // Success
219 });
220 }
221
230 void play()
231 {
232 std::unique_lock<std::mutex> lock(mtx); // Protect shared state
233 deviceConfig.playback.pDeviceID = &deviceID;
234 if (!isPlaying)
235 {
236 if (ma_sound_start(&sound) != MA_SUCCESS)
237 {
238 throw std::runtime_error("Failed to play the sound.");
239 }
240 isPlaying = true;
241
242 if (playbackThread.joinable())
243 playbackThread.join();
244
245 // Start a new playback thread
246 playbackThread = std::thread(
247 [this]()
248 {
249 while (isPlaying)
250 {
251 if (!ma_sound_is_playing(&sound))
252 {
253 std::unique_lock<std::mutex> lock(mtx); // Protect shared state
254 isPlaying = false;
255 break;
256 }
257 std::this_thread::sleep_for(std::chrono::milliseconds(50));
258 }
259 });
260 }
261 mtx.unlock();
262 }
263
272 void pause()
273 {
274 std::unique_lock<std::mutex> lock(mtx); // Protect shared state
275 if (isPlaying)
276 {
277 // Store the current position before stopping
278 pausePosition = ma_sound_get_time_in_pcm_frames(&sound);
279
280 if (ma_sound_stop(&sound) != MA_SUCCESS)
281 {
282 throw std::runtime_error("Failed to pause the sound.");
283 }
284 isPlaying = false;
285 wasPaused = true;
286
287 if (playbackThread.joinable())
288 {
289 playbackThread.join();
290 }
291 }
292 else
293 {
294 throw std::runtime_error("No song is playing...");
295 }
296 }
297
304 void resume()
305 {
306 if (wasPaused)
307 {
308 // Seek to the stored position and start playing again
309 ma_sound_seek_to_pcm_frame(&sound, pausePosition);
310 play(); // Call play() to resume playback from the correct position
311 wasPaused = false;
312 }
313 }
314
321 void stop()
322 {
323 std::unique_lock<std::mutex> lock(mtx); // Protect shared state
324 if (isPlaying)
325 {
326 if (ma_sound_stop(&sound) != MA_SUCCESS)
327 {
328 throw std::runtime_error("Failed to stop the sound.");
329 }
330 isPlaying = false;
331 ma_sound_seek_to_pcm_frame(&sound, 0);
332 }
333
334 if (playbackThread.joinable())
335 {
336 playbackThread.join();
337 }
338 }
339
349 void setVolume(float volume)
350 {
351 if (volume < 0.0f || volume > 1.0f)
352 {
353 throw std::invalid_argument("Volume must be between 0.0 and 1.0.");
354 }
355 ma_sound_set_volume(&sound, volume);
356 }
357
365 [[nodiscard]] auto getVolume() const -> float { return ma_sound_get_volume(&sound); }
366
374 auto isCurrentlyPlaying() -> bool
375 {
376 std::unique_lock<std::mutex> lock(mtx); // Protect shared state
377 return isPlaying;
378 }
379
388 auto getDurationAsync() -> std::future<float>
389 {
390 return std::async(std::launch::async,
391 [this]()
392 {
393 std::unique_lock<std::mutex> lock(mtx); // Protect shared state
394 if (ma_sound_get_time_in_milliseconds(&sound) != MA_SUCCESS)
395 {
396 throw std::runtime_error("Failed to get sound duration.");
397 }
398
399 float duration = 0.0f;
400 ma_result result = ma_sound_get_length_in_seconds(&sound, &duration);
401 if (result != MA_SUCCESS)
402 {
403 throw std::runtime_error("Failed to get sound duration. Result: " +
404 std::to_string(result));
405 }
406
407 return duration;
408 });
409 }
410
419 auto seekTime(int seconds) -> double
420 {
421 if (isPlaying)
422 {
423 std::unique_lock<std::mutex> lock(mtx); // Protect shared state
424
425 // Get the sample rate of the sound
426 ma_uint32 sampleRate = ma_engine_get_sample_rate(&engine);
427
428 // Get the total length of the sound in PCM frames
429 ma_uint64 totalFrames;
430 ma_result result = ma_sound_get_length_in_pcm_frames(&sound, &totalFrames);
431 if (result != MA_SUCCESS)
432 {
433 std::cerr << "Failed to get total PCM frames." << std::endl;
434 }
435
436 // Get the current position in PCM frames
437 ma_uint64 currentFrames = ma_sound_get_time_in_pcm_frames(&sound);
438
439 // Calculate the new position in PCM frames
440 ma_int64 newFrames =
441 static_cast<ma_int64>(currentFrames) + static_cast<ma_int64>(seconds) * sampleRate;
442
443 // Clamp the new position to valid bounds
444 if (newFrames < 0)
445 newFrames = seconds = 0;
446 if (static_cast<ma_uint64>(newFrames) > totalFrames)
447 newFrames = totalFrames;
448
449 result = ma_sound_seek_to_pcm_frame(&sound, static_cast<ma_uint64>(newFrames));
450 if (result != MA_SUCCESS)
451 {
452 std::cerr << "Failed to seek sound." << std::endl;
453 }
454
455 return (double)seconds;
456 }
457 else
458 {
459 throw std::runtime_error("-- Audio is not playing, cannot seek time");
460 }
461 }
462
471 [[nodiscard]] auto getAudioPlaybackDetails() const -> std::vector<std::string>
472 {
473 std::vector<std::string> details;
474
475 // Get audio engine's sample rate
476 ma_uint32 sampleRate = ma_engine_get_sample_rate(&engine);
477 if (sampleRate == 0)
478 {
479 details.emplace_back("WARNING: Could not detect sample rate, using default 44100 Hz");
480 sampleRate = 44100;
481 }
482
483 // Get channels from the device config
484 ma_uint32 channels = deviceConfig.playback.channels;
485 if (channels == 0)
486 {
487 details.emplace_back("WARNING: Could not detect channels, using default stereo");
488 channels = 2;
489 }
490
491 // Get format from the device config
492 ma_format format = deviceConfig.playback.format;
493 if (format == ma_format_unknown)
494 {
495 details.emplace_back("WARNING: Could not detect format, using default f32");
496 format = ma_format_f32;
497 }
498
499 // Header
500 details.emplace_back("=== Audio Playback Details ===");
501
502 // Audio Configuration
503 details.emplace_back("\nAudio Configuration:");
504 {
505 std::ostringstream oss;
506 oss << " Sample Rate: " << sampleRate << " Hz";
507 details.emplace_back(oss.str());
508
509 oss.str("");
510 oss << " Channels: " << channels << " (" << getChannelLayoutString(channels) << ")";
511 details.emplace_back(oss.str());
512
513 oss.str("");
514 oss << " Format: " << getFormatString(format) << " (" << getBitsPerSample(format) << "-bit)";
515 details.emplace_back(oss.str());
516 }
517
518 // Playback State
519 details.emplace_back("\nPlayback State:");
520 {
521 std::ostringstream oss;
522 oss << " Status: " << getPlaybackStateString();
523 details.emplace_back(oss.str());
524 oss.str("");
525 oss << " Looping: " << (soundConfig.isLooping ? "Yes" : "No");
526 details.emplace_back(oss.str());
527 }
528
529 return details;
530 }
531
532 // Helper functions remain the same
533 [[nodiscard]] auto getChannelLayoutString(ma_uint32 channels) const -> std::string
534 {
535 switch (channels)
536 {
537 case 1:
538 return "Mono";
539 case 2:
540 return "Stereo";
541 case 3:
542 return "2.1";
543 case 4:
544 return "Quadraphonic";
545 case 5:
546 return "5.0";
547 case 6:
548 return "5.1";
549 case 7:
550 return "6.1";
551 case 8:
552 return "7.1";
553 default:
554 {
555 std::ostringstream oss;
556 oss << channels << " channels";
557 return oss.str();
558 }
559 }
560 }
561
562 [[nodiscard]] auto getFormatString(ma_format format) const -> std::string
563 {
564 switch (format)
565 {
566 case ma_format_f32:
567 return "32-bit float";
568 case ma_format_s16:
569 return "16-bit signed PCM";
570 case ma_format_u8:
571 return "8-bit unsigned PCM";
572 case ma_format_s24:
573 return "24-bit signed PCM";
574 case ma_format_s32:
575 return "32-bit signed PCM";
576 default:
577 return "Unknown format";
578 }
579 }
580
581 [[nodiscard]] auto getBitsPerSample(ma_format format) const -> uint32_t
582 {
583 switch (format)
584 {
585 case ma_format_f32:
586 return 32;
587 case ma_format_s16:
588 return 16;
589 case ma_format_u8:
590 return 8;
591 case ma_format_s24:
592 return 24;
593 case ma_format_s32:
594 return 32;
595 default:
596 return 0;
597 }
598 }
599
600 [[nodiscard]] auto getPlaybackStateString() const -> std::string
601 {
602 if (isPlaying)
603 return "Playing";
604 if (wasPaused)
605 return "Paused";
606 return "Stopped";
607 }
608};
auto enumerateDevices() -> std::vector< AudioDevice >
Definition audio_playback.hpp:102
auto getChannelLayoutString(ma_uint32 channels) const -> std::string
Definition audio_playback.hpp:533
auto getPlaybackStateString() const -> std::string
Definition audio_playback.hpp:600
auto isCurrentlyPlaying() -> bool
Checks if the sound is currently playing.
Definition audio_playback.hpp:374
auto loadFileAsync(const std::string &filePath, bool reloadNextFile) -> std::future< int >
Sets the device ID for playback.
Definition audio_playback.hpp:183
auto seekTime(int seconds) -> double
Seeks to a specific time in the audio (in seconds).
Definition audio_playback.hpp:419
void resume()
Resumes playback from the last paused position.
Definition audio_playback.hpp:304
auto getBitsPerSample(ma_format format) const -> uint32_t
Definition audio_playback.hpp:581
void stop()
Stops the playback and resets the playback position.
Definition audio_playback.hpp:321
auto getDurationAsync() -> std::future< float >
Gets the total duration of the sound in seconds. (async function)
Definition audio_playback.hpp:388
auto getFormatString(ma_format format) const -> std::string
Definition audio_playback.hpp:562
void pause()
Pauses the current audio playback.
Definition audio_playback.hpp:272
void setVolume(float volume)
Sets the volume of the audio playback.
Definition audio_playback.hpp:349
auto getVolume() const -> float
Gets the current volume of the audio playback.
Definition audio_playback.hpp:365
MiniAudioPlayer()
Constructs a MiniAudioPlayer object and initializes the MiniAudio engine.
Definition audio_playback.hpp:62
~MiniAudioPlayer()
Destroys the MiniAudioPlayer object, stopping any playback and cleaning up resources.
Definition audio_playback.hpp:94
void play()
Starts playback of the loaded audio file.
Definition audio_playback.hpp:230
auto getAudioPlaybackDetails() const -> std::vector< std::string >
Gets detailed information about the current audio playback configuration.
Definition audio_playback.hpp:471
A structure representing an audio playback device.
Definition audio_playback.hpp:20
std::string name
Definition audio_playback.hpp:21
ma_device_id id
Definition audio_playback.hpp:22