| @@ -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; | |||
| } | |||