add visual scope
This commit is contained in:
@@ -24,18 +24,17 @@ int main(int argc, char* argv[]) {
|
|||||||
NoteQueue queue = NoteQueue();
|
NoteQueue queue = NoteQueue();
|
||||||
ScopeBuffer scopeBuffer = ScopeBuffer(512);
|
ScopeBuffer scopeBuffer = ScopeBuffer(512);
|
||||||
KeyboardController keyboard(&config, &logger, &queue);
|
KeyboardController keyboard(&config, &logger, &queue);
|
||||||
Synth synth(&config, &logger, nullptr, &queue);
|
Synth synth(&config, &logger, &scopeBuffer, &queue);
|
||||||
|
|
||||||
// audio synthesizer doohickey
|
// audio synthesizer doohickey
|
||||||
AudioEngine audioEngine = AudioEngine(&config, &logger, &synth);
|
AudioEngine audioEngine = AudioEngine(&config, &logger, &synth);
|
||||||
audioEngine.start();
|
audioEngine.start();
|
||||||
|
|
||||||
// attach backend gui components
|
// attach backend gui components
|
||||||
//Scope scope; //scope.setBuffer(&scopeBuffer);
|
|
||||||
qmlRegisterType<TimerComponent>("AppDemo", 1, 0, "TimerComponent");
|
qmlRegisterType<TimerComponent>("AppDemo", 1, 0, "TimerComponent");
|
||||||
//qmlRegisterSingletonInstance("AppDemo", 1, 0, "Scope", &scope);
|
|
||||||
qmlRegisterType<Scope>("AppDemo", 1, 0, "Scope");
|
qmlRegisterType<Scope>("AppDemo", 1, 0, "Scope");
|
||||||
engine.rootContext()->setContextProperty("keyboardController", &keyboard);
|
engine.rootContext()->setContextProperty("keyboardController", &keyboard);
|
||||||
|
engine.rootContext()->setContextProperty("scopeBuffer", &scopeBuffer);
|
||||||
|
|
||||||
// load qml
|
// load qml
|
||||||
//engine.loadFromModule("sonobulus", "Main");
|
//engine.loadFromModule("sonobulus", "Main");
|
||||||
|
|||||||
@@ -31,7 +31,10 @@ float Instrument::process(bool& scopeTrigger) {
|
|||||||
if(!active_ && envelope_ > 0.0f) envelope_ -= 0.01f;
|
if(!active_ && envelope_ > 0.0f) envelope_ -= 0.01f;
|
||||||
|
|
||||||
phase_ += phaseIncrement_;
|
phase_ += phaseIncrement_;
|
||||||
if(phase_ > 2.0f * pi) phase_ -= 2.0f * pi;
|
if(phase_ > 2.0f * pi) {
|
||||||
|
phase_ -= 2.0f * pi;
|
||||||
|
scopeTrigger = true;
|
||||||
|
}
|
||||||
|
|
||||||
if(!isActive()) return 0.0f;
|
if(!isActive()) return 0.0f;
|
||||||
return sin(phase_) * envelope_;
|
return sin(phase_) * envelope_;
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
ScopeBuffer::ScopeBuffer(QObject *parent) : QObject(parent) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
ScopeBuffer::ScopeBuffer(size_t size) : buffer_(size) {
|
ScopeBuffer::ScopeBuffer(size_t size) : buffer_(size) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -27,21 +31,45 @@ Scope::Scope(QQuickItem *parent) : QQuickPaintedItem(parent) {
|
|||||||
setAntialiasing(true);
|
setAntialiasing(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Scope::setBuffer(ScopeBuffer* scopeBuffer) {
|
||||||
|
scopeBuffer_ = scopeBuffer;
|
||||||
|
buffer_ = std::vector<float>(scopeBuffer_->size());
|
||||||
|
}
|
||||||
|
|
||||||
void Scope::paint(QPainter *painter) {
|
void Scope::paint(QPainter *painter) {
|
||||||
|
|
||||||
//std::cout << "onPaint" << std::endl;
|
while(scopeBuffer_->spinlock()) {
|
||||||
|
// wait
|
||||||
|
}
|
||||||
|
|
||||||
static float phase = 0.0f;
|
if(scopeBuffer_ != nullptr) scopeBuffer_->read(buffer_);
|
||||||
phase += 0.1f;
|
|
||||||
float sample = sin(phase);
|
|
||||||
|
|
||||||
QPen pen(Qt::blue, 4);
|
// TODO: scale to max amplitude ?
|
||||||
|
float maxAmp = 1.0f;
|
||||||
|
/*
|
||||||
|
for(float s : buffer_) {
|
||||||
|
maxAmp = std::max(maxAmp, std::abs(s));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
float scaleY = (height() * 0.45f) / maxAmp;
|
||||||
|
float midY = height() / 2.0f;
|
||||||
|
|
||||||
|
painter->fillRect(0, 0, width(), height(), QColor(20, 20, 20));
|
||||||
|
|
||||||
|
QPen pen;
|
||||||
|
pen.setWidthF(3.0f);
|
||||||
|
QColor green(50, 255, 70);
|
||||||
|
pen.setColor(green);
|
||||||
painter->setPen(pen);
|
painter->setPen(pen);
|
||||||
painter->setRenderHint(QPainter::Antialiasing);
|
painter->setRenderHint(QPainter::Antialiasing);
|
||||||
|
|
||||||
painter->drawRect(10, 10, width() - 20*sample, height() - 20*sample);
|
for(size_t i = 1; i < buffer_.size(); i++) {
|
||||||
|
painter->drawLine(
|
||||||
painter->setPen(QPen(Qt::red, 2));
|
i * width() / buffer_.size(),
|
||||||
painter->drawLine(0, 0, width(), height());
|
midY + buffer_[i-1] * scaleY,
|
||||||
|
i * width() / buffer_.size(),
|
||||||
|
midY + buffer_[i] * scaleY
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,13 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
class ScopeBuffer {
|
class ScopeBuffer : public QObject {
|
||||||
|
|
||||||
|
Q_OBJECT // needed to attach to a qml component
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
explicit ScopeBuffer(QObject* parent = nullptr);
|
||||||
ScopeBuffer(size_t size);
|
ScopeBuffer(size_t size);
|
||||||
~ScopeBuffer() = default;
|
~ScopeBuffer() = default;
|
||||||
|
|
||||||
@@ -22,7 +25,8 @@ public:
|
|||||||
void setWavelength(size_t wavelength) { wavelength_ = wavelength; }
|
void setWavelength(size_t wavelength) { wavelength_ = wavelength; }
|
||||||
size_t trigger() { return trigger_; }
|
size_t trigger() { return trigger_; }
|
||||||
size_t wavelength() { return wavelength_; }
|
size_t wavelength() { return wavelength_; }
|
||||||
void spinlock(bool lock) { spinlock_ = lock; }
|
void spinlock(bool lock) { spinlock_.store(lock, std::memory_order_relaxed); }
|
||||||
|
bool spinlock() { return spinlock_.load(std::memory_order_relaxed); }
|
||||||
size_t size() { return buffer_.size(); }
|
size_t size() { return buffer_.size(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -33,20 +37,21 @@ private:
|
|||||||
size_t trigger_ = 0;
|
size_t trigger_ = 0;
|
||||||
size_t wavelength_ = 400;
|
size_t wavelength_ = 400;
|
||||||
|
|
||||||
bool spinlock_ = false;
|
std::atomic<bool> spinlock_ = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Scope : public QQuickPaintedItem {
|
class Scope : public QQuickPaintedItem {
|
||||||
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
Q_PROPERTY(ScopeBuffer* buffer WRITE setBuffer)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Scope(QQuickItem* parent = nullptr);
|
explicit Scope(QQuickItem* parent = nullptr);
|
||||||
|
|
||||||
void paint(QPainter* painter) override;
|
void paint(QPainter* painter) override;
|
||||||
void setBuffer(ScopeBuffer* scopeBuffer) { scopeBuffer_ = scopeBuffer; }
|
|
||||||
|
void setBuffer(ScopeBuffer* scopeBuffer);
|
||||||
|
|
||||||
std::vector<float> buffer_;
|
std::vector<float> buffer_;
|
||||||
ScopeBuffer* scopeBuffer_;
|
ScopeBuffer* scopeBuffer_;
|
||||||
|
|||||||
@@ -28,18 +28,22 @@ void Synth::process(float* out, size_t nFrames) {
|
|||||||
handleNoteEvent(noteEvent);
|
handleNoteEvent(noteEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// size_t lowestVoice = 0;
|
size_t lowestVoice = 0;
|
||||||
// float lowestFreq = 100000.0f;
|
float lowestFreq = 100000.0f;
|
||||||
// for(size_t i = 0; i < voices_.size(); i++) {
|
for(size_t i = 0; i < voices_.size(); i++) {
|
||||||
// if(!voices_[i].isActive()) continue;
|
if(!voices_[i].isActive()) continue;
|
||||||
// float currentFreq = voices_[i].frequency();
|
float currentFreq = voices_[i].frequency();
|
||||||
// if(currentFreq < lowestFreq) {
|
if(currentFreq < lowestFreq) {
|
||||||
// lowestVoice = i;
|
lowestVoice = i;
|
||||||
// lowestFreq = currentFreq;
|
lowestFreq = currentFreq;
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
scope_->spinlock(true);
|
||||||
|
|
||||||
float sampleOut = 0.0f;
|
float sampleOut = 0.0f;
|
||||||
|
bool triggered = false;
|
||||||
|
bool once = false;
|
||||||
for(size_t i = 0; i < nFrames; i++) {
|
for(size_t i = 0; i < nFrames; i++) {
|
||||||
|
|
||||||
float mix = 0.0f;
|
float mix = 0.0f;
|
||||||
@@ -47,7 +51,7 @@ void Synth::process(float* out, size_t nFrames) {
|
|||||||
bool temp = false;
|
bool temp = false;
|
||||||
//if(!voices_[j].isActive()) continue;
|
//if(!voices_[j].isActive()) continue;
|
||||||
mix += voices_[j].process(temp);
|
mix += voices_[j].process(temp);
|
||||||
// if(j == lowestVoice) triggered = temp;
|
if(j == lowestVoice) triggered = temp;
|
||||||
}
|
}
|
||||||
mix = tanh(mix/4.0f); // prevent clipping
|
mix = tanh(mix/4.0f); // prevent clipping
|
||||||
|
|
||||||
@@ -55,14 +59,15 @@ void Synth::process(float* out, size_t nFrames) {
|
|||||||
out[2*i] = sampleOut;
|
out[2*i] = sampleOut;
|
||||||
out[2*i+1] = sampleOut;
|
out[2*i+1] = sampleOut;
|
||||||
|
|
||||||
// if(scope_) scope_->push(sampleOut);
|
if(scope_) scope_->push(sampleOut);
|
||||||
// if(triggered && !once) {
|
if(triggered && !once) {
|
||||||
// scope_->setTrigger(i);
|
scope_->setTrigger(i);
|
||||||
// once = true;
|
once = true;
|
||||||
// }
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scope_->spinlock(false);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ private:
|
|||||||
|
|
||||||
ConfigService* config_;
|
ConfigService* config_;
|
||||||
LoggerService* logger_;
|
LoggerService* logger_;
|
||||||
ScopeBuffer* scope_ = nullptr;
|
ScopeBuffer* scope_;;
|
||||||
NoteQueue* noteQueue_;
|
NoteQueue* noteQueue_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public:
|
|||||||
|
|
||||||
float process(bool& scopeTrigger);
|
float process(bool& scopeTrigger);
|
||||||
uint8_t note() { return note_; }
|
uint8_t note() { return note_; }
|
||||||
|
float frequency() { return noteToFrequency(note_); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ ApplicationWindow {
|
|||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: scope.update()
|
onTriggered: scope.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buffer: scopeBuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user