improve audio scope

This commit is contained in:
luiscuenca 2017-11-22 18:15:42 -07:00
parent e070033ce4
commit b481a6060e
4 changed files with 112 additions and 90 deletions

View file

@ -9,6 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <qvector2d.h>
#include <limits>
#include <AudioClient.h>
@ -28,6 +29,8 @@ static const unsigned int SCOPE_HEIGHT = 2 * 15 * MULTIPLIER_SCOPE_HEIGHT;
AudioScope::AudioScope() :
_isEnabled(false),
_isPaused(false),
_isTriggered(false),
_autoTrigger(false),
_scopeInputOffset(0),
_scopeOutputOffset(0),
_framesPerScope(DEFAULT_FRAMES_PER_SCOPE),
@ -108,63 +111,19 @@ void AudioScope::freeScope() {
}
}
void AudioScope::render(RenderArgs* renderArgs, int width, int height) {
if (!_isEnabled) {
return;
}
static const glm::vec4 backgroundColor = { 0.4f, 0.4f, 0.4f, 0.6f };
static const glm::vec4 gridColor = { 0.7f, 0.7f, 0.7f, 1.0f };
static const glm::vec4 inputColor = { 0.3f, 1.0f, 0.3f, 1.0f };
static const glm::vec4 outputLeftColor = { 1.0f, 0.3f, 0.3f, 1.0f };
static const glm::vec4 outputRightColor = { 0.3f, 0.3f, 1.0f, 1.0f };
static const int gridCols = 2;
int gridRows = _framesPerScope;
int x = (width - (int)SCOPE_WIDTH) / 2;
int y = (height - (int)SCOPE_HEIGHT) / 2;
int w = (int)SCOPE_WIDTH;
int h = (int)SCOPE_HEIGHT;
gpu::Batch& batch = *renderArgs->_batch;
auto geometryCache = DependencyManager::get<GeometryCache>();
// Grid uses its own pipeline, so draw it before setting another
const float GRID_EDGE = 0.005f;
geometryCache->renderGrid(batch, glm::vec2(x, y), glm::vec2(x + w, y + h),
gridRows, gridCols, GRID_EDGE, gridColor, true, _audioScopeGrid);
geometryCache->useSimpleDrawPipeline(batch);
auto textureCache = DependencyManager::get<TextureCache>();
batch.setResourceTexture(0, textureCache->getWhiteTexture());
// FIXME - do we really need to reset this here? we know that we're called inside of ApplicationOverlay::renderOverlays
// which already set up our batch for us to have these settings
mat4 legacyProjection = glm::ortho<float>(0, width, height, 0, -1000, 1000);
batch.setProjectionTransform(legacyProjection);
batch.setModelTransform(Transform());
batch.resetViewTransform();
geometryCache->renderQuad(batch, x, y, w, h, backgroundColor, _audioScopeBackground);
renderLineStrip(batch, _inputID, inputColor, x, y, _samplesPerScope, _scopeInputOffset, _scopeInput);
renderLineStrip(batch, _outputLeftID, outputLeftColor, x, y, _samplesPerScope, _scopeOutputOffset, _scopeOutputLeft);
renderLineStrip(batch, _outputRightD, outputRightColor, x, y, _samplesPerScope, _scopeOutputOffset, _scopeOutputRight);
void AudioScope::setTriggerValues(float x, float y) {
_triggerValues.x = x;
_triggerValues.y = y;
}
void AudioScope::renderLineStrip(gpu::Batch& batch, int id, const glm::vec4& color, int x, int y, int n, int offset, const QByteArray* byteArray) {
QVector<int> AudioScope::getScopeVector(const QByteArray* byteArray, int offset) {
int16_t sample;
int16_t* samples = ((int16_t*) byteArray->data()) + offset;
QVector<int> points;
if (!_isEnabled || byteArray == NULL) return points;
int16_t* samples = ((int16_t*)byteArray->data()) + offset;
int numSamplesToAverage = _framesPerScope / DEFAULT_FRAMES_PER_SCOPE;
int count = (n - offset) / numSamplesToAverage;
int remainder = (n - offset) % numSamplesToAverage;
y += SCOPE_HEIGHT / 2;
auto geometryCache = DependencyManager::get<GeometryCache>();
QVector<glm::vec2> points;
int count = (_samplesPerScope - offset) / numSamplesToAverage;
int remainder = (_samplesPerScope - offset) % numSamplesToAverage;
// Compute and draw the sample averages from the offset position
for (int i = count; --i >= 0; ) {
@ -173,7 +132,7 @@ void AudioScope::renderLineStrip(gpu::Batch& batch, int id, const glm::vec4& col
sample += *samples++;
}
sample /= numSamplesToAverage;
points << glm::vec2(x++, y - sample);
points << -sample;
}
// Compute and draw the sample average across the wrap boundary
@ -182,16 +141,17 @@ void AudioScope::renderLineStrip(gpu::Batch& batch, int id, const glm::vec4& col
for (int j = remainder; --j >= 0; ) {
sample += *samples++;
}
samples = (int16_t*) byteArray->data();
samples = (int16_t*)byteArray->data();
for (int j = numSamplesToAverage - remainder; --j >= 0; ) {
sample += *samples++;
}
sample /= numSamplesToAverage;
points << glm::vec2(x++, y - sample);
} else {
samples = (int16_t*) byteArray->data();
points << -sample;
}
else {
samples = (int16_t*)byteArray->data();
}
// Compute and draw the sample average from the beginning to the offset
@ -202,12 +162,46 @@ void AudioScope::renderLineStrip(gpu::Batch& batch, int id, const glm::vec4& col
sample += *samples++;
}
sample /= numSamplesToAverage;
points << glm::vec2(x++, y - sample);
points << -sample;
}
return points;
}
bool AudioScope::shouldTrigger(const QVector<int>& scope) {
int threshold = 4;
if (_autoTrigger && _triggerValues.x < scope.size()) {
for (int i = -2*threshold; i < +2*threshold*2; i++) {
int idx = _triggerValues.x + i;
idx = (idx < 0) ? 0 : (idx < scope.size() ? idx : scope.size() - 1);
int dif = abs(_triggerValues.y - scope[idx]);
if (dif < threshold) {
return true;
}
}
}
return false;
}
void AudioScope::computeInputData() {
_scopeInputData = getScopeVector(_scopeInput, _scopeInputOffset);
if (shouldTrigger(_scopeInputData)) {
_triggerInputData = _scopeInputData;
_isTriggered = true;
}
}
void AudioScope::computeOutputData() {
_scopeOutputLeftData = getScopeVector(_scopeOutputLeft, _scopeOutputOffset);
if (shouldTrigger(_scopeOutputLeftData)) {
_triggerOutputLeftData = _scopeOutputLeftData;
_isTriggered = true;
}
_scopeOutputRightData = getScopeVector(_scopeOutputRight, _scopeOutputOffset);
if (shouldTrigger(_scopeOutputRightData)) {
_triggerOutputRightData = _scopeOutputRightData;
_isTriggered = true;
}
geometryCache->updateVertices(id, points, color);
geometryCache->renderVertices(batch, gpu::LINE_STRIP, id);
}
int AudioScope::addBufferToScope(QByteArray* byteArray, int frameOffset, const int16_t* source, int sourceSamplesPerChannel,
@ -231,7 +225,7 @@ int AudioScope::addBufferToScope(QByteArray* byteArray, int frameOffset, const i
}
int AudioScope::addSilenceToScope(QByteArray* byteArray, int frameOffset, int silentSamples) {
// Short int pointer to mapped samples in byte array
int16_t* destination = (int16_t*)byteArray->data();
@ -271,6 +265,7 @@ void AudioScope::addStereoSamplesToScope(const QByteArray& samples) {
_scopeOutputOffset = addBufferToScope(_scopeOutputRight, _scopeOutputOffset, samplesData, samplesPerChannel, 1, AudioConstants::STEREO);
_scopeLastFrame = samples.right(AudioConstants::NETWORK_FRAME_BYTES_STEREO);
computeOutputData();
}
void AudioScope::addLastFrameRepeatedWithFadeToScope(int samplesPerChannel) {
@ -302,4 +297,5 @@ void AudioScope::addInputToScope(const QByteArray& inputSamples) {
_scopeInputOffset = addBufferToScope(_scopeInput, _scopeInputOffset,
reinterpret_cast<const int16_t*>(inputSamples.data()),
inputSamples.size() / sizeof(int16_t), INPUT_AUDIO_CHANNEL, NUM_INPUT_CHANNELS);
computeInputData();
}

View file

@ -24,14 +24,21 @@
class AudioScope : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
Q_PROPERTY(QVector<int> scopeInput READ getScopeInput)
Q_PROPERTY(QVector<int> scopeOutputLeft READ getScopeOutputLeft)
Q_PROPERTY(QVector<int> scopeOutputRight READ getScopeOutputRight)
Q_PROPERTY(QVector<int> triggerInput READ getTriggerInput)
Q_PROPERTY(QVector<int> triggerOutputLeft READ getTriggerOutputLeft)
Q_PROPERTY(QVector<int> triggerOutputRight READ getTriggerOutputRight)
public:
// Audio scope methods for allocation/deallocation
void allocateScope();
void freeScope();
void reallocateScope(int frames);
void render(RenderArgs* renderArgs, int width, int height);
public slots:
void toggle() { setVisible(!_isEnabled); }
void setVisible(bool visible);
@ -41,9 +48,33 @@ public slots:
void setPause(bool paused) { _isPaused = paused; }
bool getPause() { return _isPaused; }
void toggleTrigger() { _autoTrigger = !_autoTrigger; }
bool getAutoTrigger() { return _autoTrigger; }
void setAutoTrigger(bool autoTrigger) {
_isTriggered = false;
_autoTrigger = autoTrigger;
}
void setTriggered(bool triggered) { _isTriggered = triggered; }
bool getTriggered() { return _isTriggered; }
void selectAudioScopeFiveFrames();
void selectAudioScopeTwentyFrames();
void selectAudioScopeFiftyFrames();
void setEnabled(bool enabled) { _isEnabled = enabled; }
bool getEnabled() { return _isEnabled; }
const int getAudioScopeBackground() { return _audioScopeBackground; }
QVector<int> getScopeInput() { return _scopeInputData; };
QVector<int> getScopeOutputLeft() { return _scopeOutputLeftData; };
QVector<int> getScopeOutputRight() { return _scopeOutputRightData; };
QVector<int> getTriggerInput() { return _triggerInputData; };
QVector<int> getTriggerOutputLeft() { return _triggerOutputLeftData; };
QVector<int> getTriggerOutputRight() { return _triggerOutputRightData; };
void setTriggerValues(float x, float y);
protected:
AudioScope();
@ -55,16 +86,21 @@ private slots:
void addInputToScope(const QByteArray& inputSamples);
private:
// Audio scope methods for rendering
void renderLineStrip(gpu::Batch& batch, int id, const glm::vec4& color, int x, int y, int n, int offset, const QByteArray* byteArray);
// Audio scope methods for data acquisition
int addBufferToScope(QByteArray* byteArray, int frameOffset, const int16_t* source, int sourceSamples,
unsigned int sourceChannel, unsigned int sourceNumberOfChannels, float fade = 1.0f);
int addSilenceToScope(QByteArray* byteArray, int frameOffset, int silentSamples);
QVector<int> getScopeVector(const QByteArray* scope, int offset);
bool shouldTrigger(const QVector<int>& scope);
void computeInputData();
void computeOutputData();
bool _isEnabled;
bool _isPaused;
bool _isTriggered;
int _scopeInputOffset;
int _scopeOutputOffset;
int _framesPerScope;
@ -73,6 +109,17 @@ private:
QByteArray* _scopeOutputLeft;
QByteArray* _scopeOutputRight;
QByteArray _scopeLastFrame;
QVector<int> _scopeInputData;
QVector<int> _scopeOutputLeftData;
QVector<int> _scopeOutputRightData;
QVector<int> _triggerInputData;
QVector<int> _triggerOutputLeftData;
QVector<int> _triggerOutputRightData;
bool _autoTrigger;
glm::vec2 _triggerValues;
int _audioScopeBackground;
int _audioScopeGrid;

View file

@ -82,7 +82,6 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) {
// Now render the overlay components together into a single texture
renderDomainConnectionStatusBorder(renderArgs); // renders the connected domain line
renderAudioScope(renderArgs); // audio scope in the very back - NOTE: this is the debug audio scope, not the VU meter
renderOverlays(renderArgs); // renders Scripts Overlay and AudioScope
renderQmlUi(renderArgs); // renders a unit quad with the QML UI texture, and the text overlays from scripts
});
@ -118,25 +117,6 @@ void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) {
geometryCache->renderUnitQuad(batch, glm::vec4(1), _qmlGeometryId);
}
void ApplicationOverlay::renderAudioScope(RenderArgs* renderArgs) {
PROFILE_RANGE(app, __FUNCTION__);
gpu::Batch& batch = *renderArgs->_batch;
auto geometryCache = DependencyManager::get<GeometryCache>();
geometryCache->useSimpleDrawPipeline(batch);
auto textureCache = DependencyManager::get<TextureCache>();
batch.setResourceTexture(0, textureCache->getWhiteTexture());
int width = renderArgs->_viewport.z;
int height = renderArgs->_viewport.w;
mat4 legacyProjection = glm::ortho<float>(0, width, height, 0, ORTHO_NEAR_CLIP, ORTHO_FAR_CLIP);
batch.setProjectionTransform(legacyProjection);
batch.setModelTransform(Transform());
batch.resetViewTransform();
// Render the audio scope
DependencyManager::get<AudioScope>()->render(renderArgs, width, height);
}
void ApplicationOverlay::renderOverlays(RenderArgs* renderArgs) {
PROFILE_RANGE(app, __FUNCTION__);

View file

@ -32,7 +32,6 @@ private:
void renderStatsAndLogs(RenderArgs* renderArgs);
void renderDomainConnectionStatusBorder(RenderArgs* renderArgs);
void renderQmlUi(RenderArgs* renderArgs);
void renderAudioScope(RenderArgs* renderArgs);
void renderOverlays(RenderArgs* renderArgs);
void buildFramebufferObject();