@@ -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); | |||
} | |||
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` | |||
@@ -240,6 +240,9 @@ struct Vec { | |||
} | |||
Vec clamp(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(); | |||
} | |||
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 { | |||
heldNotes.clear(); | |||
lastNote = 60; | |||
@@ -100,45 +71,6 @@ struct MIDI_CV : Module { | |||
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 { | |||
midi::Message msg; | |||
while (midiInput.shift(&msg)) { | |||
@@ -254,6 +186,74 @@ struct MIDI_CV : Module { | |||
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 { | |||
DEBUG("%p model", model); | |||
// Lazily create ModuleWidget when drawn | |||
if (!initialized) { | |||
Widget *transparentWidget = new 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->setZoom(0.5f); | |||
transparentWidget->addChild(zoomWidget); | |||
fbWidget->addChild(zoomWidget); | |||
ModuleWidget *moduleWidget = model->createModuleWidgetNull(); | |||
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); | |||
initialized = true; | |||
} | |||
@@ -25,6 +25,7 @@ void SVGSlider::setBackgroundSVG(std::shared_ptr<SVG> backgroundSVG) { | |||
void SVGSlider::setHandleSVG(std::shared_ptr<SVG> handleSVG) { | |||
handle->setSVG(handleSVG); | |||
handle->box.pos = maxHandlePos; | |||
fb->dirty = true; | |||
} | |||
@@ -269,7 +269,7 @@ struct EnginePauseItem : MenuItem { | |||
struct SampleRateValueItem : MenuItem { | |||
float sampleRate; | |||
SampleRateValueItem(float sampleRate) { | |||
void setSampleRate(float sampleRate) { | |||
this->sampleRate = sampleRate; | |||
text = string::f("%.0f Hz", sampleRate); | |||
rightText = CHECKMARK(app()->engine->getSampleRate() == sampleRate); | |||
@@ -292,7 +292,9 @@ struct SampleRateItem : MenuItem { | |||
std::vector<float> sampleRates = {44100, 48000, 88200, 96000, 176400, 192000}; | |||
for (float sampleRate : sampleRates) { | |||
menu->addChild(new SampleRateValueItem(sampleRate)); | |||
SampleRateValueItem *item = new SampleRateValueItem; | |||
item->setSampleRate(sampleRate); | |||
menu->addChild(item); | |||
} | |||
return menu; | |||
} | |||
@@ -301,9 +303,13 @@ struct SampleRateItem : MenuItem { | |||
struct ThreadCountValueItem : MenuItem { | |||
int threadCount; | |||
ThreadCountValueItem(int threadCount) { | |||
void setThreadCount(int threadCount) { | |||
this->threadCount = 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); | |||
} | |||
void onAction(const event::Action &e) override { | |||
@@ -321,7 +327,9 @@ struct ThreadCount : MenuItem { | |||
int coreCount = system::getPhysicalCoreCount(); | |||
for (int i = 1; i <= coreCount; i++) { | |||
menu->addChild(new ThreadCountValueItem(i)); | |||
ThreadCountValueItem *item = new ThreadCountValueItem; | |||
item->setThreadCount(i); | |||
menu->addChild(item); | |||
} | |||
return menu; | |||
} | |||