2019-08-19 23:08:54 +00:00
|
|
|
#include "lib/audiodev/AudioVoiceEngine.hpp"
|
|
|
|
|
|
|
|
#include <cstdio>
|
|
|
|
|
2016-07-06 21:29:06 +00:00
|
|
|
#include "boo/audiodev/IAudioVoiceEngine.hpp"
|
2019-08-19 23:08:54 +00:00
|
|
|
#include <logvisor/logvisor.hpp>
|
2021-04-04 11:05:17 +00:00
|
|
|
#include <optick.h>
|
2016-07-06 21:29:06 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
namespace boo {
|
2018-09-04 00:14:59 +00:00
|
|
|
|
2016-07-06 21:29:06 +00:00
|
|
|
static logvisor::Module Log("boo::WAVOut");
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
struct WAVOutVoiceEngine : BaseAudioVoiceEngine {
|
|
|
|
std::vector<float> m_interleavedBuf;
|
2016-07-06 21:29:06 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
AudioChannelSet _getAvailableSet() { return AudioChannelSet::Stereo; }
|
2018-08-18 22:08:58 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::string getCurrentAudioOutput() const override { return "wavout"; }
|
2018-08-18 22:08:58 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
bool setCurrentAudioOutput(const char* name) override { return false; }
|
2018-08-18 22:08:58 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::vector<std::pair<std::string, std::string>> enumerateAudioOutputs() const override {
|
|
|
|
return {{"wavout", "WAVOut"}};
|
|
|
|
}
|
2016-07-06 21:29:06 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::vector<std::pair<std::string, std::string>> enumerateMIDIInputs() const override { return {}; }
|
2018-08-19 00:28:00 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
bool supportsVirtualMIDIIn() const override { return false; }
|
2016-07-06 21:29:06 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
ReceiveFunctor* m_midiReceiver = nullptr;
|
2016-07-06 21:29:06 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
struct MIDIIn : public IMIDIIn {
|
|
|
|
MIDIIn(WAVOutVoiceEngine* parent, bool virt, ReceiveFunctor&& receiver)
|
|
|
|
: IMIDIIn(parent, virt, std::move(receiver)) {}
|
2016-07-06 21:29:06 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::string description() const override { return "WAVOut MIDI"; }
|
2018-12-08 05:17:51 +00:00
|
|
|
};
|
2016-07-06 21:29:06 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::unique_ptr<IMIDIIn> newVirtualMIDIIn(ReceiveFunctor&& receiver) override {
|
2018-12-08 05:17:51 +00:00
|
|
|
std::unique_ptr<IMIDIIn> ret = std::make_unique<MIDIIn>(nullptr, true, std::move(receiver));
|
|
|
|
m_midiReceiver = &ret->m_receiver;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::unique_ptr<IMIDIOut> newVirtualMIDIOut() override { return {}; }
|
2018-12-08 05:17:51 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::unique_ptr<IMIDIInOut> newVirtualMIDIInOut(ReceiveFunctor&& receiver) override { return {}; }
|
2018-12-08 05:17:51 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::unique_ptr<IMIDIIn> newRealMIDIIn(const char* name, ReceiveFunctor&& receiver) override { return {}; }
|
2018-12-08 05:17:51 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::unique_ptr<IMIDIOut> newRealMIDIOut(const char* name) override { return {}; }
|
2018-12-08 05:17:51 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::unique_ptr<IMIDIInOut> newRealMIDIInOut(const char* name, ReceiveFunctor&& receiver) override { return {}; }
|
2018-12-08 05:17:51 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
bool useMIDILock() const override { return false; }
|
2018-12-08 05:17:51 +00:00
|
|
|
|
|
|
|
FILE* m_fp = nullptr;
|
|
|
|
size_t m_bytesWritten = 0;
|
|
|
|
|
|
|
|
void prepareWAV(double sampleRate, int numChans) {
|
|
|
|
uint32_t speakerMask = 0;
|
|
|
|
|
|
|
|
switch (numChans) {
|
|
|
|
default:
|
|
|
|
case 2:
|
|
|
|
numChans = 2;
|
|
|
|
m_mixInfo.m_channels = AudioChannelSet::Stereo;
|
|
|
|
m_mixInfo.m_channelMap.m_channelCount = 2;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[0] = AudioChannel::FrontLeft;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[1] = AudioChannel::FrontRight;
|
|
|
|
speakerMask = 0x00000001 | 0x00000002;
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
numChans = 4;
|
|
|
|
m_mixInfo.m_channels = AudioChannelSet::Quad;
|
|
|
|
m_mixInfo.m_channelMap.m_channelCount = 4;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[0] = AudioChannel::FrontLeft;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[1] = AudioChannel::FrontRight;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[2] = AudioChannel::RearLeft;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[3] = AudioChannel::RearRight;
|
|
|
|
speakerMask = 0x00000001 | 0x00000002 | 0x00000010 | 0x00000020;
|
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
numChans = 6;
|
|
|
|
m_mixInfo.m_channels = AudioChannelSet::Surround51;
|
|
|
|
m_mixInfo.m_channelMap.m_channelCount = 6;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[0] = AudioChannel::FrontLeft;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[1] = AudioChannel::FrontRight;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[2] = AudioChannel::FrontCenter;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[3] = AudioChannel::LFE;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[4] = AudioChannel::RearLeft;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[5] = AudioChannel::RearRight;
|
|
|
|
speakerMask = 0x00000001 | 0x00000002 | 0x00000004 | 0x00000008 | 0x00000010 | 0x00000020;
|
|
|
|
break;
|
|
|
|
case 8:
|
|
|
|
numChans = 8;
|
|
|
|
m_mixInfo.m_channels = AudioChannelSet::Surround71;
|
|
|
|
m_mixInfo.m_channelMap.m_channelCount = 8;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[0] = AudioChannel::FrontLeft;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[1] = AudioChannel::FrontRight;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[2] = AudioChannel::FrontCenter;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[3] = AudioChannel::LFE;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[4] = AudioChannel::RearLeft;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[5] = AudioChannel::RearRight;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[6] = AudioChannel::SideLeft;
|
|
|
|
m_mixInfo.m_channelMap.m_channels[7] = AudioChannel::SideRight;
|
|
|
|
speakerMask =
|
|
|
|
0x00000001 | 0x00000002 | 0x00000004 | 0x00000008 | 0x00000010 | 0x00000020 | 0x00000200 | 0x00000400;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (numChans == 2) {
|
|
|
|
fwrite("RIFF", 1, 4, m_fp);
|
|
|
|
uint32_t dataSize = 0;
|
|
|
|
uint32_t chunkSize = 36 + dataSize;
|
|
|
|
fwrite(&chunkSize, 1, 4, m_fp);
|
|
|
|
|
|
|
|
fwrite("WAVE", 1, 4, m_fp);
|
|
|
|
|
|
|
|
fwrite("fmt ", 1, 4, m_fp);
|
|
|
|
uint32_t sixteen = 16;
|
|
|
|
fwrite(&sixteen, 1, 4, m_fp);
|
|
|
|
uint16_t audioFmt = 3;
|
|
|
|
fwrite(&audioFmt, 1, 2, m_fp);
|
|
|
|
uint16_t chCount = numChans;
|
|
|
|
fwrite(&chCount, 1, 2, m_fp);
|
|
|
|
uint32_t sampRate = sampleRate;
|
|
|
|
fwrite(&sampRate, 1, 4, m_fp);
|
|
|
|
uint16_t blockAlign = 4 * numChans;
|
|
|
|
uint32_t byteRate = sampRate * blockAlign;
|
|
|
|
fwrite(&byteRate, 1, 4, m_fp);
|
|
|
|
fwrite(&blockAlign, 1, 2, m_fp);
|
|
|
|
uint16_t bps = 32;
|
|
|
|
fwrite(&bps, 1, 2, m_fp);
|
|
|
|
|
|
|
|
fwrite("data", 1, 4, m_fp);
|
|
|
|
fwrite(&dataSize, 1, 4, m_fp);
|
|
|
|
} else {
|
|
|
|
fwrite("RIFF", 1, 4, m_fp);
|
|
|
|
uint32_t dataSize = 0;
|
|
|
|
uint32_t chunkSize = 60 + dataSize;
|
|
|
|
fwrite(&chunkSize, 1, 4, m_fp);
|
|
|
|
|
|
|
|
fwrite("WAVE", 1, 4, m_fp);
|
|
|
|
|
|
|
|
fwrite("fmt ", 1, 4, m_fp);
|
|
|
|
uint32_t forty = 40;
|
|
|
|
fwrite(&forty, 1, 4, m_fp);
|
|
|
|
uint16_t audioFmt = 0xFFFE;
|
|
|
|
fwrite(&audioFmt, 1, 2, m_fp);
|
|
|
|
uint16_t chCount = numChans;
|
|
|
|
fwrite(&chCount, 1, 2, m_fp);
|
|
|
|
uint32_t sampRate = sampleRate;
|
|
|
|
fwrite(&sampRate, 1, 4, m_fp);
|
|
|
|
uint16_t blockAlign = 4 * numChans;
|
|
|
|
uint32_t byteRate = sampRate * blockAlign;
|
|
|
|
fwrite(&byteRate, 1, 4, m_fp);
|
|
|
|
fwrite(&blockAlign, 1, 2, m_fp);
|
|
|
|
uint16_t bps = 32;
|
|
|
|
fwrite(&bps, 1, 2, m_fp);
|
|
|
|
uint16_t extSize = 22;
|
|
|
|
fwrite(&extSize, 1, 2, m_fp);
|
|
|
|
fwrite(&bps, 1, 2, m_fp);
|
|
|
|
fwrite(&speakerMask, 1, 4, m_fp);
|
|
|
|
fwrite("\x03\x00\x00\x00\x00\x00\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71", 1, 16, m_fp);
|
|
|
|
|
|
|
|
fwrite("data", 1, 4, m_fp);
|
|
|
|
fwrite(&dataSize, 1, 4, m_fp);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_mixInfo.m_periodFrames = 512;
|
|
|
|
m_mixInfo.m_sampleRate = sampleRate;
|
|
|
|
m_mixInfo.m_sampleFormat = SOXR_FLOAT32_I;
|
|
|
|
m_mixInfo.m_bitsPerSample = 32;
|
|
|
|
_buildAudioRenderClient();
|
|
|
|
}
|
|
|
|
|
|
|
|
WAVOutVoiceEngine(const char* path, double sampleRate, int numChans) {
|
|
|
|
m_fp = fopen(path, "wb");
|
|
|
|
if (!m_fp)
|
|
|
|
return;
|
|
|
|
prepareWAV(sampleRate, numChans);
|
|
|
|
}
|
2016-07-06 21:29:06 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
#if _WIN32
|
|
|
|
WAVOutVoiceEngine(const wchar_t* path, double sampleRate, int numChans) {
|
|
|
|
m_fp = _wfopen(path, L"wb");
|
|
|
|
if (!m_fp)
|
|
|
|
return;
|
|
|
|
prepareWAV(sampleRate, numChans);
|
|
|
|
}
|
|
|
|
#endif
|
2016-07-06 21:29:06 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
void finishWav() {
|
|
|
|
uint32_t dataSize = m_bytesWritten;
|
2016-07-06 21:29:06 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
if (m_mixInfo.m_channelMap.m_channelCount == 2) {
|
|
|
|
fseek(m_fp, 4, SEEK_SET);
|
|
|
|
uint32_t chunkSize = 36 + dataSize;
|
|
|
|
fwrite(&chunkSize, 1, 4, m_fp);
|
2016-07-06 21:29:06 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
fseek(m_fp, 40, SEEK_SET);
|
|
|
|
fwrite(&dataSize, 1, 4, m_fp);
|
|
|
|
} else {
|
|
|
|
fseek(m_fp, 4, SEEK_SET);
|
|
|
|
uint32_t chunkSize = 60 + dataSize;
|
|
|
|
fwrite(&chunkSize, 1, 4, m_fp);
|
2016-07-06 21:29:06 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
fseek(m_fp, 64, SEEK_SET);
|
|
|
|
fwrite(&dataSize, 1, 4, m_fp);
|
2016-07-06 21:29:06 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
fclose(m_fp);
|
|
|
|
}
|
2016-07-06 21:29:06 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
~WAVOutVoiceEngine() override { finishWav(); }
|
2016-07-06 21:29:06 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
void _buildAudioRenderClient() {
|
|
|
|
m_5msFrames = m_mixInfo.m_sampleRate * 5 / 1000;
|
|
|
|
m_interleavedBuf.resize(m_mixInfo.m_channelMap.m_channelCount * m_5msFrames);
|
|
|
|
}
|
2016-07-06 21:29:06 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
void _rebuildAudioRenderClient(double sampleRate, size_t periodFrames) {
|
|
|
|
m_mixInfo.m_periodFrames = periodFrames;
|
|
|
|
m_mixInfo.m_sampleRate = sampleRate;
|
|
|
|
_buildAudioRenderClient();
|
|
|
|
_resetSampleRate();
|
|
|
|
}
|
2016-07-06 21:29:06 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void pumpAndMixVoices() override {
|
2021-04-04 11:05:17 +00:00
|
|
|
OPTICK_EVENT();
|
2018-12-08 05:17:51 +00:00
|
|
|
size_t frameSz = 4 * m_mixInfo.m_channelMap.m_channelCount;
|
|
|
|
_pumpAndMixVoices(m_5msFrames, m_interleavedBuf.data());
|
|
|
|
fwrite(m_interleavedBuf.data(), 1, m_5msFrames * frameSz, m_fp);
|
|
|
|
m_bytesWritten += m_5msFrames * frameSz;
|
|
|
|
}
|
2016-07-06 21:29:06 +00:00
|
|
|
};
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
std::unique_ptr<IAudioVoiceEngine> NewWAVAudioVoiceEngine(const char* path, double sampleRate, int numChans) {
|
|
|
|
std::unique_ptr<IAudioVoiceEngine> ret = std::make_unique<WAVOutVoiceEngine>(path, sampleRate, numChans);
|
|
|
|
if (!static_cast<WAVOutVoiceEngine&>(*ret).m_fp)
|
|
|
|
return {};
|
|
|
|
return ret;
|
2016-07-06 21:29:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#if _WIN32
|
2018-12-08 05:17:51 +00:00
|
|
|
std::unique_ptr<IAudioVoiceEngine> NewWAVAudioVoiceEngine(const wchar_t* path, double sampleRate, int numChans) {
|
|
|
|
std::unique_ptr<IAudioVoiceEngine> ret = std::make_unique<WAVOutVoiceEngine>(path, sampleRate, numChans);
|
|
|
|
if (!static_cast<WAVOutVoiceEngine&>(*ret).m_fp)
|
|
|
|
return {};
|
|
|
|
return ret;
|
2016-07-06 21:29:06 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
} // namespace boo
|