|
| 1 | +// The miniaudio API through the installed wrapper, without an audio device. |
| 2 | +import std; |
| 3 | +import mcpplibs.mwrap.miniaudio; |
| 4 | + |
| 5 | +using namespace mcpplibs::mwrap::miniaudio::macros; |
| 6 | + |
| 7 | +namespace { |
| 8 | +float peak(std::span<const float> s) { |
| 9 | + float p = 0; |
| 10 | + for (float v : s) p = std::max(p, std::abs(v)); |
| 11 | + return p; |
| 12 | +} |
| 13 | +} // namespace |
| 14 | + |
| 15 | +int main() { |
| 16 | + if (std::string_view(ma_version_string()) != MA_VERSION_STRING || MA_VERSION_MINOR != 11) return 1; |
| 17 | + |
| 18 | + // The engine mixes into a caller's buffer when it has no device; its |
| 19 | + // resource manager runs without job threads. |
| 20 | + ma_resource_manager_config rmConfig = ma_resource_manager_config_init(); |
| 21 | + rmConfig.flags = MA_RESOURCE_MANAGER_FLAG_NO_THREADING; |
| 22 | + rmConfig.jobThreadCount = 0; |
| 23 | + ma_resource_manager rm; |
| 24 | + if (ma_resource_manager_init(&rmConfig, &rm) != MA_SUCCESS) return 2; |
| 25 | + ma_engine_config config = ma_engine_config_init(); |
| 26 | + config.noDevice = MA_TRUE; |
| 27 | + config.channels = 2; |
| 28 | + config.sampleRate = 48000; |
| 29 | + config.pResourceManager = &rm; |
| 30 | + ma_engine engine; |
| 31 | + if (ma_engine_init(&config, &engine) != MA_SUCCESS) return 3; |
| 32 | + const ma_waveform_config wc = ma_waveform_config_init(ma_format_f32, 2, 48000, ma_waveform_type_square, 0.5, 100); |
| 33 | + ma_waveform wave; |
| 34 | + ma_waveform_init(&wc, &wave); |
| 35 | + ma_sound_group group; |
| 36 | + ma_sound_group_init(&engine, 0, nullptr, &group); |
| 37 | + ma_sound sound; |
| 38 | + if (ma_sound_init_from_data_source(&engine, &wave, MA_SOUND_FLAG_NO_SPATIALIZATION, &group, &sound) != MA_SUCCESS) return 4; |
| 39 | + ma_sound_start(&sound); |
| 40 | + std::vector<float> mix(2 * 480); |
| 41 | + ma_uint64 read = 0; |
| 42 | + ma_engine_read_pcm_frames(&engine, mix.data(), 480, &read); |
| 43 | + if (read != 480 || std::abs(peak(mix) - 0.5f) > 1e-3f) return 5; |
| 44 | + ma_sound_group_set_volume(&group, 0.5f); |
| 45 | + ma_engine_read_pcm_frames(&engine, mix.data(), 480, &read); |
| 46 | + if (std::abs(peak(mix) - 0.25f) > 1e-3f) return 6; |
| 47 | + ma_sound_uninit(&sound); |
| 48 | + ma_sound_group_uninit(&group); |
| 49 | + ma_waveform_uninit(&wave); |
| 50 | + ma_engine_uninit(&engine); |
| 51 | + ma_resource_manager_uninit(&rm); |
| 52 | + |
| 53 | + // A tone encoded as WAV in memory and decoded back, sample for sample. |
| 54 | + struct Sink { |
| 55 | + std::vector<unsigned char> bytes; |
| 56 | + std::size_t at = 0; |
| 57 | + } sink; |
| 58 | + const auto write = [](ma_encoder* e, const void* data, std::size_t n, std::size_t* written) -> ma_result { |
| 59 | + auto& s = *static_cast<Sink*>(e->pUserData); |
| 60 | + if (s.bytes.size() < s.at + n) s.bytes.resize(s.at + n); |
| 61 | + std::memcpy(s.bytes.data() + s.at, data, n); |
| 62 | + s.at += n; |
| 63 | + *written = n; |
| 64 | + return MA_SUCCESS; |
| 65 | + }; |
| 66 | + const auto seek = [](ma_encoder* e, ma_int64 offset, ma_seek_origin origin) -> ma_result { |
| 67 | + auto& s = *static_cast<Sink*>(e->pUserData); |
| 68 | + s.at = static_cast<std::size_t>((origin == ma_seek_origin_start ? 0 : static_cast<ma_int64>(s.at)) + offset); |
| 69 | + return MA_SUCCESS; |
| 70 | + }; |
| 71 | + std::vector<ma_int16> pcm(4410); |
| 72 | + const ma_waveform_config tc = ma_waveform_config_init(ma_format_s16, 1, 44100, ma_waveform_type_sine, 0.5, 440); |
| 73 | + ma_waveform tone; |
| 74 | + ma_waveform_init(&tc, &tone); |
| 75 | + ma_waveform_read_pcm_frames(&tone, pcm.data(), pcm.size(), nullptr); |
| 76 | + const ma_encoder_config ec = ma_encoder_config_init(ma_encoding_format_wav, ma_format_s16, 1, 44100); |
| 77 | + ma_encoder encoder; |
| 78 | + if (ma_encoder_init(write, seek, &sink, &ec, &encoder) != MA_SUCCESS) return 7; |
| 79 | + ma_encoder_write_pcm_frames(&encoder, pcm.data(), pcm.size(), nullptr); |
| 80 | + ma_encoder_uninit(&encoder); |
| 81 | + const ma_decoder_config dc = ma_decoder_config_init(ma_format_s16, 0, 0); |
| 82 | + ma_decoder decoder; |
| 83 | + if (ma_decoder_init_memory(sink.bytes.data(), sink.bytes.size(), &dc, &decoder) != MA_SUCCESS) return 8; |
| 84 | + std::vector<ma_int16> back(pcm.size()); |
| 85 | + ma_decoder_read_pcm_frames(&decoder, back.data(), back.size(), &read); |
| 86 | + const ma_uint32 rate = decoder.outputSampleRate; |
| 87 | + ma_decoder_uninit(&decoder); |
| 88 | + if (read != pcm.size() || back != pcm || rate != 44100) return 9; |
| 89 | + |
| 90 | + // A device on the Null backend runs in real time without hardware. |
| 91 | + ma_backend null = ma_backend_null; |
| 92 | + ma_context context; |
| 93 | + if (ma_context_init(&null, 1, nullptr, &context) != MA_SUCCESS) return 10; |
| 94 | + ma_device_config dconfig = ma_device_config_init(ma_device_type_playback); |
| 95 | + dconfig.playback.format = ma_format_f32; |
| 96 | + dconfig.playback.channels = 2; |
| 97 | + dconfig.sampleRate = 48000; |
| 98 | + static std::atomic<ma_uint64> frames{0}; |
| 99 | + dconfig.dataCallback = [](ma_device*, void* out, const void*, ma_uint32 n) { |
| 100 | + std::memset(out, 0, n * 2 * sizeof(float)); |
| 101 | + frames += n; |
| 102 | + }; |
| 103 | + ma_device device; |
| 104 | + if (ma_device_init(&context, &dconfig, &device) != MA_SUCCESS || ma_device_start(&device) != MA_SUCCESS) return 11; |
| 105 | + for (int i = 0; i < 1000 && frames < 2400; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(5)); |
| 106 | + ma_device_uninit(&device); |
| 107 | + ma_context_uninit(&context); |
| 108 | + if (frames < 2400) return 12; |
| 109 | + |
| 110 | + // The adapted inline helper and a constant at compile time. |
| 111 | + static_assert(MA_MAX_CHANNELS == 254 && MA_ENGINE_MAX_LISTENERS == 4); |
| 112 | + return mcpplibs::mwrap::miniaudio::ma_get_bytes_per_frame(ma_format_s16, 2) == 4 ? 0 : 13; |
| 113 | +} |
0 commit comments