mirror of
https://github.com/overte-org/overte.git
synced 2025-08-13 05:00:25 +02:00
improve audio scope
This commit is contained in:
parent
e070033ce4
commit
b481a6060e
4 changed files with 112 additions and 90 deletions
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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__);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in a new issue