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