@@ -134,8 +134,8 @@ inline float rescale(float x, float a, float b, float yMin, float yMax) { | |||||
return yMin + (x - a) / (b - a) * (yMax - yMin); | return yMin + (x - a) / (b - a) * (yMax - yMin); | ||||
} | } | ||||
inline float crossfade(float a, float b, float frac) { | |||||
return a + frac * (b - a); | |||||
inline float crossfade(float a, float b, float p) { | |||||
return a + (b - a) * p; | |||||
} | } | ||||
/** Linearly interpolate an array `p` with index `x` | /** Linearly interpolate an array `p` with index `x` | ||||
@@ -240,6 +240,9 @@ struct Vec { | |||||
} | } | ||||
Vec clamp(Rect bound) const; | Vec clamp(Rect bound) const; | ||||
Vec clampSafe(Rect bound) const; | Vec clampSafe(Rect bound) const; | ||||
Vec crossfade(Vec b, float p) { | |||||
return this->plus(b.minus(*this).mult(p)); | |||||
} | |||||
}; | }; | ||||
@@ -60,35 +60,6 @@ struct MIDI_CV : Module { | |||||
onReset(); | onReset(); | ||||
} | } | ||||
json_t *dataToJson() override { | |||||
json_t *rootJ = json_object(); | |||||
json_t *divisionsJ = json_array(); | |||||
for (int i = 0; i < 2; i++) { | |||||
json_t *divisionJ = json_integer(divisions[i]); | |||||
json_array_append_new(divisionsJ, divisionJ); | |||||
} | |||||
json_object_set_new(rootJ, "divisions", divisionsJ); | |||||
json_object_set_new(rootJ, "midi", midiInput.toJson()); | |||||
return rootJ; | |||||
} | |||||
void dataFromJson(json_t *rootJ) override { | |||||
json_t *divisionsJ = json_object_get(rootJ, "divisions"); | |||||
if (divisionsJ) { | |||||
for (int i = 0; i < 2; i++) { | |||||
json_t *divisionJ = json_array_get(divisionsJ, i); | |||||
if (divisionJ) | |||||
divisions[i] = json_integer_value(divisionJ); | |||||
} | |||||
} | |||||
json_t *midiJ = json_object_get(rootJ, "midi"); | |||||
if (midiJ) | |||||
midiInput.fromJson(midiJ); | |||||
} | |||||
void onReset() override { | void onReset() override { | ||||
heldNotes.clear(); | heldNotes.clear(); | ||||
lastNote = 60; | lastNote = 60; | ||||
@@ -100,45 +71,6 @@ struct MIDI_CV : Module { | |||||
midiInput.reset(); | midiInput.reset(); | ||||
} | } | ||||
void pressNote(uint8_t note) { | |||||
// Remove existing similar note | |||||
auto it = std::find(heldNotes.begin(), heldNotes.end(), note); | |||||
if (it != heldNotes.end()) | |||||
heldNotes.erase(it); | |||||
// Push note | |||||
heldNotes.push_back(note); | |||||
lastNote = note; | |||||
gate = true; | |||||
retriggerPulse.trigger(1e-3); | |||||
} | |||||
void releaseNote(uint8_t note) { | |||||
// Remove the note | |||||
auto it = std::find(heldNotes.begin(), heldNotes.end(), note); | |||||
if (it != heldNotes.end()) | |||||
heldNotes.erase(it); | |||||
// Hold note if pedal is pressed | |||||
if (pedal) | |||||
return; | |||||
// Set last note | |||||
if (!heldNotes.empty()) { | |||||
lastNote = heldNotes[heldNotes.size() - 1]; | |||||
gate = true; | |||||
} | |||||
else { | |||||
gate = false; | |||||
} | |||||
} | |||||
void pressPedal() { | |||||
pedal = true; | |||||
} | |||||
void releasePedal() { | |||||
pedal = false; | |||||
releaseNote(255); | |||||
} | |||||
void step() override { | void step() override { | ||||
midi::Message msg; | midi::Message msg; | ||||
while (midiInput.shift(&msg)) { | while (midiInput.shift(&msg)) { | ||||
@@ -254,6 +186,74 @@ struct MIDI_CV : Module { | |||||
default: break; | default: break; | ||||
} | } | ||||
} | } | ||||
void pressNote(uint8_t note) { | |||||
// Remove existing similar note | |||||
auto it = std::find(heldNotes.begin(), heldNotes.end(), note); | |||||
if (it != heldNotes.end()) | |||||
heldNotes.erase(it); | |||||
// Push note | |||||
heldNotes.push_back(note); | |||||
lastNote = note; | |||||
gate = true; | |||||
retriggerPulse.trigger(1e-3); | |||||
} | |||||
void releaseNote(uint8_t note) { | |||||
// Remove the note | |||||
auto it = std::find(heldNotes.begin(), heldNotes.end(), note); | |||||
if (it != heldNotes.end()) | |||||
heldNotes.erase(it); | |||||
// Hold note if pedal is pressed | |||||
if (pedal) | |||||
return; | |||||
// Set last note | |||||
if (!heldNotes.empty()) { | |||||
lastNote = heldNotes[heldNotes.size() - 1]; | |||||
gate = true; | |||||
} | |||||
else { | |||||
gate = false; | |||||
} | |||||
} | |||||
void pressPedal() { | |||||
pedal = true; | |||||
} | |||||
void releasePedal() { | |||||
pedal = false; | |||||
releaseNote(255); | |||||
} | |||||
json_t *dataToJson() override { | |||||
json_t *rootJ = json_object(); | |||||
json_t *divisionsJ = json_array(); | |||||
for (int i = 0; i < 2; i++) { | |||||
json_t *divisionJ = json_integer(divisions[i]); | |||||
json_array_append_new(divisionsJ, divisionJ); | |||||
} | |||||
json_object_set_new(rootJ, "divisions", divisionsJ); | |||||
json_object_set_new(rootJ, "midi", midiInput.toJson()); | |||||
return rootJ; | |||||
} | |||||
void dataFromJson(json_t *rootJ) override { | |||||
json_t *divisionsJ = json_object_get(rootJ, "divisions"); | |||||
if (divisionsJ) { | |||||
for (int i = 0; i < 2; i++) { | |||||
json_t *divisionJ = json_array_get(divisionsJ, i); | |||||
if (divisionJ) | |||||
divisions[i] = json_integer_value(divisionJ); | |||||
} | |||||
} | |||||
json_t *midiJ = json_object_get(rootJ, "midi"); | |||||
if (midiJ) | |||||
midiInput.fromJson(midiJ); | |||||
} | |||||
}; | }; | ||||
@@ -48,19 +48,30 @@ struct ModuleBox : OpaqueWidget { | |||||
} | } | ||||
void draw(NVGcontext *vg) override { | void draw(NVGcontext *vg) override { | ||||
DEBUG("%p model", model); | |||||
// Lazily create ModuleWidget when drawn | // Lazily create ModuleWidget when drawn | ||||
if (!initialized) { | if (!initialized) { | ||||
Widget *transparentWidget = new TransparentWidget; | Widget *transparentWidget = new TransparentWidget; | ||||
addChild(transparentWidget); | addChild(transparentWidget); | ||||
FramebufferWidget *fbWidget = new FramebufferWidget; | |||||
if (math::isNear(app()->window->pixelRatio, 1.0)) { | |||||
// Small details draw poorly at low DPI, so oversample when drawing to the framebuffer | |||||
fbWidget->oversample = 2.0; | |||||
} | |||||
transparentWidget->addChild(fbWidget); | |||||
ZoomWidget *zoomWidget = new ZoomWidget; | ZoomWidget *zoomWidget = new ZoomWidget; | ||||
zoomWidget->setZoom(0.5f); | zoomWidget->setZoom(0.5f); | ||||
transparentWidget->addChild(zoomWidget); | |||||
fbWidget->addChild(zoomWidget); | |||||
ModuleWidget *moduleWidget = model->createModuleWidgetNull(); | ModuleWidget *moduleWidget = model->createModuleWidgetNull(); | ||||
zoomWidget->addChild(moduleWidget); | zoomWidget->addChild(moduleWidget); | ||||
float width = std::ceil(moduleWidget->box.size.x * 0.5f); | |||||
zoomWidget->box.size.x = moduleWidget->box.size.x * zoomWidget->zoom; | |||||
zoomWidget->box.size.y = RACK_GRID_HEIGHT; | |||||
float width = std::ceil(zoomWidget->box.size.x); | |||||
box.size.x = std::max(box.size.x, width); | box.size.x = std::max(box.size.x, width); | ||||
initialized = true; | initialized = true; | ||||
} | } | ||||
@@ -25,6 +25,7 @@ void SVGSlider::setBackgroundSVG(std::shared_ptr<SVG> backgroundSVG) { | |||||
void SVGSlider::setHandleSVG(std::shared_ptr<SVG> handleSVG) { | void SVGSlider::setHandleSVG(std::shared_ptr<SVG> handleSVG) { | ||||
handle->setSVG(handleSVG); | handle->setSVG(handleSVG); | ||||
handle->box.pos = maxHandlePos; | |||||
fb->dirty = true; | fb->dirty = true; | ||||
} | } | ||||
@@ -269,7 +269,7 @@ struct EnginePauseItem : MenuItem { | |||||
struct SampleRateValueItem : MenuItem { | struct SampleRateValueItem : MenuItem { | ||||
float sampleRate; | float sampleRate; | ||||
SampleRateValueItem(float sampleRate) { | |||||
void setSampleRate(float sampleRate) { | |||||
this->sampleRate = sampleRate; | this->sampleRate = sampleRate; | ||||
text = string::f("%.0f Hz", sampleRate); | text = string::f("%.0f Hz", sampleRate); | ||||
rightText = CHECKMARK(app()->engine->getSampleRate() == sampleRate); | rightText = CHECKMARK(app()->engine->getSampleRate() == sampleRate); | ||||
@@ -292,7 +292,9 @@ struct SampleRateItem : MenuItem { | |||||
std::vector<float> sampleRates = {44100, 48000, 88200, 96000, 176400, 192000}; | std::vector<float> sampleRates = {44100, 48000, 88200, 96000, 176400, 192000}; | ||||
for (float sampleRate : sampleRates) { | for (float sampleRate : sampleRates) { | ||||
menu->addChild(new SampleRateValueItem(sampleRate)); | |||||
SampleRateValueItem *item = new SampleRateValueItem; | |||||
item->setSampleRate(sampleRate); | |||||
menu->addChild(item); | |||||
} | } | ||||
return menu; | return menu; | ||||
} | } | ||||
@@ -301,9 +303,13 @@ struct SampleRateItem : MenuItem { | |||||
struct ThreadCountValueItem : MenuItem { | struct ThreadCountValueItem : MenuItem { | ||||
int threadCount; | int threadCount; | ||||
ThreadCountValueItem(int threadCount) { | |||||
void setThreadCount(int threadCount) { | |||||
this->threadCount = threadCount; | this->threadCount = threadCount; | ||||
text = string::f("%d", threadCount); | text = string::f("%d", threadCount); | ||||
if (threadCount == 1) | |||||
text += " (default)"; | |||||
else if (threadCount == system::getPhysicalCoreCount() / 2) | |||||
text += " (recommended)"; | |||||
rightText = CHECKMARK(app()->engine->threadCount == threadCount); | rightText = CHECKMARK(app()->engine->threadCount == threadCount); | ||||
} | } | ||||
void onAction(const event::Action &e) override { | void onAction(const event::Action &e) override { | ||||
@@ -321,7 +327,9 @@ struct ThreadCount : MenuItem { | |||||
int coreCount = system::getPhysicalCoreCount(); | int coreCount = system::getPhysicalCoreCount(); | ||||
for (int i = 1; i <= coreCount; i++) { | for (int i = 1; i <= coreCount; i++) { | ||||
menu->addChild(new ThreadCountValueItem(i)); | |||||
ThreadCountValueItem *item = new ThreadCountValueItem; | |||||
item->setThreadCount(i); | |||||
menu->addChild(item); | |||||
} | } | ||||
return menu; | return menu; | ||||
} | } | ||||