Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion src/framework/audio/common/audiotypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,21 +105,47 @@ enum class SoundTrackType {
WAV
};

enum class AudioSampleFormat {
Undefined = 0,
Int16,
Int24,
Float32
};

struct SoundTrackFormat {
SoundTrackType type = SoundTrackType::Undefined;
OutputSpec outputSpec;
AudioSampleFormat sampleFormat = AudioSampleFormat::Undefined;
int bitRate = 0;

bool operator==(const SoundTrackFormat& other) const
{
return type == other.type
&& outputSpec == other.outputSpec
&& sampleFormat == other.sampleFormat
&& bitRate == other.bitRate;
}

bool isValid() const
{
return type != SoundTrackType::Undefined && outputSpec.isValid();
if (!outputSpec.isValid()) {
return false;
}

switch (type) {
case SoundTrackType::WAV:
case SoundTrackType::FLAC:
// For lossless/uncompressed, sample format must be defined
return sampleFormat != AudioSampleFormat::Undefined;

case SoundTrackType::MP3:
case SoundTrackType::OGG:
// For lossy, bitrate must be positive
return bitRate > 0;

default:
return false;
}
}
};

Expand Down
18 changes: 16 additions & 2 deletions src/framework/audio/common/rpc/rpcpacker.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ void unpack_custom(muse::msgpack::UnPacker& p, muse::audio::SoundPreset& value);

void pack_custom(muse::msgpack::Packer& p, const muse::audio::SoundTrackType& value);
void unpack_custom(muse::msgpack::UnPacker& p, muse::audio::SoundTrackType& value);
void pack_custom(muse::msgpack::Packer& p, const muse::audio::AudioSampleFormat& value);
void unpack_custom(muse::msgpack::UnPacker& p, muse::audio::AudioSampleFormat& value);
void pack_custom(muse::msgpack::Packer& p, const muse::audio::SoundTrackFormat& value);
void unpack_custom(muse::msgpack::UnPacker& p, muse::audio::SoundTrackFormat& value);

Expand Down Expand Up @@ -259,14 +261,26 @@ inline void unpack_custom(muse::msgpack::UnPacker& p, muse::audio::SoundTrackTyp
value = static_cast<muse::audio::SoundTrackType>(type);
}

inline void pack_custom(muse::msgpack::Packer& p, const muse::audio::AudioSampleFormat& value)
{
p.process(static_cast<int>(value));
}

inline void unpack_custom(muse::msgpack::UnPacker& p, muse::audio::AudioSampleFormat& value)
{
int format = 0;
p.process(format);
value = static_cast<muse::audio::AudioSampleFormat>(format);
}

inline void pack_custom(muse::msgpack::Packer& p, const muse::audio::SoundTrackFormat& value)
{
p.process(value.type, value.outputSpec, value.bitRate);
p.process(value.type, value.outputSpec, value.sampleFormat, value.bitRate);
}

inline void unpack_custom(muse::msgpack::UnPacker& p, muse::audio::SoundTrackFormat& value)
{
p.process(value.type, value.outputSpec, value.bitRate);
p.process(value.type, value.outputSpec, value.sampleFormat, value.bitRate);
}

inline void pack_custom(muse::msgpack::Packer& p, const muse::audio::AudioSignalVal& value)
Expand Down
40 changes: 34 additions & 6 deletions src/framework/audio/engine/internal/export/flacencoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,23 @@
m_progress.progress(current, total);
});

int bitsPerSample = 0;
switch (m_format.sampleFormat) {
case AudioSampleFormat::Int16:
bitsPerSample = 16;
break;
case AudioSampleFormat::Int24:
bitsPerSample = 24;
break;
default:
return false;
}

if (!m_flac->set_verify(true)
|| !m_flac->set_compression_level(0)
|| !m_flac->set_channels(m_format.outputSpec.audioChannelCount)
|| !m_flac->set_sample_rate(m_format.outputSpec.sampleRate)
|| !m_flac->set_bits_per_sample(16)
|| !m_flac->set_bits_per_sample(bitsPerSample)
|| !m_flac->set_total_samples_estimate(totalSamplesNumber)) {
return false;
}
Expand Down Expand Up @@ -107,18 +119,34 @@
uint32_t frameSize = 1024;
size_t stepSize = frameSize * m_format.outputSpec.audioChannelCount;

