mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-04-20 20:40:29 +08:00
Use nested namespaces (C++17 feature)
This commit is contained in:
@@ -8,46 +8,43 @@
|
||||
|
||||
#include "opusdecoder.h"
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::Codecs
|
||||
{
|
||||
namespace Codecs
|
||||
COpusDecoder::COpusDecoder(int sampleRate, int channels) : m_channels(channels)
|
||||
{
|
||||
COpusDecoder::COpusDecoder(int sampleRate, int channels) : m_channels(channels)
|
||||
{
|
||||
int error;
|
||||
m_opusDecoder = opus_decoder_create(sampleRate, channels, &error);
|
||||
}
|
||||
int error;
|
||||
m_opusDecoder = opus_decoder_create(sampleRate, channels, &error);
|
||||
}
|
||||
|
||||
COpusDecoder::~COpusDecoder()
|
||||
{
|
||||
opus_decoder_destroy(m_opusDecoder);
|
||||
}
|
||||
COpusDecoder::~COpusDecoder()
|
||||
{
|
||||
opus_decoder_destroy(m_opusDecoder);
|
||||
}
|
||||
|
||||
int COpusDecoder::frameCount(int bufferSize)
|
||||
{
|
||||
// seems like bitrate should be required
|
||||
int bitrate = 16;
|
||||
int bytesPerSample = (bitrate / 8) * m_channels;
|
||||
return bufferSize / bytesPerSample;
|
||||
}
|
||||
int COpusDecoder::frameCount(int bufferSize)
|
||||
{
|
||||
// seems like bitrate should be required
|
||||
int bitrate = 16;
|
||||
int bytesPerSample = (bitrate / 8) * m_channels;
|
||||
return bufferSize / bytesPerSample;
|
||||
}
|
||||
|
||||
QVector<qint16> COpusDecoder::decode(const QByteArray &opusData, int dataLength, int *decodedLength)
|
||||
{
|
||||
QVector<qint16> decoded(MaxDataBytes, 0);
|
||||
int count = frameCount(MaxDataBytes);
|
||||
QVector<qint16> COpusDecoder::decode(const QByteArray &opusData, int dataLength, int *decodedLength)
|
||||
{
|
||||
QVector<qint16> decoded(MaxDataBytes, 0);
|
||||
int count = frameCount(MaxDataBytes);
|
||||
|
||||
if (!opusData.isEmpty())
|
||||
{
|
||||
*decodedLength = opus_decode(m_opusDecoder, reinterpret_cast<const unsigned char *>(opusData.data()), dataLength, decoded.data(), count, 0);
|
||||
}
|
||||
decoded.resize(*decodedLength);
|
||||
return decoded;
|
||||
}
|
||||
|
||||
void COpusDecoder::resetState()
|
||||
if (!opusData.isEmpty())
|
||||
{
|
||||
if (!m_opusDecoder) { return; }
|
||||
opus_decoder_ctl(m_opusDecoder, OPUS_RESET_STATE);
|
||||
*decodedLength = opus_decode(m_opusDecoder, reinterpret_cast<const unsigned char *>(opusData.data()), dataLength, decoded.data(), count, 0);
|
||||
}
|
||||
} // ns
|
||||
decoded.resize(*decodedLength);
|
||||
return decoded;
|
||||
}
|
||||
|
||||
void COpusDecoder::resetState()
|
||||
{
|
||||
if (!m_opusDecoder) { return; }
|
||||
opus_decoder_ctl(m_opusDecoder, OPUS_RESET_STATE);
|
||||
}
|
||||
} // ns
|
||||
|
||||
@@ -16,42 +16,39 @@
|
||||
|
||||
#include <QVector>
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::Codecs
|
||||
{
|
||||
namespace Codecs
|
||||
//! OPUS decoder
|
||||
class BLACKSOUND_EXPORT COpusDecoder
|
||||
{
|
||||
//! OPUS decoder
|
||||
class BLACKSOUND_EXPORT COpusDecoder
|
||||
{
|
||||
public:
|
||||
//! Ctor
|
||||
COpusDecoder(int sampleRate, int channels);
|
||||
public:
|
||||
//! Ctor
|
||||
COpusDecoder(int sampleRate, int channels);
|
||||
|
||||
//! Dtor
|
||||
~COpusDecoder();
|
||||
//! Dtor
|
||||
~COpusDecoder();
|
||||
|
||||
//! Not copyable and assignable
|
||||
//! @{
|
||||
COpusDecoder(const COpusDecoder &decoder) = delete;
|
||||
COpusDecoder& operator=(COpusDecoder const&) = delete;
|
||||
//! @}
|
||||
//! Not copyable and assignable
|
||||
//! @{
|
||||
COpusDecoder(const COpusDecoder &decoder) = delete;
|
||||
COpusDecoder& operator=(COpusDecoder const&) = delete;
|
||||
//! @}
|
||||
|
||||
//! Frame count
|
||||
int frameCount(int bufferSize);
|
||||
//! Frame count
|
||||
int frameCount(int bufferSize);
|
||||
|
||||
//! Decode
|
||||
QVector<qint16> decode(const QByteArray &opusData, int dataLength, int *decodedLength);
|
||||
//! Decode
|
||||
QVector<qint16> decode(const QByteArray &opusData, int dataLength, int *decodedLength);
|
||||
|
||||
//! Reset
|
||||
void resetState();
|
||||
//! Reset
|
||||
void resetState();
|
||||
|
||||
private:
|
||||
OpusDecoder *m_opusDecoder = nullptr;
|
||||
int m_channels;
|
||||
private:
|
||||
OpusDecoder *m_opusDecoder = nullptr;
|
||||
int m_channels;
|
||||
|
||||
static constexpr int MaxDataBytes = 4000;
|
||||
};
|
||||
} // ns
|
||||
static constexpr int MaxDataBytes = 4000;
|
||||
};
|
||||
} // ns
|
||||
|
||||
#endif // guard
|
||||
|
||||
@@ -8,33 +8,30 @@
|
||||
|
||||
#include "opusencoder.h"
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::Codecs
|
||||
{
|
||||
namespace Codecs
|
||||
COpusEncoder::COpusEncoder(int sampleRate, int channels, int application)
|
||||
{
|
||||
COpusEncoder::COpusEncoder(int sampleRate, int channels, int application)
|
||||
{
|
||||
int error;
|
||||
opusEncoder = opus_encoder_create(sampleRate, channels, application, &error);
|
||||
}
|
||||
int error;
|
||||
opusEncoder = opus_encoder_create(sampleRate, channels, application, &error);
|
||||
}
|
||||
|
||||
COpusEncoder::~COpusEncoder()
|
||||
{
|
||||
opus_encoder_destroy(opusEncoder);
|
||||
}
|
||||
COpusEncoder::~COpusEncoder()
|
||||
{
|
||||
opus_encoder_destroy(opusEncoder);
|
||||
}
|
||||
|
||||
void COpusEncoder::setBitRate(int bitRate)
|
||||
{
|
||||
opus_encoder_ctl(opusEncoder, OPUS_SET_BITRATE(bitRate));
|
||||
}
|
||||
void COpusEncoder::setBitRate(int bitRate)
|
||||
{
|
||||
opus_encoder_ctl(opusEncoder, OPUS_SET_BITRATE(bitRate));
|
||||
}
|
||||
|
||||
QByteArray COpusEncoder::encode(const QVector<qint16> &pcmSamples, int samplesLength, int *encodedLength)
|
||||
{
|
||||
QByteArray encoded(maxDataBytes, 0);
|
||||
int length = opus_encode(opusEncoder, reinterpret_cast<const opus_int16 *>(pcmSamples.data()), samplesLength, reinterpret_cast<unsigned char *>(encoded.data()), maxDataBytes);
|
||||
*encodedLength = length;
|
||||
encoded.truncate(length);
|
||||
return encoded;
|
||||
}
|
||||
} // ns
|
||||
QByteArray COpusEncoder::encode(const QVector<qint16> &pcmSamples, int samplesLength, int *encodedLength)
|
||||
{
|
||||
QByteArray encoded(maxDataBytes, 0);
|
||||
int length = opus_encode(opusEncoder, reinterpret_cast<const opus_int16 *>(pcmSamples.data()), samplesLength, reinterpret_cast<unsigned char *>(encoded.data()), maxDataBytes);
|
||||
*encodedLength = length;
|
||||
encoded.truncate(length);
|
||||
return encoded;
|
||||
}
|
||||
} // ns
|
||||
|
||||
@@ -17,38 +17,35 @@
|
||||
#include <QByteArray>
|
||||
#include <QVector>
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::Codecs
|
||||
{
|
||||
namespace Codecs
|
||||
//! OPUS encoder
|
||||
class BLACKSOUND_EXPORT COpusEncoder
|
||||
{
|
||||
//! OPUS encoder
|
||||
class BLACKSOUND_EXPORT COpusEncoder
|
||||
{
|
||||
public:
|
||||
//! Ctor
|
||||
COpusEncoder(int sampleRate, int channels, int application = OPUS_APPLICATION_VOIP);
|
||||
public:
|
||||
//! Ctor
|
||||
COpusEncoder(int sampleRate, int channels, int application = OPUS_APPLICATION_VOIP);
|
||||
|
||||
//! Dtor
|
||||
~COpusEncoder();
|
||||
//! Dtor
|
||||
~COpusEncoder();
|
||||
|
||||
//! Non copyable
|
||||
COpusEncoder(const COpusEncoder &temp_obj) = delete;
|
||||
//! Non copyable
|
||||
COpusEncoder(const COpusEncoder &temp_obj) = delete;
|
||||
|
||||
//! Non assignable
|
||||
COpusEncoder &operator=(const COpusEncoder &temp_obj) = delete;
|
||||
//! Non assignable
|
||||
COpusEncoder &operator=(const COpusEncoder &temp_obj) = delete;
|
||||
|
||||
//! Bit rate
|
||||
void setBitRate(int bitRate);
|
||||
//! Bit rate
|
||||
void setBitRate(int bitRate);
|
||||
|
||||
//! Encode
|
||||
QByteArray encode(const QVector<qint16> &pcmSamples, int samplesLength, int *encodedLength);
|
||||
//! Encode
|
||||
QByteArray encode(const QVector<qint16> &pcmSamples, int samplesLength, int *encodedLength);
|
||||
|
||||
private:
|
||||
OpusEncoder *opusEncoder = nullptr;
|
||||
private:
|
||||
OpusEncoder *opusEncoder = nullptr;
|
||||
|
||||
static constexpr int maxDataBytes = 4000;
|
||||
};
|
||||
} // ns
|
||||
static constexpr int maxDataBytes = 4000;
|
||||
};
|
||||
} // ns
|
||||
|
||||
#endif // guard
|
||||
|
||||
@@ -16,107 +16,104 @@
|
||||
using namespace BlackMisc;
|
||||
using namespace BlackConfig;
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::Dsp
|
||||
{
|
||||
namespace Dsp
|
||||
float BiQuadFilter::transform(float inSample)
|
||||
{
|
||||
float BiQuadFilter::transform(float inSample)
|
||||
{
|
||||
// compute result
|
||||
double result = m_a0 * inSample + m_a1 * m_x1 + m_a2 * m_x2 - m_a3 * m_y1 - m_a4 * m_y2;
|
||||
// compute result
|
||||
double result = m_a0 * inSample + m_a1 * m_x1 + m_a2 * m_x2 - m_a3 * m_y1 - m_a4 * m_y2;
|
||||
|
||||
// shift x1 to x2, sample to x1
|
||||
m_x2 = m_x1;
|
||||
m_x1 = inSample;
|
||||
// shift x1 to x2, sample to x1
|
||||
m_x2 = m_x1;
|
||||
m_x1 = inSample;
|
||||
|
||||
// shift y1 to y2, result to y1
|
||||
m_y2 = m_y1;
|
||||
m_y1 = static_cast<float>(result);
|
||||
// shift y1 to y2, result to y1
|
||||
m_y2 = m_y1;
|
||||
m_y1 = static_cast<float>(result);
|
||||
|
||||
return m_y1;
|
||||
}
|
||||
return m_y1;
|
||||
}
|
||||
|
||||
void BiQuadFilter::setCoefficients(double aa0, double aa1, double aa2, double b0, double b1, double b2)
|
||||
{
|
||||
if (CBuildConfig::isLocalDeveloperDebugBuild()) { BLACK_VERIFY_X(qAbs(aa0) > 1E-06, Q_FUNC_INFO, "Div by zero?"); }
|
||||
void BiQuadFilter::setCoefficients(double aa0, double aa1, double aa2, double b0, double b1, double b2)
|
||||
{
|
||||
if (CBuildConfig::isLocalDeveloperDebugBuild()) { BLACK_VERIFY_X(qAbs(aa0) > 1E-06, Q_FUNC_INFO, "Div by zero?"); }
|
||||
|
||||
// precompute the coefficients
|
||||
m_a0 = b0 / aa0;
|
||||
m_a1 = b1 / aa0;
|
||||
m_a2 = b2 / aa0;
|
||||
m_a3 = aa1 / aa0;
|
||||
m_a4 = aa2 / aa0;
|
||||
}
|
||||
// precompute the coefficients
|
||||
m_a0 = b0 / aa0;
|
||||
m_a1 = b1 / aa0;
|
||||
m_a2 = b2 / aa0;
|
||||
m_a3 = aa1 / aa0;
|
||||
m_a4 = aa2 / aa0;
|
||||
}
|
||||
|
||||
void BiQuadFilter::setLowPassFilter(float sampleRate, float cutoffFrequency, float q)
|
||||
{
|
||||
// H(s) = 1 / (s^2 + s/Q + 1)
|
||||
auto w0 = 2 * M_PI * cutoffFrequency / sampleRate;
|
||||
auto cosw0 = qCos(w0);
|
||||
auto alpha = qSin(w0) / (2 * q);
|
||||
void BiQuadFilter::setLowPassFilter(float sampleRate, float cutoffFrequency, float q)
|
||||
{
|
||||
// H(s) = 1 / (s^2 + s/Q + 1)
|
||||
auto w0 = 2 * M_PI * cutoffFrequency / sampleRate;
|
||||
auto cosw0 = qCos(w0);
|
||||
auto alpha = qSin(w0) / (2 * q);
|
||||
|
||||
auto b0 = (1 - cosw0) / 2;
|
||||
auto b1 = 1 - cosw0;
|
||||
auto b2 = (1 - cosw0) / 2;
|
||||
auto aa0 = 1 + alpha;
|
||||
auto aa1 = -2 * cosw0;
|
||||
auto aa2 = 1 - alpha;
|
||||
setCoefficients(aa0, aa1, aa2, b0, b1, b2);
|
||||
}
|
||||
auto b0 = (1 - cosw0) / 2;
|
||||
auto b1 = 1 - cosw0;
|
||||
auto b2 = (1 - cosw0) / 2;
|
||||
auto aa0 = 1 + alpha;
|
||||
auto aa1 = -2 * cosw0;
|
||||
auto aa2 = 1 - alpha;
|
||||
setCoefficients(aa0, aa1, aa2, b0, b1, b2);
|
||||
}
|
||||
|
||||
void BiQuadFilter::setPeakingEq(float sampleRate, float centreFrequency, float q, float dbGain)
|
||||
{
|
||||
// H(s) = (s^2 + s*(A/Q) + 1) / (s^2 + s/(A*Q) + 1)
|
||||
auto w0 = 2 * M_PI * centreFrequency / sampleRate;
|
||||
auto cosw0 = qCos(w0);
|
||||
auto sinw0 = qSin(w0);
|
||||
auto alpha = sinw0 / (2 * q);
|
||||
auto a = qPow(10, dbGain / 40); // TODO: should we square root this value?
|
||||
void BiQuadFilter::setPeakingEq(float sampleRate, float centreFrequency, float q, float dbGain)
|
||||
{
|
||||
// H(s) = (s^2 + s*(A/Q) + 1) / (s^2 + s/(A*Q) + 1)
|
||||
auto w0 = 2 * M_PI * centreFrequency / sampleRate;
|
||||
auto cosw0 = qCos(w0);
|
||||
auto sinw0 = qSin(w0);
|
||||
auto alpha = sinw0 / (2 * q);
|
||||
auto a = qPow(10, dbGain / 40); // TODO: should we square root this value?
|
||||
|
||||
auto b0 = 1 + alpha * a;
|
||||
auto b1 = -2 * cosw0;
|
||||
auto b2 = 1 - alpha * a;
|
||||
auto aa0 = 1 + alpha / a;
|
||||
auto aa1 = -2 * cosw0;
|
||||
auto aa2 = 1 - alpha / a;
|
||||
setCoefficients(aa0, aa1, aa2, b0, b1, b2);
|
||||
}
|
||||
auto b0 = 1 + alpha * a;
|
||||
auto b1 = -2 * cosw0;
|
||||
auto b2 = 1 - alpha * a;
|
||||
auto aa0 = 1 + alpha / a;
|
||||
auto aa1 = -2 * cosw0;
|
||||
auto aa2 = 1 - alpha / a;
|
||||
setCoefficients(aa0, aa1, aa2, b0, b1, b2);
|
||||
}
|
||||
|
||||
void BiQuadFilter::setHighPassFilter(float sampleRate, float cutoffFrequency, float q)
|
||||
{
|
||||
// H(s) = s^2 / (s^2 + s/Q + 1)
|
||||
auto w0 = 2 * M_PI * cutoffFrequency / sampleRate;
|
||||
auto cosw0 = qCos(w0);
|
||||
auto alpha = qSin(w0) / (2 * q);
|
||||
void BiQuadFilter::setHighPassFilter(float sampleRate, float cutoffFrequency, float q)
|
||||
{
|
||||
// H(s) = s^2 / (s^2 + s/Q + 1)
|
||||
auto w0 = 2 * M_PI * cutoffFrequency / sampleRate;
|
||||
auto cosw0 = qCos(w0);
|
||||
auto alpha = qSin(w0) / (2 * q);
|
||||
|
||||
auto b0 = (1 + cosw0) / 2;
|
||||
auto b1 = -(1 + cosw0);
|
||||
auto b2 = (1 + cosw0) / 2;
|
||||
auto aa0 = 1 + alpha;
|
||||
auto aa1 = -2 * cosw0;
|
||||
auto aa2 = 1 - alpha;
|
||||
setCoefficients(aa0, aa1, aa2, b0, b1, b2);
|
||||
}
|
||||
auto b0 = (1 + cosw0) / 2;
|
||||
auto b1 = -(1 + cosw0);
|
||||
auto b2 = (1 + cosw0) / 2;
|
||||
auto aa0 = 1 + alpha;
|
||||
auto aa1 = -2 * cosw0;
|
||||
auto aa2 = 1 - alpha;
|
||||
setCoefficients(aa0, aa1, aa2, b0, b1, b2);
|
||||
}
|
||||
|
||||
BiQuadFilter BiQuadFilter::lowPassFilter(float sampleRate, float cutoffFrequency, float q)
|
||||
{
|
||||
BiQuadFilter filter;
|
||||
filter.setLowPassFilter(sampleRate, cutoffFrequency, q);
|
||||
return filter;
|
||||
}
|
||||
BiQuadFilter BiQuadFilter::lowPassFilter(float sampleRate, float cutoffFrequency, float q)
|
||||
{
|
||||
BiQuadFilter filter;
|
||||
filter.setLowPassFilter(sampleRate, cutoffFrequency, q);
|
||||
return filter;
|
||||
}
|
||||
|
||||
BiQuadFilter BiQuadFilter::highPassFilter(float sampleRate, float cutoffFrequency, float q)
|
||||
{
|
||||
BiQuadFilter filter;
|
||||
filter.setHighPassFilter(sampleRate, cutoffFrequency, q);
|
||||
return filter;
|
||||
}
|
||||
BiQuadFilter BiQuadFilter::highPassFilter(float sampleRate, float cutoffFrequency, float q)
|
||||
{
|
||||
BiQuadFilter filter;
|
||||
filter.setHighPassFilter(sampleRate, cutoffFrequency, q);
|
||||
return filter;
|
||||
}
|
||||
|
||||
BiQuadFilter BiQuadFilter::peakingEQ(float sampleRate, float centreFrequency, float q, float dbGain)
|
||||
{
|
||||
BiQuadFilter filter;
|
||||
filter.setPeakingEq(sampleRate, centreFrequency, q, dbGain);
|
||||
return filter;
|
||||
}
|
||||
} // ns
|
||||
BiQuadFilter BiQuadFilter::peakingEQ(float sampleRate, float centreFrequency, float q, float dbGain)
|
||||
{
|
||||
BiQuadFilter filter;
|
||||
filter.setPeakingEq(sampleRate, centreFrequency, q, dbGain);
|
||||
return filter;
|
||||
}
|
||||
} // ns
|
||||
|
||||
@@ -13,49 +13,46 @@
|
||||
|
||||
#include "blacksound/blacksoundexport.h"
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::Dsp
|
||||
{
|
||||
namespace Dsp
|
||||
//! Digital biquad filter
|
||||
class BiQuadFilter
|
||||
{
|
||||
//! Digital biquad filter
|
||||
class BiQuadFilter
|
||||
{
|
||||
public:
|
||||
//! Ctor
|
||||
BiQuadFilter() = default;
|
||||
public:
|
||||
//! Ctor
|
||||
BiQuadFilter() = default;
|
||||
|
||||
//! Transform
|
||||
float transform(float inSample);
|
||||
//! Transform
|
||||
float transform(float inSample);
|
||||
|
||||
//! Set filter parameters
|
||||
//! @{
|
||||
void setCoefficients(double aa0, double aa1, double aa2, double b0, double b1, double b2);
|
||||
void setLowPassFilter(float sampleRate, float cutoffFrequency, float q);
|
||||
void setPeakingEq(float sampleRate, float centreFrequency, float q, float dbGain);
|
||||
void setHighPassFilter(float sampleRate, float cutoffFrequency, float q);
|
||||
//! @}
|
||||
//! Set filter parameters
|
||||
//! @{
|
||||
void setCoefficients(double aa0, double aa1, double aa2, double b0, double b1, double b2);
|
||||
void setLowPassFilter(float sampleRate, float cutoffFrequency, float q);
|
||||
void setPeakingEq(float sampleRate, float centreFrequency, float q, float dbGain);
|
||||
void setHighPassFilter(float sampleRate, float cutoffFrequency, float q);
|
||||
//! @}
|
||||
|
||||
//! Get filters
|
||||
//! @{
|
||||
static BiQuadFilter lowPassFilter(float sampleRate, float cutoffFrequency, float q);
|
||||
static BiQuadFilter highPassFilter(float sampleRate, float cutoffFrequency, float q);
|
||||
static BiQuadFilter peakingEQ(float sampleRate, float centreFrequency, float q, float dbGain);
|
||||
//! @}
|
||||
//! Get filters
|
||||
//! @{
|
||||
static BiQuadFilter lowPassFilter(float sampleRate, float cutoffFrequency, float q);
|
||||
static BiQuadFilter highPassFilter(float sampleRate, float cutoffFrequency, float q);
|
||||
static BiQuadFilter peakingEQ(float sampleRate, float centreFrequency, float q, float dbGain);
|
||||
//! @}
|
||||
|
||||
private:
|
||||
double m_a0 = 0.0;
|
||||
double m_a1 = 0.0;
|
||||
double m_a2 = 0.0;
|
||||
double m_a3 = 0.0;
|
||||
double m_a4 = 0.0;
|
||||
private:
|
||||
double m_a0 = 0.0;
|
||||
double m_a1 = 0.0;
|
||||
double m_a2 = 0.0;
|
||||
double m_a3 = 0.0;
|
||||
double m_a4 = 0.0;
|
||||
|
||||
// state
|
||||
float m_x1 = 0.0;
|
||||
float m_x2 = 0.0;
|
||||
float m_y1 = 0.0;
|
||||
float m_y2 = 0.0;
|
||||
};
|
||||
} // ns
|
||||
// state
|
||||
float m_x1 = 0.0;
|
||||
float m_x2 = 0.0;
|
||||
float m_y1 = 0.0;
|
||||
float m_y2 = 0.0;
|
||||
};
|
||||
} // ns
|
||||
|
||||
#endif // guard
|
||||
|
||||
@@ -3,42 +3,39 @@
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
CBufferedWaveProvider::CBufferedWaveProvider(const QAudioFormat &format, QObject *parent) :
|
||||
ISampleProvider(parent)
|
||||
{
|
||||
CBufferedWaveProvider::CBufferedWaveProvider(const QAudioFormat &format, QObject *parent) :
|
||||
ISampleProvider(parent)
|
||||
{
|
||||
const QString on = QStringLiteral("%1 format: '%2'").arg(this->metaObject()->className(), BlackSound::toQString(format));
|
||||
this->setObjectName(on);
|
||||
const QString on = QStringLiteral("%1 format: '%2'").arg(this->metaObject()->className(), BlackSound::toQString(format));
|
||||
this->setObjectName(on);
|
||||
|
||||
// Set buffer size to 10 secs
|
||||
m_maxBufferSize = format.bytesForDuration(10 * 1000 * 1000);
|
||||
}
|
||||
// Set buffer size to 10 secs
|
||||
m_maxBufferSize = format.bytesForDuration(10 * 1000 * 1000);
|
||||
}
|
||||
|
||||
void CBufferedWaveProvider::addSamples(const QVector<float> &samples)
|
||||
void CBufferedWaveProvider::addSamples(const QVector<float> &samples)
|
||||
{
|
||||
int delta = m_audioBuffer.size() + samples.size() - m_maxBufferSize;
|
||||
if (delta > 0)
|
||||
{
|
||||
int delta = m_audioBuffer.size() + samples.size() - m_maxBufferSize;
|
||||
if (delta > 0)
|
||||
{
|
||||
m_audioBuffer.remove(0, delta);
|
||||
}
|
||||
m_audioBuffer.append(samples);
|
||||
m_audioBuffer.remove(0, delta);
|
||||
}
|
||||
m_audioBuffer.append(samples);
|
||||
}
|
||||
|
||||
int CBufferedWaveProvider::readSamples(QVector<float> &samples, qint64 count)
|
||||
{
|
||||
const int len = static_cast<int>(qMin(count, static_cast<qint64>(m_audioBuffer.size())));
|
||||
samples = m_audioBuffer.mid(0, len);
|
||||
// if (len != 0) qDebug() << "Reading" << count << "samples." << m_audioBuffer.size() << "currently in the buffer.";
|
||||
m_audioBuffer.remove(0, len);
|
||||
return len;
|
||||
}
|
||||
int CBufferedWaveProvider::readSamples(QVector<float> &samples, qint64 count)
|
||||
{
|
||||
const int len = static_cast<int>(qMin(count, static_cast<qint64>(m_audioBuffer.size())));
|
||||
samples = m_audioBuffer.mid(0, len);
|
||||
// if (len != 0) qDebug() << "Reading" << count << "samples." << m_audioBuffer.size() << "currently in the buffer.";
|
||||
m_audioBuffer.remove(0, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
void CBufferedWaveProvider::clearBuffer()
|
||||
{
|
||||
m_audioBuffer.clear();
|
||||
}
|
||||
} // ns
|
||||
void CBufferedWaveProvider::clearBuffer()
|
||||
{
|
||||
m_audioBuffer.clear();
|
||||
}
|
||||
} // ns
|
||||
|
||||
@@ -18,36 +18,33 @@
|
||||
#include <QByteArray>
|
||||
#include <QVector>
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
//! Buffered wave generator
|
||||
class BLACKSOUND_EXPORT CBufferedWaveProvider : public ISampleProvider
|
||||
{
|
||||
//! Buffered wave generator
|
||||
class BLACKSOUND_EXPORT CBufferedWaveProvider : public ISampleProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
//! Ctor
|
||||
CBufferedWaveProvider(const QAudioFormat &format, QObject *parent = nullptr);
|
||||
public:
|
||||
//! Ctor
|
||||
CBufferedWaveProvider(const QAudioFormat &format, QObject *parent = nullptr);
|
||||
|
||||
//! Add samples
|
||||
void addSamples(const QVector<float> &samples);
|
||||
//! Add samples
|
||||
void addSamples(const QVector<float> &samples);
|
||||
|
||||
//! ISampleProvider::readSamples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) override;
|
||||
//! ISampleProvider::readSamples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) override;
|
||||
|
||||
//! Bytes from buffer
|
||||
int getBufferedBytes() const { return m_audioBuffer.size(); }
|
||||
//! Bytes from buffer
|
||||
int getBufferedBytes() const { return m_audioBuffer.size(); }
|
||||
|
||||
//! Clear the buffer
|
||||
void clearBuffer();
|
||||
//! Clear the buffer
|
||||
void clearBuffer();
|
||||
|
||||
private:
|
||||
QVector<float> m_audioBuffer;
|
||||
qint32 m_maxBufferSize;
|
||||
};
|
||||
} // ns
|
||||
private:
|
||||
QVector<float> m_audioBuffer;
|
||||
qint32 m_maxBufferSize;
|
||||
};
|
||||
} // ns
|
||||
|
||||
#endif // guard
|
||||
|
||||
@@ -4,59 +4,56 @@
|
||||
|
||||
using namespace BlackSound::Dsp;
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
CEqualizerSampleProvider::CEqualizerSampleProvider(ISampleProvider *sourceProvider, EqualizerPresets preset, QObject *parent) :
|
||||
ISampleProvider(parent)
|
||||
{
|
||||
CEqualizerSampleProvider::CEqualizerSampleProvider(ISampleProvider *sourceProvider, EqualizerPresets preset, QObject *parent) :
|
||||
ISampleProvider(parent)
|
||||
Q_ASSERT_X(sourceProvider, Q_FUNC_INFO, "Need provider");
|
||||
const QString on = QStringLiteral("%1 of %2").arg(this->metaObject()->className(), sourceProvider->objectName());
|
||||
this->setObjectName(on);
|
||||
|
||||
m_sourceProvider = sourceProvider;
|
||||
setupPreset(preset);
|
||||
}
|
||||
|
||||
int CEqualizerSampleProvider::readSamples(QVector<float> &samples, qint64 count)
|
||||
{
|
||||
const int samplesRead = m_sourceProvider->readSamples(samples, count);
|
||||
if (m_bypass) return samplesRead;
|
||||
|
||||
for (int n = 0; n < samplesRead; n++)
|
||||
{
|
||||
Q_ASSERT_X(sourceProvider, Q_FUNC_INFO, "Need provider");
|
||||
const QString on = QStringLiteral("%1 of %2").arg(this->metaObject()->className(), sourceProvider->objectName());
|
||||
this->setObjectName(on);
|
||||
|
||||
m_sourceProvider = sourceProvider;
|
||||
setupPreset(preset);
|
||||
}
|
||||
|
||||
int CEqualizerSampleProvider::readSamples(QVector<float> &samples, qint64 count)
|
||||
{
|
||||
const int samplesRead = m_sourceProvider->readSamples(samples, count);
|
||||
if (m_bypass) return samplesRead;
|
||||
|
||||
for (int n = 0; n < samplesRead; n++)
|
||||
for (int band = 0; band < m_filters.size(); band++)
|
||||
{
|
||||
for (int band = 0; band < m_filters.size(); band++)
|
||||
{
|
||||
samples[n] = m_filters[band].transform(samples[n]);
|
||||
}
|
||||
samples[n] *= static_cast<float>(m_outputGain);
|
||||
samples[n] = m_filters[band].transform(samples[n]);
|
||||
}
|
||||
return samplesRead;
|
||||
samples[n] *= static_cast<float>(m_outputGain);
|
||||
}
|
||||
return samplesRead;
|
||||
}
|
||||
|
||||
void CEqualizerSampleProvider::setupPreset(EqualizerPresets preset)
|
||||
void CEqualizerSampleProvider::setupPreset(EqualizerPresets preset)
|
||||
{
|
||||
switch (preset)
|
||||
{
|
||||
switch (preset)
|
||||
{
|
||||
case VHFEmulation:
|
||||
m_filters.push_back(BiQuadFilter::highPassFilter(44100, 310, 0.25));
|
||||
m_filters.push_back(BiQuadFilter::peakingEQ(44100, 450, 0.75, 17.0));
|
||||
m_filters.push_back(BiQuadFilter::peakingEQ(44100, 1450, 1.0, 25.0));
|
||||
m_filters.push_back(BiQuadFilter::peakingEQ(44100, 2000, 1.0, 25.0));
|
||||
m_filters.push_back(BiQuadFilter::lowPassFilter(44100, 2500, 0.25));
|
||||
break;
|
||||
}
|
||||
case VHFEmulation:
|
||||
m_filters.push_back(BiQuadFilter::highPassFilter(44100, 310, 0.25));
|
||||
m_filters.push_back(BiQuadFilter::peakingEQ(44100, 450, 0.75, 17.0));
|
||||
m_filters.push_back(BiQuadFilter::peakingEQ(44100, 1450, 1.0, 25.0));
|
||||
m_filters.push_back(BiQuadFilter::peakingEQ(44100, 2000, 1.0, 25.0));
|
||||
m_filters.push_back(BiQuadFilter::lowPassFilter(44100, 2500, 0.25));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
double CEqualizerSampleProvider::outputGain() const
|
||||
{
|
||||
return m_outputGain;
|
||||
}
|
||||
double CEqualizerSampleProvider::outputGain() const
|
||||
{
|
||||
return m_outputGain;
|
||||
}
|
||||
|
||||
void CEqualizerSampleProvider::setOutputGain(double outputGain)
|
||||
{
|
||||
m_outputGain = outputGain;
|
||||
}
|
||||
} // ns
|
||||
void CEqualizerSampleProvider::setOutputGain(double outputGain)
|
||||
{
|
||||
m_outputGain = outputGain;
|
||||
}
|
||||
} // ns
|
||||
|
||||
@@ -18,47 +18,44 @@
|
||||
#include <QSharedPointer>
|
||||
#include <QVector>
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
//! Equalizer
|
||||
enum EqualizerPresets
|
||||
{
|
||||
//! Equalizer
|
||||
enum EqualizerPresets
|
||||
{
|
||||
VHFEmulation = 1
|
||||
};
|
||||
VHFEmulation = 1
|
||||
};
|
||||
|
||||
//! Equalizer
|
||||
class BLACKSOUND_EXPORT CEqualizerSampleProvider : public ISampleProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
//! Equalizer
|
||||
class BLACKSOUND_EXPORT CEqualizerSampleProvider : public ISampleProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
//! Ctor
|
||||
CEqualizerSampleProvider(ISampleProvider *sourceProvider, EqualizerPresets preset, QObject *parent = nullptr);
|
||||
public:
|
||||
//! Ctor
|
||||
CEqualizerSampleProvider(ISampleProvider *sourceProvider, EqualizerPresets preset, QObject *parent = nullptr);
|
||||
|
||||
//! \copydoc ISampleProvider::readSamples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) override;
|
||||
//! \copydoc ISampleProvider::readSamples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) override;
|
||||
|
||||
//! Bypassing?
|
||||
void setBypassEffects(bool value) { m_bypass = value; }
|
||||
//! Bypassing?
|
||||
void setBypassEffects(bool value) { m_bypass = value; }
|
||||
|
||||
//! Gain
|
||||
//! @{
|
||||
double outputGain() const;
|
||||
void setOutputGain(double outputGain);
|
||||
//! @}
|
||||
//! Gain
|
||||
//! @{
|
||||
double outputGain() const;
|
||||
void setOutputGain(double outputGain);
|
||||
//! @}
|
||||
|
||||
private:
|
||||
void setupPreset(EqualizerPresets preset);
|
||||
private:
|
||||
void setupPreset(EqualizerPresets preset);
|
||||
|
||||
ISampleProvider *m_sourceProvider = nullptr;
|
||||
int m_channels = 1;
|
||||
bool m_bypass = false;
|
||||
double m_outputGain = 1.0;
|
||||
QVector<Dsp::BiQuadFilter> m_filters;
|
||||
};
|
||||
} // ns
|
||||
ISampleProvider *m_sourceProvider = nullptr;
|
||||
int m_channels = 1;
|
||||
bool m_bypass = false;
|
||||
double m_outputGain = 1.0;
|
||||
QVector<Dsp::BiQuadFilter> m_filters;
|
||||
};
|
||||
} // ns
|
||||
|
||||
#endif // guard
|
||||
|
||||
@@ -11,57 +11,54 @@
|
||||
|
||||
using namespace BlackMisc;
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
CMixingSampleProvider::CMixingSampleProvider(QObject *parent) : ISampleProvider(parent)
|
||||
{
|
||||
CMixingSampleProvider::CMixingSampleProvider(QObject *parent) : ISampleProvider(parent)
|
||||
const QString on = QStringLiteral("%1").arg(classNameShort(this));
|
||||
this->setObjectName(on);
|
||||
}
|
||||
|
||||
void CMixingSampleProvider::addMixerInput(ISampleProvider *provider)
|
||||
{
|
||||
Q_ASSERT(provider);
|
||||
m_sources.append(provider);
|
||||
|
||||
const QString on = QStringLiteral("%1 sources: %2").arg(classNameShort(this)).arg(m_sources.size());
|
||||
this->setObjectName(on);
|
||||
}
|
||||
|
||||
int CMixingSampleProvider::readSamples(QVector<float> &samples, qint64 count)
|
||||
{
|
||||
samples.clear();
|
||||
samples.fill(0, static_cast<int>(count));
|
||||
int outputLen = 0;
|
||||
|
||||
QVector<ISampleProvider *> finishedProviders;
|
||||
for (int i = 0; i < m_sources.size(); i++)
|
||||
{
|
||||
const QString on = QStringLiteral("%1").arg(classNameShort(this));
|
||||
this->setObjectName(on);
|
||||
}
|
||||
ISampleProvider *sampleProvider = m_sources.at(i);
|
||||
QVector<float> sourceBuffer;
|
||||
|
||||
void CMixingSampleProvider::addMixerInput(ISampleProvider *provider)
|
||||
{
|
||||
Q_ASSERT(provider);
|
||||
m_sources.append(provider);
|
||||
|
||||
const QString on = QStringLiteral("%1 sources: %2").arg(classNameShort(this)).arg(m_sources.size());
|
||||
this->setObjectName(on);
|
||||
}
|
||||
|
||||
int CMixingSampleProvider::readSamples(QVector<float> &samples, qint64 count)
|
||||
{
|
||||
samples.clear();
|
||||
samples.fill(0, static_cast<int>(count));
|
||||
int outputLen = 0;
|
||||
|
||||
QVector<ISampleProvider *> finishedProviders;
|
||||
for (int i = 0; i < m_sources.size(); i++)
|
||||
const int len = sampleProvider->readSamples(sourceBuffer, count);
|
||||
for (int n = 0; n < len; n++)
|
||||
{
|
||||
ISampleProvider *sampleProvider = m_sources.at(i);
|
||||
QVector<float> sourceBuffer;
|
||||
|
||||
const int len = sampleProvider->readSamples(sourceBuffer, count);
|
||||
for (int n = 0; n < len; n++)
|
||||
{
|
||||
samples[n] += sourceBuffer[n];
|
||||
}
|
||||
|
||||
outputLen = qMax(len, outputLen);
|
||||
if (sampleProvider->isFinished())
|
||||
{
|
||||
finishedProviders.push_back(sampleProvider);
|
||||
}
|
||||
samples[n] += sourceBuffer[n];
|
||||
}
|
||||
|
||||
for (ISampleProvider *sampleProvider : finishedProviders)
|
||||
outputLen = qMax(len, outputLen);
|
||||
if (sampleProvider->isFinished())
|
||||
{
|
||||
sampleProvider->deleteLater();
|
||||
m_sources.removeAll(sampleProvider);
|
||||
finishedProviders.push_back(sampleProvider);
|
||||
}
|
||||
|
||||
return outputLen;
|
||||
}
|
||||
} // ns
|
||||
|
||||
for (ISampleProvider *sampleProvider : finishedProviders)
|
||||
{
|
||||
sampleProvider->deleteLater();
|
||||
m_sources.removeAll(sampleProvider);
|
||||
}
|
||||
|
||||
return outputLen;
|
||||
}
|
||||
} // ns
|
||||
|
||||
@@ -16,27 +16,24 @@
|
||||
#include <QSharedPointer>
|
||||
#include <QVector>
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
//! Mixer
|
||||
class BLACKSOUND_EXPORT CMixingSampleProvider : public ISampleProvider
|
||||
{
|
||||
//! Mixer
|
||||
class BLACKSOUND_EXPORT CMixingSampleProvider : public ISampleProvider
|
||||
{
|
||||
public:
|
||||
//! Ctor mixing provider
|
||||
CMixingSampleProvider(QObject *parent = nullptr);
|
||||
public:
|
||||
//! Ctor mixing provider
|
||||
CMixingSampleProvider(QObject *parent = nullptr);
|
||||
|
||||
//! Add a provider
|
||||
void addMixerInput(ISampleProvider *provider);
|
||||
//! Add a provider
|
||||
void addMixerInput(ISampleProvider *provider);
|
||||
|
||||
//! \copydoc ISampleProvider::readSamples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) override;
|
||||
//! \copydoc ISampleProvider::readSamples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) override;
|
||||
|
||||
private:
|
||||
QVector<ISampleProvider *> m_sources;
|
||||
};
|
||||
} // ns
|
||||
private:
|
||||
QVector<ISampleProvider *> m_sources;
|
||||
};
|
||||
} // ns
|
||||
|
||||
#endif // guard
|
||||
|
||||
@@ -10,32 +10,29 @@
|
||||
|
||||
#include "pinknoisegenerator.h"
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
int CPinkNoiseGenerator::readSamples(QVector<float> &samples, qint64 count)
|
||||
{
|
||||
int CPinkNoiseGenerator::readSamples(QVector<float> &samples, qint64 count)
|
||||
const int c = static_cast<int>(count);
|
||||
samples.clear();
|
||||
samples.fill(0, c);
|
||||
|
||||
for (int sampleCount = 0; sampleCount < count; sampleCount++)
|
||||
{
|
||||
const int c = static_cast<int>(count);
|
||||
samples.clear();
|
||||
samples.fill(0, c);
|
||||
double white = 2 * m_random.generateDouble() - 1;
|
||||
|
||||
for (int sampleCount = 0; sampleCount < count; sampleCount++)
|
||||
{
|
||||
double white = 2 * m_random.generateDouble() - 1;
|
||||
|
||||
m_pinkNoiseBuffer[0] = 0.99886 * m_pinkNoiseBuffer[0] + white * 0.0555179;
|
||||
m_pinkNoiseBuffer[1] = 0.99332 * m_pinkNoiseBuffer[1] + white * 0.0750759;
|
||||
m_pinkNoiseBuffer[2] = 0.96900 * m_pinkNoiseBuffer[2] + white * 0.1538520;
|
||||
m_pinkNoiseBuffer[3] = 0.86650 * m_pinkNoiseBuffer[3] + white * 0.3104856;
|
||||
m_pinkNoiseBuffer[4] = 0.55000 * m_pinkNoiseBuffer[4] + white * 0.5329522;
|
||||
m_pinkNoiseBuffer[5] = -0.7616 * m_pinkNoiseBuffer[5] - white * 0.0168980;
|
||||
double pink = m_pinkNoiseBuffer[0] + m_pinkNoiseBuffer[1] + m_pinkNoiseBuffer[2] + m_pinkNoiseBuffer[3] + m_pinkNoiseBuffer[4] + m_pinkNoiseBuffer[5] + m_pinkNoiseBuffer[6] + white * 0.5362;
|
||||
m_pinkNoiseBuffer[6] = white * 0.115926;
|
||||
const float sampleValue = static_cast<float>(m_gain * (pink / 5));
|
||||
samples[sampleCount] = sampleValue;
|
||||
}
|
||||
return c;
|
||||
m_pinkNoiseBuffer[0] = 0.99886 * m_pinkNoiseBuffer[0] + white * 0.0555179;
|
||||
m_pinkNoiseBuffer[1] = 0.99332 * m_pinkNoiseBuffer[1] + white * 0.0750759;
|
||||
m_pinkNoiseBuffer[2] = 0.96900 * m_pinkNoiseBuffer[2] + white * 0.1538520;
|
||||
m_pinkNoiseBuffer[3] = 0.86650 * m_pinkNoiseBuffer[3] + white * 0.3104856;
|
||||
m_pinkNoiseBuffer[4] = 0.55000 * m_pinkNoiseBuffer[4] + white * 0.5329522;
|
||||
m_pinkNoiseBuffer[5] = -0.7616 * m_pinkNoiseBuffer[5] - white * 0.0168980;
|
||||
double pink = m_pinkNoiseBuffer[0] + m_pinkNoiseBuffer[1] + m_pinkNoiseBuffer[2] + m_pinkNoiseBuffer[3] + m_pinkNoiseBuffer[4] + m_pinkNoiseBuffer[5] + m_pinkNoiseBuffer[6] + white * 0.5362;
|
||||
m_pinkNoiseBuffer[6] = white * 0.115926;
|
||||
const float sampleValue = static_cast<float>(m_gain * (pink / 5));
|
||||
samples[sampleCount] = sampleValue;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,31 +19,28 @@
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
//! Pink noise generator
|
||||
class BLACKSOUND_EXPORT CPinkNoiseGenerator : public ISampleProvider
|
||||
{
|
||||
//! Pink noise generator
|
||||
class BLACKSOUND_EXPORT CPinkNoiseGenerator : public ISampleProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
//! Noise generator
|
||||
CPinkNoiseGenerator(QObject *parent = nullptr) : ISampleProvider(parent) {}
|
||||
public:
|
||||
//! Noise generator
|
||||
CPinkNoiseGenerator(QObject *parent = nullptr) : ISampleProvider(parent) {}
|
||||
|
||||
//! Read samples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) override;
|
||||
//! Read samples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) override;
|
||||
|
||||
//! Gain
|
||||
void setGain(double gain) { m_gain = gain; }
|
||||
//! Gain
|
||||
void setGain(double gain) { m_gain = gain; }
|
||||
|
||||
private:
|
||||
QRandomGenerator m_random;
|
||||
std::array<double, 7> m_pinkNoiseBuffer = {{0}};
|
||||
double m_gain = 0.0;
|
||||
};
|
||||
}
|
||||
private:
|
||||
QRandomGenerator m_random;
|
||||
std::array<double, 7> m_pinkNoiseBuffer = {{0}};
|
||||
double m_gain = 0.0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // guard
|
||||
|
||||
@@ -17,53 +17,50 @@
|
||||
using namespace BlackMisc;
|
||||
using namespace BlackSound::Wav;
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
CResourceSound::CResourceSound() : m_data(new CResourceSoundData)
|
||||
{
|
||||
CResourceSound::CResourceSound() : m_data(new CResourceSoundData)
|
||||
{
|
||||
// void
|
||||
}
|
||||
// void
|
||||
}
|
||||
|
||||
CResourceSound::CResourceSound(const QString &audioFileName) : m_data(new CResourceSoundData)
|
||||
{
|
||||
m_data->fileName = audioFileName;
|
||||
}
|
||||
CResourceSound::CResourceSound(const QString &audioFileName) : m_data(new CResourceSoundData)
|
||||
{
|
||||
m_data->fileName = audioFileName;
|
||||
}
|
||||
|
||||
bool CResourceSound::load()
|
||||
{
|
||||
if (!m_data || m_data->fileName.isEmpty()) { return false; }
|
||||
bool CResourceSound::load()
|
||||
{
|
||||
if (!m_data || m_data->fileName.isEmpty()) { return false; }
|
||||
|
||||
CWavFile wavFile;
|
||||
m_data->samples.clear();
|
||||
if (wavFile.open(m_data->fileName))
|
||||
CWavFile wavFile;
|
||||
m_data->samples.clear();
|
||||
if (wavFile.open(m_data->fileName))
|
||||
{
|
||||
if (wavFile.fileFormat().sampleType() == QAudioFormat::Float)
|
||||
{
|
||||
if (wavFile.fileFormat().sampleType() == QAudioFormat::Float)
|
||||
{
|
||||
// Not implemented
|
||||
// m_samples = convertFloatBytesTo16BitPCM(wavFile.audioData());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_data->samples = convertBytesTo32BitFloatPCM(wavFile.audioData());
|
||||
}
|
||||
// Not implemented
|
||||
// m_samples = convertFloatBytesTo16BitPCM(wavFile.audioData());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_data->samples = convertBytesTo32BitFloatPCM(wavFile.audioData());
|
||||
}
|
||||
|
||||
m_data->isLoaded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
const QString &CResourceSound::getFileName() const
|
||||
{
|
||||
static const QString empty;
|
||||
return m_data ? m_data->fileName : empty;
|
||||
}
|
||||
m_data->isLoaded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CResourceSound::isSameFileName(const QString &fn) const
|
||||
{
|
||||
if (fn.isEmpty()) { return false; }
|
||||
return stringCompare(fn, m_data->fileName, CFileUtils::osFileNameCaseSensitivity());
|
||||
}
|
||||
} // ns
|
||||
const QString &CResourceSound::getFileName() const
|
||||
{
|
||||
static const QString empty;
|
||||
return m_data ? m_data->fileName : empty;
|
||||
}
|
||||
|
||||
bool CResourceSound::isSameFileName(const QString &fn) const
|
||||
{
|
||||
if (fn.isEmpty()) { return false; }
|
||||
return stringCompare(fn, m_data->fileName, CFileUtils::osFileNameCaseSensitivity());
|
||||
}
|
||||
} // ns
|
||||
|
||||
@@ -20,47 +20,44 @@
|
||||
#include <QExplicitlySharedDataPointer>
|
||||
#include <atomic>
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
//! CResourceSound shared data
|
||||
struct CResourceSoundData : public QSharedData
|
||||
{
|
||||
//! CResourceSound shared data
|
||||
struct CResourceSoundData : public QSharedData
|
||||
{
|
||||
QString fileName; //!< file name
|
||||
bool isLoaded = false; //!< is audio loaded
|
||||
QVector<float> samples; //!< audio samples
|
||||
};
|
||||
QString fileName; //!< file name
|
||||
bool isLoaded = false; //!< is audio loaded
|
||||
QVector<float> samples; //!< audio samples
|
||||
};
|
||||
|
||||
//! File from resources
|
||||
class CResourceSound
|
||||
{
|
||||
public:
|
||||
//! Constructor
|
||||
CResourceSound();
|
||||
//! File from resources
|
||||
class CResourceSound
|
||||
{
|
||||
public:
|
||||
//! Constructor
|
||||
CResourceSound();
|
||||
|
||||
//! Sound of audio file
|
||||
CResourceSound(const QString &audioFileName);
|
||||
//! Sound of audio file
|
||||
CResourceSound(const QString &audioFileName);
|
||||
|
||||
//! Load the attached resource file
|
||||
bool load();
|
||||
//! Load the attached resource file
|
||||
bool load();
|
||||
|
||||
//! Is resource already loaded?
|
||||
bool isLoaded() { return m_data->isLoaded; }
|
||||
//! Is resource already loaded?
|
||||
bool isLoaded() { return m_data->isLoaded; }
|
||||
|
||||
//! Audio data
|
||||
const QVector<float> &audioData() const { return m_data->samples; }
|
||||
//! Audio data
|
||||
const QVector<float> &audioData() const { return m_data->samples; }
|
||||
|
||||
//! Corresponding file
|
||||
const QString &getFileName() const;
|
||||
//! Corresponding file
|
||||
const QString &getFileName() const;
|
||||
|
||||
//! Is same file?
|
||||
bool isSameFileName(const QString &fn) const;
|
||||
//! Is same file?
|
||||
bool isSameFileName(const QString &fn) const;
|
||||
|
||||
private:
|
||||
QExplicitlySharedDataPointer<CResourceSoundData> m_data;
|
||||
};
|
||||
} // ns
|
||||
private:
|
||||
QExplicitlySharedDataPointer<CResourceSoundData> m_data;
|
||||
};
|
||||
} // ns
|
||||
|
||||
#endif // guard
|
||||
|
||||
@@ -13,59 +13,56 @@
|
||||
|
||||
using namespace BlackMisc;
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
CResourceSoundSampleProvider::CResourceSoundSampleProvider(const CResourceSound &resourceSound, QObject *parent) :
|
||||
ISampleProvider(parent),
|
||||
m_resourceSound(resourceSound)
|
||||
{
|
||||
CResourceSoundSampleProvider::CResourceSoundSampleProvider(const CResourceSound &resourceSound, QObject *parent) :
|
||||
ISampleProvider(parent),
|
||||
m_resourceSound(resourceSound)
|
||||
const QString on = QStringLiteral("%1 %2").arg(classNameShort(this), resourceSound.getFileName());
|
||||
this->setObjectName(on);
|
||||
m_tempBuffer.resize(m_tempBufferSize);
|
||||
}
|
||||
|
||||
int CResourceSoundSampleProvider::readSamples(QVector<float> &samples, qint64 count)
|
||||
{
|
||||
if (!m_resourceSound.isLoaded()) { return 0; }
|
||||
if (count > m_tempBufferSize)
|
||||
{
|
||||
const QString on = QStringLiteral("%1 %2").arg(classNameShort(this), resourceSound.getFileName());
|
||||
this->setObjectName(on);
|
||||
m_tempBuffer.resize(m_tempBufferSize);
|
||||
qDebug() << "Count too large for temp buffer" << count;
|
||||
return 0;
|
||||
}
|
||||
const qint64 availableSamples = m_resourceSound.audioData().size() - m_position;
|
||||
const qint64 samplesToCopy = qMin(availableSamples, count);
|
||||
samples.clear();
|
||||
samples.fill(0, static_cast<int>(samplesToCopy));
|
||||
|
||||
for (int i = 0; i < samplesToCopy; i++)
|
||||
{
|
||||
m_tempBuffer[i] = m_resourceSound.audioData().at(static_cast<int>(m_position) + i);
|
||||
}
|
||||
|
||||
int CResourceSoundSampleProvider::readSamples(QVector<float> &samples, qint64 count)
|
||||
if (!qFuzzyCompare(m_gain, 1.0))
|
||||
{
|
||||
if (!m_resourceSound.isLoaded()) { return 0; }
|
||||
if (count > m_tempBufferSize)
|
||||
{
|
||||
qDebug() << "Count too large for temp buffer" << count;
|
||||
return 0;
|
||||
}
|
||||
const qint64 availableSamples = m_resourceSound.audioData().size() - m_position;
|
||||
const qint64 samplesToCopy = qMin(availableSamples, count);
|
||||
samples.clear();
|
||||
samples.fill(0, static_cast<int>(samplesToCopy));
|
||||
|
||||
for (int i = 0; i < samplesToCopy; i++)
|
||||
{
|
||||
m_tempBuffer[i] = m_resourceSound.audioData().at(static_cast<int>(m_position) + i);
|
||||
m_tempBuffer[i] = static_cast<float>(m_gain * m_tempBuffer[i]);
|
||||
}
|
||||
|
||||
if (!qFuzzyCompare(m_gain, 1.0))
|
||||
{
|
||||
for (int i = 0; i < samplesToCopy; i++)
|
||||
{
|
||||
m_tempBuffer[i] = static_cast<float>(m_gain * m_tempBuffer[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < samplesToCopy; i++)
|
||||
{
|
||||
samples[i] = m_tempBuffer.at(i);
|
||||
}
|
||||
|
||||
m_position += samplesToCopy;
|
||||
|
||||
if (m_position > availableSamples - 1)
|
||||
{
|
||||
if (m_looping) { m_position = 0; }
|
||||
else { m_isFinished = true; }
|
||||
}
|
||||
|
||||
return static_cast<int>(samplesToCopy);
|
||||
}
|
||||
} // ns
|
||||
|
||||
for (int i = 0; i < samplesToCopy; i++)
|
||||
{
|
||||
samples[i] = m_tempBuffer.at(i);
|
||||
}
|
||||
|
||||
m_position += samplesToCopy;
|
||||
|
||||
if (m_position > availableSamples - 1)
|
||||
{
|
||||
if (m_looping) { m_position = 0; }
|
||||
else { m_isFinished = true; }
|
||||
}
|
||||
|
||||
return static_cast<int>(samplesToCopy);
|
||||
}
|
||||
} // ns
|
||||
|
||||
@@ -13,48 +13,45 @@
|
||||
#include "sampleprovider.h"
|
||||
#include "resourcesound.h"
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
//! A sample provider
|
||||
class BLACKSOUND_EXPORT CResourceSoundSampleProvider : public ISampleProvider
|
||||
{
|
||||
//! A sample provider
|
||||
class BLACKSOUND_EXPORT CResourceSoundSampleProvider : public ISampleProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
//! Ctor
|
||||
CResourceSoundSampleProvider(const CResourceSound &resourceSound, QObject *parent = nullptr);
|
||||
public:
|
||||
//! Ctor
|
||||
CResourceSoundSampleProvider(const CResourceSound &resourceSound, QObject *parent = nullptr);
|
||||
|
||||
//! copydoc ISampleProvider::readSamples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) override;
|
||||
//! copydoc ISampleProvider::readSamples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) override;
|
||||
|
||||
//! copydoc ISampleProvider::isFinished
|
||||
virtual bool isFinished() const override { return m_isFinished; }
|
||||
//! copydoc ISampleProvider::isFinished
|
||||
virtual bool isFinished() const override { return m_isFinished; }
|
||||
|
||||
//! Looping
|
||||
//! @{
|
||||
bool looping() const { return m_looping; }
|
||||
void setLooping(bool looping) { m_looping = looping; }
|
||||
//! @}
|
||||
//! Looping
|
||||
//! @{
|
||||
bool looping() const { return m_looping; }
|
||||
void setLooping(bool looping) { m_looping = looping; }
|
||||
//! @}
|
||||
|
||||
//! Gain
|
||||
//! @{
|
||||
double gain() const { return m_gain; }
|
||||
void setGain(double gain) { m_gain = gain; }
|
||||
//! @}
|
||||
//! Gain
|
||||
//! @{
|
||||
double gain() const { return m_gain; }
|
||||
void setGain(double gain) { m_gain = gain; }
|
||||
//! @}
|
||||
|
||||
private:
|
||||
double m_gain = 1.0;
|
||||
bool m_looping = false;
|
||||
private:
|
||||
double m_gain = 1.0;
|
||||
bool m_looping = false;
|
||||
|
||||
CResourceSound m_resourceSound;
|
||||
qint64 m_position = 0;
|
||||
const int m_tempBufferSize = 24000; //24000 = 500ms (avoid buffer overflow), m_tempBufferSize = 9600; //9600 = 200ms
|
||||
QVector<float> m_tempBuffer;
|
||||
bool m_isFinished = false;
|
||||
};
|
||||
} // ns
|
||||
CResourceSound m_resourceSound;
|
||||
qint64 m_position = 0;
|
||||
const int m_tempBufferSize = 24000; //24000 = 500ms (avoid buffer overflow), m_tempBufferSize = 9600; //9600 = 200ms
|
||||
QVector<float> m_tempBuffer;
|
||||
bool m_isFinished = false;
|
||||
};
|
||||
} // ns
|
||||
|
||||
#endif // guard
|
||||
|
||||
@@ -16,34 +16,31 @@
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
//! Sample provider interface
|
||||
class BLACKSOUND_EXPORT ISampleProvider : public QObject
|
||||
{
|
||||
//! Sample provider interface
|
||||
class BLACKSOUND_EXPORT ISampleProvider : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
//! Ctor
|
||||
ISampleProvider(QObject *parent = nullptr) : QObject(parent) {}
|
||||
public:
|
||||
//! Ctor
|
||||
ISampleProvider(QObject *parent = nullptr) : QObject(parent) {}
|
||||
|
||||
//! Dtor
|
||||
virtual ~ISampleProvider() override {}
|
||||
//! Dtor
|
||||
virtual ~ISampleProvider() override {}
|
||||
|
||||
//! Read samples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) = 0;
|
||||
//! Read samples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) = 0;
|
||||
|
||||
//! Finished?
|
||||
virtual bool isFinished() const { return false; }
|
||||
//! Finished?
|
||||
virtual bool isFinished() const { return false; }
|
||||
|
||||
protected:
|
||||
//! Verbose logs?
|
||||
bool static verbose() { return BlackConfig::CBuildConfig::isLocalDeveloperDebugBuild(); }
|
||||
};
|
||||
protected:
|
||||
//! Verbose logs?
|
||||
bool static verbose() { return BlackConfig::CBuildConfig::isLocalDeveloperDebugBuild(); }
|
||||
};
|
||||
|
||||
} // ns
|
||||
} // ns
|
||||
|
||||
#endif // guard
|
||||
|
||||
@@ -13,56 +13,53 @@
|
||||
using namespace BlackMisc;
|
||||
using namespace BlackMisc::Audio;
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
const Samples &Samples::instance()
|
||||
{
|
||||
const Samples &Samples::instance()
|
||||
static const Samples samples;
|
||||
return samples;
|
||||
}
|
||||
|
||||
Samples::Samples()
|
||||
{
|
||||
this->initSounds();
|
||||
}
|
||||
|
||||
void Samples::initSounds()
|
||||
{
|
||||
const CSettings settings = m_audioSettings.get();
|
||||
QString f = settings.getNotificationFilePath(fnCrackle());
|
||||
if (!m_crackle.isSameFileName(f))
|
||||
{
|
||||
static const Samples samples;
|
||||
return samples;
|
||||
m_crackle = CResourceSound(f);
|
||||
m_crackle.load();
|
||||
}
|
||||
|
||||
Samples::Samples()
|
||||
f = settings.getNotificationFilePath(fnClick());
|
||||
if (!m_click.isSameFileName(f))
|
||||
{
|
||||
this->initSounds();
|
||||
m_click = CResourceSound(f);
|
||||
m_click.load();
|
||||
}
|
||||
|
||||
void Samples::initSounds()
|
||||
f = settings.getNotificationFilePath(fnWhiteNoise());
|
||||
if (!m_whiteNoise.isSameFileName(f))
|
||||
{
|
||||
const CSettings settings = m_audioSettings.get();
|
||||
QString f = settings.getNotificationFilePath(fnCrackle());
|
||||
if (!m_crackle.isSameFileName(f))
|
||||
{
|
||||
m_crackle = CResourceSound(f);
|
||||
m_crackle.load();
|
||||
}
|
||||
|
||||
f = settings.getNotificationFilePath(fnClick());
|
||||
if (!m_click.isSameFileName(f))
|
||||
{
|
||||
m_click = CResourceSound(f);
|
||||
m_click.load();
|
||||
}
|
||||
|
||||
f = settings.getNotificationFilePath(fnWhiteNoise());
|
||||
if (!m_whiteNoise.isSameFileName(f))
|
||||
{
|
||||
m_whiteNoise = CResourceSound(f);
|
||||
m_whiteNoise.load();
|
||||
}
|
||||
|
||||
f = settings.getNotificationFilePath(fnHfWhiteNoise());
|
||||
if (!m_hfWhiteNoise.isSameFileName(f))
|
||||
{
|
||||
m_hfWhiteNoise = CResourceSound(f);
|
||||
m_hfWhiteNoise.load();
|
||||
}
|
||||
m_whiteNoise = CResourceSound(f);
|
||||
m_whiteNoise.load();
|
||||
}
|
||||
|
||||
void Samples::onSettingsChanged()
|
||||
f = settings.getNotificationFilePath(fnHfWhiteNoise());
|
||||
if (!m_hfWhiteNoise.isSameFileName(f))
|
||||
{
|
||||
this->initSounds();
|
||||
m_hfWhiteNoise = CResourceSound(f);
|
||||
m_hfWhiteNoise.load();
|
||||
}
|
||||
} // ns
|
||||
}
|
||||
|
||||
void Samples::onSettingsChanged()
|
||||
{
|
||||
this->initSounds();
|
||||
}
|
||||
} // ns
|
||||
|
||||
@@ -18,58 +18,55 @@
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
//! Sound samples from resources (wav files)
|
||||
class BLACKSOUND_EXPORT Samples : public QObject
|
||||
{
|
||||
//! Sound samples from resources (wav files)
|
||||
class BLACKSOUND_EXPORT Samples : public QObject
|
||||
{
|
||||
public:
|
||||
//! Singleton
|
||||
static const Samples &instance();
|
||||
public:
|
||||
//! Singleton
|
||||
static const Samples &instance();
|
||||
|
||||
//! Avoid to copy
|
||||
Samples(const Samples &) = delete;
|
||||
//! Avoid to copy
|
||||
Samples(const Samples &) = delete;
|
||||
|
||||
//! Various samples (sounds)
|
||||
//! @{
|
||||
const CResourceSound &crackle() const { return m_crackle; }
|
||||
const CResourceSound &click() const { return m_click; }
|
||||
const CResourceSound &whiteNoise() const { return m_whiteNoise; }
|
||||
const CResourceSound &hfWhiteNoise() const { return m_hfWhiteNoise; }
|
||||
//! @}
|
||||
//! Various samples (sounds)
|
||||
//! @{
|
||||
const CResourceSound &crackle() const { return m_crackle; }
|
||||
const CResourceSound &click() const { return m_click; }
|
||||
const CResourceSound &whiteNoise() const { return m_whiteNoise; }
|
||||
const CResourceSound &hfWhiteNoise() const { return m_hfWhiteNoise; }
|
||||
//! @}
|
||||
|
||||
//! Play the click sound
|
||||
bool playClick() const { return m_audioSettings.get().pttClickUp(); }
|
||||
//! Play the click sound
|
||||
bool playClick() const { return m_audioSettings.get().pttClickUp(); }
|
||||
|
||||
//! File names
|
||||
//! @{
|
||||
static const QString &fnCrackle() { static const QString f = "afv_crackle_f32.wav"; return f; }
|
||||
static const QString &fnClick() { static const QString f = "afv_click_f32.wav"; return f; }
|
||||
static const QString &fnWhiteNoise() { static const QString f = "afv_whitenoise_f32.wav"; return f; }
|
||||
static const QString &fnHfWhiteNoise() { static const QString f = "afv_hf_whiteNoise_f32.wav"; return f; }
|
||||
//! @}
|
||||
//! File names
|
||||
//! @{
|
||||
static const QString &fnCrackle() { static const QString f = "afv_crackle_f32.wav"; return f; }
|
||||
static const QString &fnClick() { static const QString f = "afv_click_f32.wav"; return f; }
|
||||
static const QString &fnWhiteNoise() { static const QString f = "afv_whitenoise_f32.wav"; return f; }
|
||||
static const QString &fnHfWhiteNoise() { static const QString f = "afv_hf_whiteNoise_f32.wav"; return f; }
|
||||
//! @}
|
||||
|
||||
private:
|
||||
//! Ctor
|
||||
Samples();
|
||||
private:
|
||||
//! Ctor
|
||||
Samples();
|
||||
|
||||
CResourceSound m_crackle;
|
||||
CResourceSound m_click;
|
||||
CResourceSound m_whiteNoise;
|
||||
CResourceSound m_hfWhiteNoise;
|
||||
CResourceSound m_crackle;
|
||||
CResourceSound m_click;
|
||||
CResourceSound m_whiteNoise;
|
||||
CResourceSound m_hfWhiteNoise;
|
||||
|
||||
BlackMisc::CSetting<BlackMisc::Audio::TSettings> m_audioSettings { this, &Samples::onSettingsChanged };
|
||||
BlackMisc::CSetting<BlackMisc::Audio::TSettings> m_audioSettings { this, &Samples::onSettingsChanged };
|
||||
|
||||
//! Init sounds
|
||||
void initSounds();
|
||||
//! Init sounds
|
||||
void initSounds();
|
||||
|
||||
//! Settings have been changed
|
||||
void onSettingsChanged();
|
||||
};
|
||||
//! Settings have been changed
|
||||
void onSettingsChanged();
|
||||
};
|
||||
|
||||
} // ns
|
||||
} // ns
|
||||
|
||||
#endif // guard
|
||||
|
||||
@@ -9,31 +9,28 @@
|
||||
#include "sawtoothgenerator.h"
|
||||
#include <cmath>
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
CSawToothGenerator::CSawToothGenerator(double frequency, QObject *parent) :
|
||||
ISampleProvider(parent),
|
||||
m_frequency(frequency)
|
||||
{
|
||||
CSawToothGenerator::CSawToothGenerator(double frequency, QObject *parent) :
|
||||
ISampleProvider(parent),
|
||||
m_frequency(frequency)
|
||||
{
|
||||
this->setObjectName("CSawToothGenerator");
|
||||
}
|
||||
this->setObjectName("CSawToothGenerator");
|
||||
}
|
||||
|
||||
int CSawToothGenerator::readSamples(QVector<float> &samples, qint64 count)
|
||||
{
|
||||
samples.clear();
|
||||
samples.fill(0, static_cast<int>(count));
|
||||
int CSawToothGenerator::readSamples(QVector<float> &samples, qint64 count)
|
||||
{
|
||||
samples.clear();
|
||||
samples.fill(0, static_cast<int>(count));
|
||||
|
||||
for (int sampleCount = 0; sampleCount < count; sampleCount++)
|
||||
{
|
||||
double multiple = 2 * m_frequency / m_sampleRate;
|
||||
double sampleSaw = std::fmod((m_nSample * multiple), 2) - 1;
|
||||
double sampleValue = m_gain * sampleSaw;
|
||||
samples[sampleCount] = static_cast<float>(sampleValue);
|
||||
m_nSample++;
|
||||
}
|
||||
return static_cast<int>(count);
|
||||
for (int sampleCount = 0; sampleCount < count; sampleCount++)
|
||||
{
|
||||
double multiple = 2 * m_frequency / m_sampleRate;
|
||||
double sampleSaw = std::fmod((m_nSample * multiple), 2) - 1;
|
||||
double sampleValue = m_gain * sampleSaw;
|
||||
samples[sampleCount] = static_cast<float>(sampleValue);
|
||||
m_nSample++;
|
||||
}
|
||||
} // ns
|
||||
return static_cast<int>(count);
|
||||
}
|
||||
} // ns
|
||||
|
||||
@@ -17,32 +17,29 @@
|
||||
#include <QVector>
|
||||
#include <array>
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
//! Saw tooth generator
|
||||
class BLACKSOUND_EXPORT CSawToothGenerator : public ISampleProvider
|
||||
{
|
||||
//! Saw tooth generator
|
||||
class BLACKSOUND_EXPORT CSawToothGenerator : public ISampleProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
//! Ctor
|
||||
CSawToothGenerator(double frequency, QObject *parent = nullptr);
|
||||
public:
|
||||
//! Ctor
|
||||
CSawToothGenerator(double frequency, QObject *parent = nullptr);
|
||||
|
||||
//! \copydoc ISampleProvider::readSamples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) override;
|
||||
//! \copydoc ISampleProvider::readSamples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) override;
|
||||
|
||||
//! Set the gain
|
||||
void setGain(double gain) { m_gain = gain; }
|
||||
//! Set the gain
|
||||
void setGain(double gain) { m_gain = gain; }
|
||||
|
||||
private:
|
||||
double m_gain = 0.0;
|
||||
double m_frequency = 0.0;
|
||||
double m_sampleRate = 48000;
|
||||
int m_nSample = 0;
|
||||
};
|
||||
} // ns
|
||||
private:
|
||||
double m_gain = 0.0;
|
||||
double m_frequency = 0.0;
|
||||
double m_sampleRate = 48000;
|
||||
int m_nSample = 0;
|
||||
};
|
||||
} // ns
|
||||
|
||||
#endif // guard
|
||||
|
||||
@@ -9,64 +9,61 @@
|
||||
#include "simplecompressoreffect.h"
|
||||
#include <QDebug>
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
CSimpleCompressorEffect::CSimpleCompressorEffect(ISampleProvider *source, QObject *parent) :
|
||||
ISampleProvider(parent),
|
||||
m_sourceStream(source)
|
||||
{
|
||||
CSimpleCompressorEffect::CSimpleCompressorEffect(ISampleProvider *source, QObject *parent) :
|
||||
ISampleProvider(parent),
|
||||
m_sourceStream(source)
|
||||
|
||||
this->setObjectName("CSimpleCompressorEffect");
|
||||
m_timer = new QTimer(this);
|
||||
m_timer->setObjectName(this->objectName() + ":m_timer");
|
||||
|
||||
m_simpleCompressor.setAttack(5.0);
|
||||
m_simpleCompressor.setRelease(10.0);
|
||||
m_simpleCompressor.setSampleRate(48000.0);
|
||||
m_simpleCompressor.setThresh(16.0);
|
||||
m_simpleCompressor.setRatio(6.0);
|
||||
m_simpleCompressor.setMakeUpGain(16.0);
|
||||
m_timer->start(3000);
|
||||
}
|
||||
|
||||
int CSimpleCompressorEffect::readSamples(QVector<float> &samples, qint64 count)
|
||||
{
|
||||
int samplesRead = m_sourceStream->readSamples(samples, count);
|
||||
|
||||
if (m_enabled)
|
||||
{
|
||||
|
||||
this->setObjectName("CSimpleCompressorEffect");
|
||||
m_timer = new QTimer(this);
|
||||
m_timer->setObjectName(this->objectName() + ":m_timer");
|
||||
|
||||
m_simpleCompressor.setAttack(5.0);
|
||||
m_simpleCompressor.setRelease(10.0);
|
||||
m_simpleCompressor.setSampleRate(48000.0);
|
||||
m_simpleCompressor.setThresh(16.0);
|
||||
m_simpleCompressor.setRatio(6.0);
|
||||
m_simpleCompressor.setMakeUpGain(16.0);
|
||||
m_timer->start(3000);
|
||||
}
|
||||
|
||||
int CSimpleCompressorEffect::readSamples(QVector<float> &samples, qint64 count)
|
||||
{
|
||||
int samplesRead = m_sourceStream->readSamples(samples, count);
|
||||
|
||||
if (m_enabled)
|
||||
for (int sample = 0; sample < samplesRead; sample += m_channels)
|
||||
{
|
||||
for (int sample = 0; sample < samplesRead; sample += m_channels)
|
||||
double in1 = samples.at(sample);
|
||||
double in2 = (m_channels == 1) ? 0 : samples.at(sample + 1);
|
||||
m_simpleCompressor.process(in1, in2);
|
||||
samples[sample] = static_cast<float>(in1);
|
||||
if (m_channels > 1)
|
||||
{
|
||||
double in1 = samples.at(sample);
|
||||
double in2 = (m_channels == 1) ? 0 : samples.at(sample + 1);
|
||||
m_simpleCompressor.process(in1, in2);
|
||||
samples[sample] = static_cast<float>(in1);
|
||||
if (m_channels > 1)
|
||||
{
|
||||
samples[sample + 1] = static_cast<float>(in2);
|
||||
}
|
||||
samples[sample + 1] = static_cast<float>(in2);
|
||||
}
|
||||
}
|
||||
return samplesRead;
|
||||
}
|
||||
return samplesRead;
|
||||
}
|
||||
|
||||
void CSimpleCompressorEffect::setEnabled(bool enabled)
|
||||
{
|
||||
m_enabled = enabled;
|
||||
}
|
||||
void CSimpleCompressorEffect::setEnabled(bool enabled)
|
||||
{
|
||||
m_enabled = enabled;
|
||||
}
|
||||
|
||||
void CSimpleCompressorEffect::setMakeUpGain(double gain)
|
||||
{
|
||||
m_simpleCompressor.setMakeUpGain(gain);
|
||||
}
|
||||
void CSimpleCompressorEffect::setMakeUpGain(double gain)
|
||||
{
|
||||
m_simpleCompressor.setMakeUpGain(gain);
|
||||
}
|
||||
|
||||
void CSimpleCompressorEffect::setChannels(int channels)
|
||||
{
|
||||
if (channels < 1) { channels = 1; }
|
||||
else if (channels > 2) { channels = 2; }
|
||||
m_channels = channels;
|
||||
}
|
||||
void CSimpleCompressorEffect::setChannels(int channels)
|
||||
{
|
||||
if (channels < 1) { channels = 1; }
|
||||
else if (channels > 2) { channels = 2; }
|
||||
m_channels = channels;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,39 +18,36 @@
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
//! Compressor effect
|
||||
class BLACKSOUND_EXPORT CSimpleCompressorEffect : public ISampleProvider
|
||||
{
|
||||
//! Compressor effect
|
||||
class BLACKSOUND_EXPORT CSimpleCompressorEffect : public ISampleProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
//! Ctor
|
||||
CSimpleCompressorEffect(ISampleProvider *source, QObject *parent = nullptr);
|
||||
public:
|
||||
//! Ctor
|
||||
CSimpleCompressorEffect(ISampleProvider *source, QObject *parent = nullptr);
|
||||
|
||||
//! \copydoc ISampleProvider::readSamples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) override;
|
||||
//! \copydoc ISampleProvider::readSamples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) override;
|
||||
|
||||
//! Enable
|
||||
void setEnabled(bool enabled);
|
||||
//! Enable
|
||||
void setEnabled(bool enabled);
|
||||
|
||||
//! Set gain
|
||||
void setMakeUpGain(double gain);
|
||||
//! Set gain
|
||||
void setMakeUpGain(double gain);
|
||||
|
||||
//! Set channels 1 or 2
|
||||
void setChannels(int channels);
|
||||
//! Set channels 1 or 2
|
||||
void setChannels(int channels);
|
||||
|
||||
private:
|
||||
QTimer *m_timer = nullptr;
|
||||
ISampleProvider *m_sourceStream = nullptr;
|
||||
bool m_enabled = true;
|
||||
int m_channels = 1;
|
||||
chunkware_simple::SimpleComp m_simpleCompressor;
|
||||
};
|
||||
} // ns
|
||||
private:
|
||||
QTimer *m_timer = nullptr;
|
||||
ISampleProvider *m_sourceStream = nullptr;
|
||||
bool m_enabled = true;
|
||||
int m_channels = 1;
|
||||
chunkware_simple::SimpleComp m_simpleCompressor;
|
||||
};
|
||||
} // ns
|
||||
|
||||
#endif // guard
|
||||
|
||||
@@ -12,36 +12,33 @@
|
||||
|
||||
using namespace BlackMisc;
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
CSinusGenerator::CSinusGenerator(double frequencyHz, QObject *parent) :
|
||||
ISampleProvider(parent),
|
||||
m_frequencyHz(frequencyHz)
|
||||
{
|
||||
CSinusGenerator::CSinusGenerator(double frequencyHz, QObject *parent) :
|
||||
ISampleProvider(parent),
|
||||
m_frequencyHz(frequencyHz)
|
||||
{
|
||||
const QString on = QStringLiteral("%1 frequency: %2Hz").arg(classNameShort(this)).arg(frequencyHz);
|
||||
this->setObjectName(on);
|
||||
}
|
||||
const QString on = QStringLiteral("%1 frequency: %2Hz").arg(classNameShort(this)).arg(frequencyHz);
|
||||
this->setObjectName(on);
|
||||
}
|
||||
|
||||
int CSinusGenerator::readSamples(QVector<float> &samples, qint64 count)
|
||||
{
|
||||
samples.clear();
|
||||
samples.fill(0, static_cast<int>(count));
|
||||
int CSinusGenerator::readSamples(QVector<float> &samples, qint64 count)
|
||||
{
|
||||
samples.clear();
|
||||
samples.fill(0, static_cast<int>(count));
|
||||
|
||||
for (int sampleCount = 0; sampleCount < count; sampleCount++)
|
||||
{
|
||||
const double multiple = s_twoPi * m_frequencyHz / m_sampleRate;
|
||||
const double sampleValue = m_gain * qSin(m_nSample * multiple);
|
||||
samples[sampleCount] = static_cast<float>(sampleValue);
|
||||
m_nSample++;
|
||||
}
|
||||
return static_cast<int>(count);
|
||||
}
|
||||
|
||||
void CSinusGenerator::setFrequency(double frequencyHz)
|
||||
for (int sampleCount = 0; sampleCount < count; sampleCount++)
|
||||
{
|
||||
m_frequencyHz = frequencyHz;
|
||||
const double multiple = s_twoPi * m_frequencyHz / m_sampleRate;
|
||||
const double sampleValue = m_gain * qSin(m_nSample * multiple);
|
||||
samples[sampleCount] = static_cast<float>(sampleValue);
|
||||
m_nSample++;
|
||||
}
|
||||
} // ns
|
||||
return static_cast<int>(count);
|
||||
}
|
||||
|
||||
void CSinusGenerator::setFrequency(double frequencyHz)
|
||||
{
|
||||
m_frequencyHz = frequencyHz;
|
||||
}
|
||||
} // ns
|
||||
|
||||
@@ -16,36 +16,33 @@
|
||||
|
||||
#include <QtMath>
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
//! Saw tooth generator
|
||||
class BLACKSOUND_EXPORT CSinusGenerator : public ISampleProvider
|
||||
{
|
||||
//! Saw tooth generator
|
||||
class BLACKSOUND_EXPORT CSinusGenerator : public ISampleProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
//! Ctor
|
||||
CSinusGenerator(double frequencyHz, QObject *parent = nullptr);
|
||||
public:
|
||||
//! Ctor
|
||||
CSinusGenerator(double frequencyHz, QObject *parent = nullptr);
|
||||
|
||||
//! \copydoc ISampleProvider::readSamples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) override;
|
||||
//! \copydoc ISampleProvider::readSamples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) override;
|
||||
|
||||
//! Set the gain
|
||||
void setGain(double gain) { m_gain = gain; }
|
||||
//! Set the gain
|
||||
void setGain(double gain) { m_gain = gain; }
|
||||
|
||||
//! Set frequency in Hz
|
||||
void setFrequency(double frequencyHz);
|
||||
//! Set frequency in Hz
|
||||
void setFrequency(double frequencyHz);
|
||||
|
||||
private:
|
||||
double m_gain = 0.0;
|
||||
double m_frequencyHz = 0.0;
|
||||
double m_sampleRate = 48000;
|
||||
int m_nSample = 0;
|
||||
static constexpr double s_twoPi = 2 * M_PI;
|
||||
};
|
||||
} // ns
|
||||
private:
|
||||
double m_gain = 0.0;
|
||||
double m_frequencyHz = 0.0;
|
||||
double m_sampleRate = 48000;
|
||||
int m_nSample = 0;
|
||||
static constexpr double s_twoPi = 2 * M_PI;
|
||||
};
|
||||
} // ns
|
||||
|
||||
#endif // guard
|
||||
|
||||
@@ -13,37 +13,34 @@
|
||||
|
||||
using namespace BlackMisc;
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
CVolumeSampleProvider::CVolumeSampleProvider(ISampleProvider *sourceProvider, QObject *parent) :
|
||||
ISampleProvider(parent),
|
||||
m_sourceProvider(sourceProvider)
|
||||
{
|
||||
CVolumeSampleProvider::CVolumeSampleProvider(ISampleProvider *sourceProvider, QObject *parent) :
|
||||
ISampleProvider(parent),
|
||||
m_sourceProvider(sourceProvider)
|
||||
{
|
||||
Q_ASSERT_X(sourceProvider, Q_FUNC_INFO, "Need source provider");
|
||||
const QString on = QStringLiteral("%1 with source: %2").arg(classNameShort(this), sourceProvider->objectName());
|
||||
this->setObjectName(on);
|
||||
}
|
||||
Q_ASSERT_X(sourceProvider, Q_FUNC_INFO, "Need source provider");
|
||||
const QString on = QStringLiteral("%1 with source: %2").arg(classNameShort(this), sourceProvider->objectName());
|
||||
this->setObjectName(on);
|
||||
}
|
||||
|
||||
int CVolumeSampleProvider::readSamples(QVector<float> &samples, qint64 count)
|
||||
int CVolumeSampleProvider::readSamples(QVector<float> &samples, qint64 count)
|
||||
{
|
||||
const int samplesRead = m_sourceProvider->readSamples(samples, count);
|
||||
if (!qFuzzyCompare(m_gainRatio, 1.0))
|
||||
{
|
||||
const int samplesRead = m_sourceProvider->readSamples(samples, count);
|
||||
if (!qFuzzyCompare(m_gainRatio, 1.0))
|
||||
for (int n = 0; n < samplesRead; n++)
|
||||
{
|
||||
for (int n = 0; n < samplesRead; n++)
|
||||
{
|
||||
samples[n] = static_cast<float>(m_gainRatio * samples[n]);
|
||||
}
|
||||
samples[n] = static_cast<float>(m_gainRatio * samples[n]);
|
||||
}
|
||||
return samplesRead;
|
||||
}
|
||||
return samplesRead;
|
||||
}
|
||||
|
||||
bool CVolumeSampleProvider::setGainRatio(double volume)
|
||||
{
|
||||
const bool changed = !qFuzzyCompare(m_gainRatio, volume);
|
||||
if (changed) { m_gainRatio = volume; }
|
||||
return changed;
|
||||
}
|
||||
} // ns
|
||||
bool CVolumeSampleProvider::setGainRatio(double volume)
|
||||
{
|
||||
const bool changed = !qFuzzyCompare(m_gainRatio, volume);
|
||||
if (changed) { m_gainRatio = volume; }
|
||||
return changed;
|
||||
}
|
||||
} // ns
|
||||
|
||||
@@ -14,38 +14,35 @@
|
||||
#include "blacksound/blacksoundexport.h"
|
||||
#include "blacksound/sampleprovider/sampleprovider.h"
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::SampleProvider
|
||||
{
|
||||
namespace SampleProvider
|
||||
//! Pink noise generator
|
||||
class BLACKSOUND_EXPORT CVolumeSampleProvider : public ISampleProvider
|
||||
{
|
||||
//! Pink noise generator
|
||||
class BLACKSOUND_EXPORT CVolumeSampleProvider : public ISampleProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
//! Noise generator
|
||||
CVolumeSampleProvider(ISampleProvider *sourceProvider, QObject *parent = nullptr);
|
||||
public:
|
||||
//! Noise generator
|
||||
CVolumeSampleProvider(ISampleProvider *sourceProvider, QObject *parent = nullptr);
|
||||
|
||||
//! \copydoc ISampleProvider::readSamples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) override;
|
||||
//! \copydoc ISampleProvider::readSamples
|
||||
virtual int readSamples(QVector<float> &samples, qint64 count) override;
|
||||
|
||||
//! Gain ratio, value a amplitude need to be multiplied with
|
||||
//! \see http://www.sengpielaudio.com/calculator-amplification.htm
|
||||
//! \remark gain ratio is voltage ratio/or amplitude ratio, something between 0.001-7.95 for -60dB to 80dB
|
||||
//! @{
|
||||
double getGainRatio() const { return m_gainRatio; }
|
||||
bool setGainRatio(double gainRatio);
|
||||
//! @}
|
||||
// those used to be the original function names
|
||||
// double volume() const { return m_volume; }
|
||||
// bool setVolume(double volume);
|
||||
//! Gain ratio, value a amplitude need to be multiplied with
|
||||
//! \see http://www.sengpielaudio.com/calculator-amplification.htm
|
||||
//! \remark gain ratio is voltage ratio/or amplitude ratio, something between 0.001-7.95 for -60dB to 80dB
|
||||
//! @{
|
||||
double getGainRatio() const { return m_gainRatio; }
|
||||
bool setGainRatio(double gainRatio);
|
||||
//! @}
|
||||
// those used to be the original function names
|
||||
// double volume() const { return m_volume; }
|
||||
// bool setVolume(double volume);
|
||||
|
||||
private:
|
||||
ISampleProvider *m_sourceProvider = nullptr;
|
||||
double m_gainRatio = 1.0;
|
||||
};
|
||||
} // ns
|
||||
private:
|
||||
ISampleProvider *m_sourceProvider = nullptr;
|
||||
double m_gainRatio = 1.0;
|
||||
};
|
||||
} // ns
|
||||
|
||||
#endif // guard
|
||||
|
||||
@@ -12,140 +12,137 @@
|
||||
#include "wavfile.h"
|
||||
// #include "utils.h"
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::Wav
|
||||
{
|
||||
namespace Wav
|
||||
//! WAV chunk
|
||||
struct chunk
|
||||
{
|
||||
//! WAV chunk
|
||||
struct chunk
|
||||
{
|
||||
char id[4]; //!< chunk id
|
||||
quint32 size; //!< chunk size
|
||||
};
|
||||
char id[4]; //!< chunk id
|
||||
quint32 size; //!< chunk size
|
||||
};
|
||||
|
||||
//! RIFF header
|
||||
struct RIFFHeader
|
||||
{
|
||||
chunk descriptor; //!< "RIFF"
|
||||
char type[4]; //!< "WAVE"
|
||||
};
|
||||
//! RIFF header
|
||||
struct RIFFHeader
|
||||
{
|
||||
chunk descriptor; //!< "RIFF"
|
||||
char type[4]; //!< "WAVE"
|
||||
};
|
||||
|
||||
//! WAVE header
|
||||
struct WAVEHeader
|
||||
{
|
||||
chunk descriptor; //!< chunk descriptor
|
||||
quint16 audioFormat; //!< audio format, e.g. 0x0001 => PCM
|
||||
quint16 numChannels; //!< number of channels
|
||||
quint32 sampleRate; //!< sample rate
|
||||
quint32 byteRate; //!< byte rate
|
||||
quint16 blockAlign; //!< block align
|
||||
quint16 bitsPerSample; //!< bits per sample
|
||||
};
|
||||
//! WAVE header
|
||||
struct WAVEHeader
|
||||
{
|
||||
chunk descriptor; //!< chunk descriptor
|
||||
quint16 audioFormat; //!< audio format, e.g. 0x0001 => PCM
|
||||
quint16 numChannels; //!< number of channels
|
||||
quint32 sampleRate; //!< sample rate
|
||||
quint32 byteRate; //!< byte rate
|
||||
quint16 blockAlign; //!< block align
|
||||
quint16 bitsPerSample; //!< bits per sample
|
||||
};
|
||||
|
||||
//! Data header
|
||||
struct DATAHeader
|
||||
{
|
||||
chunk descriptor; //!< chunk descriptor
|
||||
};
|
||||
//! Data header
|
||||
struct DATAHeader
|
||||
{
|
||||
chunk descriptor; //!< chunk descriptor
|
||||
};
|
||||
|
||||
//! Combined header
|
||||
struct CombinedHeader
|
||||
{
|
||||
RIFFHeader riff; //!< RIFF header
|
||||
WAVEHeader wave; //!< WAVE header
|
||||
};
|
||||
//! Combined header
|
||||
struct CombinedHeader
|
||||
{
|
||||
RIFFHeader riff; //!< RIFF header
|
||||
WAVEHeader wave; //!< WAVE header
|
||||
};
|
||||
|
||||
CWavFile::CWavFile(QObject *parent) :
|
||||
QFile(parent),
|
||||
m_headerLength(0)
|
||||
{ }
|
||||
CWavFile::CWavFile(QObject *parent) :
|
||||
QFile(parent),
|
||||
m_headerLength(0)
|
||||
{ }
|
||||
|
||||
bool CWavFile::open(const QString &fileName)
|
||||
{
|
||||
this->close();
|
||||
this->setFileName(fileName);
|
||||
return QFile::open(QIODevice::ReadOnly) && readHeader();
|
||||
}
|
||||
bool CWavFile::open(const QString &fileName)
|
||||
{
|
||||
this->close();
|
||||
this->setFileName(fileName);
|
||||
return QFile::open(QIODevice::ReadOnly) && readHeader();
|
||||
}
|
||||
|
||||
const QAudioFormat &CWavFile::fileFormat() const
|
||||
{
|
||||
return m_fileFormat;
|
||||
}
|
||||
const QAudioFormat &CWavFile::fileFormat() const
|
||||
{
|
||||
return m_fileFormat;
|
||||
}
|
||||
|
||||
qint64 CWavFile::headerLength() const
|
||||
{
|
||||
return m_headerLength;
|
||||
}
|
||||
qint64 CWavFile::headerLength() const
|
||||
{
|
||||
return m_headerLength;
|
||||
}
|
||||
|
||||
bool CWavFile::readHeader()
|
||||
bool CWavFile::readHeader()
|
||||
{
|
||||
seek(0);
|
||||
CombinedHeader header;
|
||||
DATAHeader dataHeader;
|
||||
bool result = read(reinterpret_cast<char *>(&header), sizeof(CombinedHeader)) == sizeof(CombinedHeader);
|
||||
if (result)
|
||||
{
|
||||
seek(0);
|
||||
CombinedHeader header;
|
||||
DATAHeader dataHeader;
|
||||
bool result = read(reinterpret_cast<char *>(&header), sizeof(CombinedHeader)) == sizeof(CombinedHeader);
|
||||
if (result)
|
||||
if ((memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0
|
||||
|| memcmp(&header.riff.descriptor.id, "RIFX", 4) == 0)
|
||||
&& memcmp(&header.riff.type, "WAVE", 4) == 0
|
||||
&& memcmp(&header.wave.descriptor.id, "fmt ", 4) == 0
|
||||
&& (header.wave.audioFormat == 1 || header.wave.audioFormat == 0 || header.wave.audioFormat == 3))
|
||||
{
|
||||
if ((memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0
|
||||
|| memcmp(&header.riff.descriptor.id, "RIFX", 4) == 0)
|
||||
&& memcmp(&header.riff.type, "WAVE", 4) == 0
|
||||
&& memcmp(&header.wave.descriptor.id, "fmt ", 4) == 0
|
||||
&& (header.wave.audioFormat == 1 || header.wave.audioFormat == 0 || header.wave.audioFormat == 3))
|
||||
// Read off remaining header information
|
||||
if (qFromLittleEndian<quint32>(header.wave.descriptor.size) > sizeof(WAVEHeader))
|
||||
{
|
||||
// Read off remaining header information
|
||||
if (qFromLittleEndian<quint32>(header.wave.descriptor.size) > sizeof(WAVEHeader))
|
||||
{
|
||||
// Extended data available
|
||||
quint16 extraFormatBytes;
|
||||
if (peek((char *)&extraFormatBytes, sizeof(quint16)) != sizeof(quint16))
|
||||
return false;
|
||||
const qint64 throwAwayBytes = sizeof(quint16) + qFromLittleEndian<quint16>(extraFormatBytes);
|
||||
if (read(throwAwayBytes).size() != throwAwayBytes)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (read((char *)&dataHeader, sizeof(DATAHeader)) != sizeof(DATAHeader))
|
||||
// Extended data available
|
||||
quint16 extraFormatBytes;
|
||||
if (peek((char *)&extraFormatBytes, sizeof(quint16)) != sizeof(quint16))
|
||||
return false;
|
||||
const qint64 throwAwayBytes = sizeof(quint16) + qFromLittleEndian<quint16>(extraFormatBytes);
|
||||
if (read(throwAwayBytes).size() != throwAwayBytes)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Establish format
|
||||
if (memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0)
|
||||
m_fileFormat.setByteOrder(QAudioFormat::LittleEndian);
|
||||
else
|
||||
m_fileFormat.setByteOrder(QAudioFormat::BigEndian);
|
||||
if (read((char *)&dataHeader, sizeof(DATAHeader)) != sizeof(DATAHeader))
|
||||
return false;
|
||||
|
||||
int bps = qFromLittleEndian<quint16>(header.wave.bitsPerSample);
|
||||
m_fileFormat.setChannelCount(qFromLittleEndian<quint16>(header.wave.numChannels));
|
||||
m_fileFormat.setCodec("audio/pcm");
|
||||
m_fileFormat.setSampleRate(qFromLittleEndian<quint32>(header.wave.sampleRate));
|
||||
m_fileFormat.setSampleSize(qFromLittleEndian<quint16>(header.wave.bitsPerSample));
|
||||
// Establish format
|
||||
if (memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0)
|
||||
m_fileFormat.setByteOrder(QAudioFormat::LittleEndian);
|
||||
else
|
||||
m_fileFormat.setByteOrder(QAudioFormat::BigEndian);
|
||||
|
||||
if (header.wave.audioFormat == 1 || header.wave.audioFormat == 0)
|
||||
{
|
||||
m_fileFormat.setSampleType(bps == 8 ? QAudioFormat::UnSignedInt : QAudioFormat::SignedInt);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_fileFormat.setSampleType(QAudioFormat::Float);
|
||||
}
|
||||
int bps = qFromLittleEndian<quint16>(header.wave.bitsPerSample);
|
||||
m_fileFormat.setChannelCount(qFromLittleEndian<quint16>(header.wave.numChannels));
|
||||
m_fileFormat.setCodec("audio/pcm");
|
||||
m_fileFormat.setSampleRate(qFromLittleEndian<quint32>(header.wave.sampleRate));
|
||||
m_fileFormat.setSampleSize(qFromLittleEndian<quint16>(header.wave.bitsPerSample));
|
||||
|
||||
if (header.wave.audioFormat == 1 || header.wave.audioFormat == 0)
|
||||
{
|
||||
m_fileFormat.setSampleType(bps == 8 ? QAudioFormat::UnSignedInt : QAudioFormat::SignedInt);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = false;
|
||||
m_fileFormat.setSampleType(QAudioFormat::Float);
|
||||
}
|
||||
}
|
||||
m_headerLength = pos();
|
||||
|
||||
if (memcmp(&dataHeader.descriptor.id, "data", 4) == 0)
|
||||
{
|
||||
const qint32 dataLength = qFromLittleEndian<qint32>(dataHeader.descriptor.size);
|
||||
m_audioData = read(dataLength);
|
||||
if (m_audioData.size() != dataLength)
|
||||
{
|
||||
m_audioData.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
else
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
} // ns
|
||||
m_headerLength = pos();
|
||||
|
||||
if (memcmp(&dataHeader.descriptor.id, "data", 4) == 0)
|
||||
{
|
||||
const qint32 dataLength = qFromLittleEndian<qint32>(dataHeader.descriptor.size);
|
||||
m_audioData = read(dataLength);
|
||||
if (m_audioData.size() != dataLength)
|
||||
{
|
||||
m_audioData.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // ns
|
||||
|
||||
@@ -15,40 +15,37 @@
|
||||
#include <QFile>
|
||||
#include <QAudioFormat>
|
||||
|
||||
namespace BlackSound
|
||||
namespace BlackSound::Wav
|
||||
{
|
||||
namespace Wav
|
||||
//! * WAV file
|
||||
class CWavFile : public QFile
|
||||
{
|
||||
//! * WAV file
|
||||
class CWavFile : public QFile
|
||||
{
|
||||
public:
|
||||
//! Ctor
|
||||
CWavFile(QObject *parent = nullptr);
|
||||
public:
|
||||
//! Ctor
|
||||
CWavFile(QObject *parent = nullptr);
|
||||
|
||||
//! Standard open
|
||||
using QFile::open;
|
||||
//! Standard open
|
||||
using QFile::open;
|
||||
|
||||
//! Open
|
||||
bool open(const QString &fileName);
|
||||
//! Open
|
||||
bool open(const QString &fileName);
|
||||
|
||||
//! Audio format
|
||||
const QAudioFormat &fileFormat() const;
|
||||
//! Audio format
|
||||
const QAudioFormat &fileFormat() const;
|
||||
|
||||
//! Header length
|
||||
qint64 headerLength() const;
|
||||
//! Header length
|
||||
qint64 headerLength() const;
|
||||
|
||||
//! The audio data
|
||||
const QByteArray &audioData() const { return m_audioData; }
|
||||
//! The audio data
|
||||
const QByteArray &audioData() const { return m_audioData; }
|
||||
|
||||
private:
|
||||
bool readHeader();
|
||||
private:
|
||||
bool readHeader();
|
||||
|
||||
QAudioFormat m_fileFormat;
|
||||
qint64 m_headerLength;
|
||||
QByteArray m_audioData;
|
||||
};
|
||||
} // ns
|
||||
QAudioFormat m_fileFormat;
|
||||
qint64 m_headerLength;
|
||||
QByteArray m_audioData;
|
||||
};
|
||||
} // ns
|
||||
|
||||
#endif // guard
|
||||
|
||||
Reference in New Issue
Block a user