Multichannel WAVOut

This commit is contained in:
Jack Andersen 2018-09-03 14:14:59 -10:00
parent b624a993dd
commit 08d632a8bd
2 changed files with 158 additions and 68 deletions

View File

@ -108,9 +108,9 @@ struct IAudioVoiceEngine
std::unique_ptr<IAudioVoiceEngine> NewAudioVoiceEngine(); std::unique_ptr<IAudioVoiceEngine> NewAudioVoiceEngine();
/** Construct WAV-rendering voice engine */ /** Construct WAV-rendering voice engine */
std::unique_ptr<IAudioVoiceEngine> NewWAVAudioVoiceEngine(const char* path, double sampleRate); std::unique_ptr<IAudioVoiceEngine> NewWAVAudioVoiceEngine(const char* path, double sampleRate, int numChans);
#if _WIN32 #if _WIN32
std::unique_ptr<IAudioVoiceEngine> NewWAVAudioVoiceEngine(const wchar_t* path, double sampleRate); std::unique_ptr<IAudioVoiceEngine> NewWAVAudioVoiceEngine(const wchar_t* path, double sampleRate, int numChans);
#endif #endif
} }

View File

@ -2,15 +2,18 @@
#include "logvisor/logvisor.hpp" #include "logvisor/logvisor.hpp"
#include "boo/audiodev/IAudioVoiceEngine.hpp" #include "boo/audiodev/IAudioVoiceEngine.hpp"
namespace boo
{
static logvisor::Module Log("boo::WAVOut"); static logvisor::Module Log("boo::WAVOut");
struct WAVOutVoiceEngine : boo::BaseAudioVoiceEngine struct WAVOutVoiceEngine : BaseAudioVoiceEngine
{ {
std::vector<float> m_interleavedBuf; std::vector<float> m_interleavedBuf;
boo::AudioChannelSet _getAvailableSet() AudioChannelSet _getAvailableSet()
{ {
return boo::AudioChannelSet::Stereo; return AudioChannelSet::Stereo;
} }
std::string getCurrentAudioOutput() const std::string getCurrentAudioOutput() const
@ -38,11 +41,11 @@ struct WAVOutVoiceEngine : boo::BaseAudioVoiceEngine
return false; return false;
} }
boo::ReceiveFunctor* m_midiReceiver = nullptr; ReceiveFunctor* m_midiReceiver = nullptr;
struct MIDIIn : public boo::IMIDIIn struct MIDIIn : public IMIDIIn
{ {
MIDIIn(WAVOutVoiceEngine* parent, bool virt, boo::ReceiveFunctor&& receiver) MIDIIn(WAVOutVoiceEngine* parent, bool virt, ReceiveFunctor&& receiver)
: IMIDIIn(parent, virt, std::move(receiver)) {} : IMIDIIn(parent, virt, std::move(receiver)) {}
std::string description() const std::string description() const
@ -51,34 +54,34 @@ struct WAVOutVoiceEngine : boo::BaseAudioVoiceEngine
} }
}; };
std::unique_ptr<boo::IMIDIIn> newVirtualMIDIIn(boo::ReceiveFunctor&& receiver) std::unique_ptr<IMIDIIn> newVirtualMIDIIn(ReceiveFunctor&& receiver)
{ {
std::unique_ptr<boo::IMIDIIn> ret = std::make_unique<MIDIIn>(nullptr, true, std::move(receiver)); std::unique_ptr<IMIDIIn> ret = std::make_unique<MIDIIn>(nullptr, true, std::move(receiver));
m_midiReceiver = &ret->m_receiver; m_midiReceiver = &ret->m_receiver;
return ret; return ret;
} }
std::unique_ptr<boo::IMIDIOut> newVirtualMIDIOut() std::unique_ptr<IMIDIOut> newVirtualMIDIOut()
{ {
return {}; return {};
} }
std::unique_ptr<boo::IMIDIInOut> newVirtualMIDIInOut(boo::ReceiveFunctor&& receiver) std::unique_ptr<IMIDIInOut> newVirtualMIDIInOut(ReceiveFunctor&& receiver)
{ {
return {}; return {};
} }
std::unique_ptr<boo::IMIDIIn> newRealMIDIIn(const char* name, boo::ReceiveFunctor&& receiver) std::unique_ptr<IMIDIIn> newRealMIDIIn(const char* name, ReceiveFunctor&& receiver)
{ {
return {}; return {};
} }
std::unique_ptr<boo::IMIDIOut> newRealMIDIOut(const char* name) std::unique_ptr<IMIDIOut> newRealMIDIOut(const char* name)
{ {
return {}; return {};
} }
std::unique_ptr<boo::IMIDIInOut> newRealMIDIInOut(const char* name, boo::ReceiveFunctor&& receiver) std::unique_ptr<IMIDIInOut> newRealMIDIInOut(const char* name, ReceiveFunctor&& receiver)
{ {
return {}; return {};
} }
@ -88,32 +91,120 @@ struct WAVOutVoiceEngine : boo::BaseAudioVoiceEngine
FILE* m_fp = nullptr; FILE* m_fp = nullptr;
size_t m_bytesWritten = 0; size_t m_bytesWritten = 0;
void prepareWAV(double sampleRate) void prepareWAV(double sampleRate, int numChans)
{ {
fwrite("RIFF", 1, 4, m_fp); uint32_t speakerMask = 0;
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); switch (numChans)
uint32_t sixteen = 16; {
fwrite(&sixteen, 1, 4, m_fp); default:
uint16_t audioFmt = 3; case 2:
fwrite(&audioFmt, 1, 2, m_fp); numChans = 2;
uint16_t chCount = 2; m_mixInfo.m_channels = AudioChannelSet::Stereo;
fwrite(&chCount, 1, 2, m_fp); m_mixInfo.m_channelMap.m_channelCount = 2;
uint32_t sampRate = sampleRate; m_mixInfo.m_channelMap.m_channels[0] = AudioChannel::FrontLeft;
fwrite(&sampRate, 1, 4, m_fp); m_mixInfo.m_channelMap.m_channels[1] = AudioChannel::FrontRight;
uint16_t blockAlign = 8; speakerMask = 0x00000001 | 0x00000002;
uint32_t byteRate = sampRate * blockAlign; break;
fwrite(&byteRate, 1, 4, m_fp); case 4:
fwrite(&blockAlign, 1, 2, m_fp); numChans = 4;
uint16_t bps = 32; m_mixInfo.m_channels = AudioChannelSet::Quad;
fwrite(&bps, 1, 2, m_fp); 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;
}
fwrite("data", 1, 4, m_fp); if (numChans == 2)
fwrite(&dataSize, 1, 4, m_fp); {
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_periodFrames = 512;
m_mixInfo.m_sampleRate = sampleRate; m_mixInfo.m_sampleRate = sampleRate;
@ -122,21 +213,21 @@ struct WAVOutVoiceEngine : boo::BaseAudioVoiceEngine
_buildAudioRenderClient(); _buildAudioRenderClient();
} }
WAVOutVoiceEngine(const char* path, double sampleRate) WAVOutVoiceEngine(const char* path, double sampleRate, int numChans)
{ {
m_fp = fopen(path, "wb"); m_fp = fopen(path, "wb");
if (!m_fp) if (!m_fp)
return; return;
prepareWAV(sampleRate); prepareWAV(sampleRate, numChans);
} }
#if _WIN32 #if _WIN32
WAVOutVoiceEngine(const wchar_t* path, double sampleRate) WAVOutVoiceEngine(const wchar_t* path, double sampleRate, int numChans)
{ {
m_fp = _wfopen(path, L"wb"); m_fp = _wfopen(path, L"wb");
if (!m_fp) if (!m_fp)
return; return;
prepareWAV(sampleRate); prepareWAV(sampleRate, numChans);
} }
#endif #endif
@ -144,12 +235,24 @@ struct WAVOutVoiceEngine : boo::BaseAudioVoiceEngine
{ {
uint32_t dataSize = m_bytesWritten; uint32_t dataSize = m_bytesWritten;
fseek(m_fp, 4, SEEK_SET); if (m_mixInfo.m_channelMap.m_channelCount == 2)
uint32_t chunkSize = 36 + dataSize; {
fwrite(&chunkSize, 1, 4, m_fp); fseek(m_fp, 4, SEEK_SET);
uint32_t chunkSize = 36 + dataSize;
fwrite(&chunkSize, 1, 4, m_fp);
fseek(m_fp, 40, SEEK_SET); fseek(m_fp, 40, SEEK_SET);
fwrite(&dataSize, 1, 4, m_fp); fwrite(&dataSize, 1, 4, m_fp);
}
else
{
fseek(m_fp, 4, SEEK_SET);
uint32_t chunkSize = 60 + dataSize;
fwrite(&chunkSize, 1, 4, m_fp);
fseek(m_fp, 64, SEEK_SET);
fwrite(&dataSize, 1, 4, m_fp);
}
fclose(m_fp); fclose(m_fp);
} }
@ -161,19 +264,8 @@ struct WAVOutVoiceEngine : boo::BaseAudioVoiceEngine
void _buildAudioRenderClient() void _buildAudioRenderClient()
{ {
m_mixInfo.m_channels = _getAvailableSet();
unsigned chCount = ChannelCount(m_mixInfo.m_channels);
m_5msFrames = m_mixInfo.m_sampleRate * 5 / 1000; m_5msFrames = m_mixInfo.m_sampleRate * 5 / 1000;
m_interleavedBuf.resize(2 * m_5msFrames); m_interleavedBuf.resize(m_mixInfo.m_channelMap.m_channelCount * m_5msFrames);
boo::ChannelMap& chMapOut = m_mixInfo.m_channelMap;
chMapOut.m_channelCount = 2;
chMapOut.m_channels[0] = boo::AudioChannel::FrontLeft;
chMapOut.m_channels[1] = boo::AudioChannel::FrontRight;
while (chMapOut.m_channelCount < chCount)
chMapOut.m_channels[chMapOut.m_channelCount++] = boo::AudioChannel::Unknown;
} }
void _rebuildAudioRenderClient(double sampleRate, size_t periodFrames) void _rebuildAudioRenderClient(double sampleRate, size_t periodFrames)
@ -186,27 +278,25 @@ struct WAVOutVoiceEngine : boo::BaseAudioVoiceEngine
void pumpAndMixVoices() void pumpAndMixVoices()
{ {
size_t frameSz = 4 * m_mixInfo.m_channelMap.m_channelCount;
_pumpAndMixVoices(m_5msFrames, m_interleavedBuf.data()); _pumpAndMixVoices(m_5msFrames, m_interleavedBuf.data());
fwrite(m_interleavedBuf.data(), 1, m_5msFrames * 8, m_fp); fwrite(m_interleavedBuf.data(), 1, m_5msFrames * frameSz, m_fp);
m_bytesWritten += m_5msFrames * 8; m_bytesWritten += m_5msFrames * frameSz;
} }
}; };
namespace boo std::unique_ptr<IAudioVoiceEngine> NewWAVAudioVoiceEngine(const char* path, double sampleRate, int numChans)
{ {
std::unique_ptr<IAudioVoiceEngine> ret = std::make_unique<WAVOutVoiceEngine>(path, sampleRate, numChans);
std::unique_ptr<IAudioVoiceEngine> NewWAVAudioVoiceEngine(const char* path, double sampleRate)
{
std::unique_ptr<IAudioVoiceEngine> ret = std::make_unique<WAVOutVoiceEngine>(path, sampleRate);
if (!static_cast<WAVOutVoiceEngine&>(*ret).m_fp) if (!static_cast<WAVOutVoiceEngine&>(*ret).m_fp)
return {}; return {};
return ret; return ret;
} }
#if _WIN32 #if _WIN32
std::unique_ptr<IAudioVoiceEngine> NewWAVAudioVoiceEngine(const wchar_t* path, double sampleRate) std::unique_ptr<IAudioVoiceEngine> NewWAVAudioVoiceEngine(const wchar_t* path, double sampleRate, int numChans)
{ {
std::unique_ptr<IAudioVoiceEngine> ret = std::make_unique<WAVOutVoiceEngine>(path, sampleRate); std::unique_ptr<IAudioVoiceEngine> ret = std::make_unique<WAVOutVoiceEngine>(path, sampleRate, numChans);
if (!static_cast<WAVOutVoiceEngine&>(*ret).m_fp) if (!static_cast<WAVOutVoiceEngine&>(*ret).m_fp)
return {}; return {};
return ret; return ret;