std::vector<FLAC__int32> buff(samplesPerChannel * sizeof(float));
int bitsPerSample = 0;
switch (m_format.sampleFormat) {
case AudioSampleFormat::Int16:
bitsPerSample = 16;
break;
case AudioSampleFormat::Int24:
bitsPerSample = 24;
break;
default:
return 0;
}

std::vector<FLAC__int32> buff(totalSamplesNumber);

for (size_t i = 0; i < buff.size(); ++i) {
buff[i] = dsp::convertFloatSamples<FLAC__int32>(input[i], 16);
for (size_t i = 0; i < totalSamplesNumber; ++i) {
buff[i] = dsp::convertFloatSamples<FLAC__int32>(input[i], bitsPerSample);
}

std::vector<FLAC__int32> intermBuff(stepSize);

for (size_t i = 0; i < totalSamplesNumber; i += stepSize) {
std::copy(buff.data() + i, buff.data() + i + stepSize, intermBuff.data());
size_t remainingSamples = totalSamplesNumber - i;
size_t samplesToCopy = std::min(stepSize, remainingSamples);
uint32_t samplesPerChannelToProcess = samplesToCopy / m_format.outputSpec.audioChannelCount;

Check warning on line 145 in src/framework/audio/engine/internal/export/flacencoder.cpp

View workflow job for this annotation

GitHub Actions / windows_x64

'initializing': conversion from 'size_t' to 'uint32_t', possible loss of data

std::copy(buff.data() + i, buff.data() + i + samplesToCopy, intermBuff.data());

if (m_flac->process_interleaved(intermBuff.data(), frameSize)) {
if (m_flac->process_interleaved(intermBuff.data(), samplesPerChannelToProcess)) {
result += stepSize;
} else {
break;
Expand Down
68 changes: 48 additions & 20 deletions src/framework/audio/engine/internal/export/wavencoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,14 @@

#include "wavencoder.h"

#include "../dsp/audiomathutils.h"

#include "log.h"

using namespace muse::audio;
using namespace muse::audio::encode;

struct WavHeader {
enum class WavFileType {
int16, // 16 bit signed integer
float32
};

uint32_t chunkSize = 0;
uint16_t audioChannelsNumber = 0;
uint16_t bitsPerSample = 0;
Expand All @@ -42,7 +39,7 @@ struct WavHeader {

void write(std::ofstream& stream)
{
const uint32_t bytesPerSample = 4;
const uint32_t bytesPerSample = bitsPerSample / 8;
const uint32_t sampleDataLength = audioChannelsNumber * samplesPerChannel * bytesPerSample;
const uint32_t headerLength = 20 + chunkSize + 8;
const uint32_t file_length = headerLength + sampleDataLength;
Expand All @@ -58,7 +55,7 @@ struct WavHeader {

writeTagData<uint32_t>(stream, chunkSize);
writeTagData<uint16_t>(stream, code);
writeTagData<uint16_t>(stream, 2);
writeTagData<uint16_t>(stream, audioChannelsNumber);
writeTagData<uint32_t>(stream, sampleRate);
writeTagData<uint32_t>(stream, bytesPerSec);
writeTagData<uint16_t>(stream, bytesPerFrame);
Expand Down Expand Up @@ -89,8 +86,25 @@ size_t WavEncoder::encode(samples_t samplesPerChannel, const float* input)

WavHeader header;
header.chunkSize = 18; // 18 is 2 bytes more to include cbsize field / extension size
header.bitsPerSample = 32;
header.code = 3; // IEEE_FLOAT = 3, PCM = 1

switch (m_format.sampleFormat) {
case AudioSampleFormat::Int16:
header.bitsPerSample = 16;
header.code = 1; // PCM
break;
case AudioSampleFormat::Int24:
header.bitsPerSample = 24;
header.code = 1; // PCM
break;
case AudioSampleFormat::Float32:
header.bitsPerSample = 32;
header.code = 3; // IEEE_FLOAT
break;
case AudioSampleFormat::Undefined:
default:
return 0;
}

header.audioChannelsNumber = m_format.outputSpec.audioChannelCount;
header.sampleRate = m_format.outputSpec.sampleRate;
header.samplesPerChannel = samplesPerChannel;
Expand All @@ -99,20 +113,34 @@ size_t WavEncoder::encode(samples_t samplesPerChannel, const float* input)

int total = header.samplesPerChannel;
int progressStep = (total * 5) / 100; // every 5%
QVector<samples_t> progressValues;
for (samples_t sampleIdx = 0; sampleIdx < header.samplesPerChannel;) {
progressValues << sampleIdx;
sampleIdx += progressStep;
}

for (samples_t sampleIdx = 0; sampleIdx < header.samplesPerChannel; ++sampleIdx) {
for (audioch_t audioChNum = 0; audioChNum < m_format.outputSpec.audioChannelCount; ++audioChNum) {
int idx = sampleIdx * m_format.outputSpec.audioChannelCount + audioChNum;
m_fileStream.write(reinterpret_cast<const char*>(input + idx), 4);
const int channels = m_format.outputSpec.audioChannelCount;

if (m_format.sampleFormat == AudioSampleFormat::Float32) {
for (samples_t sampleIdx = 0; sampleIdx < header.samplesPerChannel; ++sampleIdx) {
for (audioch_t audioChNum = 0; audioChNum < channels; ++audioChNum) {
int idx = sampleIdx * channels + audioChNum;
m_fileStream.write(reinterpret_cast<const char*>(input + idx), 4);
}
if (sampleIdx % progressStep == 0) {
m_progress.progress(sampleIdx, total);
}
}
} else {
const int bits = header.bitsPerSample;
const int bytesToWrite = bits / 8;

for (samples_t sampleIdx = 0; sampleIdx < header.samplesPerChannel; ++sampleIdx) {
for (audioch_t audioChNum = 0; audioChNum < channels; ++audioChNum) {
int idx = sampleIdx * channels + audioChNum;

int32_t sampleInt = dsp::convertFloatSamples<int32_t>(input[idx], bits);

if (progressValues.contains(sampleIdx)) {
m_progress.progress(sampleIdx, total);
m_fileStream.write(reinterpret_cast<const char*>(&sampleInt), bytesToWrite);
}
if (sampleIdx % progressStep == 0) {
m_progress.progress(sampleIdx, total);
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/framework/audio/tests/rpcpacker_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,11 +258,13 @@ TEST_F(Audio_RpcPackerTests, SoundTrackFormat)
origin.outputSpec.samplesPerChannel = 256;
origin.outputSpec.audioChannelCount = 2;
origin.bitRate = 196;
origin.sampleFormat = AudioSampleFormat::Float32;

KNOWN_FIELDS(origin,
origin.type,
origin.outputSpec,
origin.bitRate);
origin.bitRate,
origin.sampleFormat);

ByteArray data = rpc::RpcPacker::pack(origin);

Expand Down
7 changes: 7 additions & 0 deletions src/importexport/audioexport/iaudioexportconfiguration.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ class IAudioExportConfiguration : MODULE_EXPORT_INTERFACE
virtual const std::vector<int>& availableSampleRates() const = 0;

virtual muse::audio::samples_t exportBufferSize() const = 0;

virtual muse::audio::AudioSampleFormat exportSampleFormat() const = 0;
virtual void setExportSampleFormat(muse::audio::AudioSampleFormat format) = 0;
virtual void setExportSampleFormat(const QString& extension, muse::audio::AudioSampleFormat format) = 0;
virtual const std::vector<muse::audio::AudioSampleFormat>& availableSampleFormats(const QString& extension) const = 0;
virtual QString sampleFormatToString(muse::audio::AudioSampleFormat format) const = 0;
virtual void loadSampleFormatSetting(const QString& extension) = 0;
};
}

Expand Down
69 changes: 69 additions & 0 deletions src/importexport/audioexport/internal/audioexportconfiguration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "audioexportconfiguration.h"

#include "settings.h"
#include "translation.h"

using namespace muse;
using namespace mu;
Expand All @@ -31,11 +32,15 @@ using namespace muse::audio;

static const Settings::Key EXPORT_SAMPLE_RATE_KEY("iex_audioexport", "export/audio/sampleRate");
static const Settings::Key EXPORT_MP3_BITRATE("iex_audioexport", "export/audio/mp3Bitrate");
static const Settings::Key EXPORT_WAV_SAMPLE_FORMAT_KEY("iex_audioexport", "export/audio/wavSampleFormat");
static const Settings::Key EXPORT_FLAC_SAMPLE_FORMAT_KEY("iex_audioexport", "export/audio/flacSampleFormat");

void AudioExportConfiguration::init()
{
settings()->setDefaultValue(EXPORT_SAMPLE_RATE_KEY, Val(44100));
settings()->setDefaultValue(EXPORT_MP3_BITRATE, Val(128));
settings()->setDefaultValue(EXPORT_WAV_SAMPLE_FORMAT_KEY, Val(static_cast<int>(AudioSampleFormat::Float32)));
settings()->setDefaultValue(EXPORT_FLAC_SAMPLE_FORMAT_KEY, Val(static_cast<int>(AudioSampleFormat::Int16)));
}

int AudioExportConfiguration::exportMp3Bitrate() const
Expand Down Expand Up @@ -79,3 +84,67 @@ samples_t AudioExportConfiguration::exportBufferSize() const
{
return 4096;
}

AudioSampleFormat AudioExportConfiguration::exportSampleFormat() const
{
return m_exportSampleFormat;
}

void AudioExportConfiguration::setExportSampleFormat(AudioSampleFormat format)
{
m_exportSampleFormat = format;
}

void AudioExportConfiguration::setExportSampleFormat(const QString& extension, AudioSampleFormat format)
{
m_exportSampleFormat = format;
if (extension == QLatin1String("wav")) {
settings()->setSharedValue(EXPORT_WAV_SAMPLE_FORMAT_KEY, Val(static_cast<int>(format)));
} else if (extension == QLatin1String("flac")) {
settings()->setSharedValue(EXPORT_FLAC_SAMPLE_FORMAT_KEY, Val(static_cast<int>(format)));
}
}

const std::vector<AudioSampleFormat>& AudioExportConfiguration::availableSampleFormats(const QString& extension) const
{
if (extension == QLatin1String("wav")) {
static const std::vector<muse::audio::AudioSampleFormat> wavSampleFormats {
AudioSampleFormat::Int16,
AudioSampleFormat::Int24,
AudioSampleFormat::Float32,
};
return wavSampleFormats;
}
if (extension == QLatin1String("flac")) {
static const std::vector<muse::audio::AudioSampleFormat> flacSampleFormats {
AudioSampleFormat::Int16,
AudioSampleFormat::Int24,
};
return flacSampleFormats;
}
static const std::vector<muse::audio::AudioSampleFormat> emptySampleFormats {};
return emptySampleFormats;
}

void AudioExportConfiguration::loadSampleFormatSetting(const QString& extension)
{
if (extension == QLatin1String("wav")) {
setExportSampleFormat(static_cast<AudioSampleFormat>(settings()->value(EXPORT_WAV_SAMPLE_FORMAT_KEY).toInt()));
} else if (extension == QLatin1String("flac")) {
setExportSampleFormat(static_cast<AudioSampleFormat>(settings()->value(EXPORT_FLAC_SAMPLE_FORMAT_KEY).toInt()));
}
}

QString AudioExportConfiguration::sampleFormatToString(AudioSampleFormat format) const
{
switch (format) {
case AudioSampleFormat::Int16:
return muse::qtrc("project/export", "16-bit integer");
case AudioSampleFormat::Int24:
return muse::qtrc("project/export", "24-bit integer");
case AudioSampleFormat::Float32:
return muse::qtrc("project/export", "32-bit float");
default:
return QString();
}
}
Loading
Loading