You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

272 lines
6.7KB

  1. #include <app/Knob.hpp>
  2. #include <context.hpp>
  3. #include <app/Scene.hpp>
  4. #include <app/RackScrollWidget.hpp>
  5. #include <random.hpp>
  6. #include <history.hpp>
  7. #include <settings.hpp>
  8. namespace rack {
  9. namespace app {
  10. struct Knob::Internal {
  11. /** Value of the knob before dragging. */
  12. float oldValue = 0.f;
  13. /** Fractional value between the param's value and the dragged knob position.
  14. Using a "snapValue" variable and rounding is insufficient because the mouse needs to reach 1.0, not 0.5 to obtain the first increment.
  15. */
  16. float snapDelta = 0.f;
  17. /** Speed multiplier in speed knob mode */
  18. float linearScale = 1.f;
  19. /** The mouse has once escaped from the knob while dragging. */
  20. bool rotaryDragEnabled = false;
  21. float dragAngle = NAN;
  22. };
  23. Knob::Knob() {
  24. internal = new Internal;
  25. }
  26. Knob::~Knob() {
  27. delete internal;
  28. }
  29. void Knob::initParamQuantity() {
  30. ParamWidget::initParamQuantity();
  31. engine::ParamQuantity* pq = getParamQuantity();
  32. if (pq) {
  33. if (snap)
  34. pq->snapEnabled = true;
  35. if (smooth)
  36. pq->smoothEnabled = true;
  37. }
  38. }
  39. void Knob::onHover(const HoverEvent& e) {
  40. // Only call super if mouse position is in the circle
  41. math::Vec c = box.size.div(2);
  42. float dist = e.pos.minus(c).norm();
  43. if (dist <= c.x) {
  44. ParamWidget::onHover(e);
  45. }
  46. }
  47. void Knob::onButton(const ButtonEvent& e) {
  48. math::Vec c = box.size.div(2);
  49. float dist = e.pos.minus(c).norm();
  50. if (dist <= c.x) {
  51. ParamWidget::onButton(e);
  52. }
  53. }
  54. void Knob::onDragStart(const DragStartEvent& e) {
  55. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  56. return;
  57. engine::ParamQuantity* pq = getParamQuantity();
  58. if (pq) {
  59. internal->oldValue = pq->getSmoothValue();
  60. internal->snapDelta = 0.f;
  61. }
  62. settings::KnobMode km = settings::knobMode;
  63. if (km == settings::KNOB_MODE_LINEAR || km == settings::KNOB_MODE_SCALED_LINEAR) {
  64. APP->window->cursorLock();
  65. }
  66. // Only changed for KNOB_MODE_LINEAR_*.
  67. internal->linearScale = 1.f;
  68. // Only used for KNOB_MODE_ROTARY_*.
  69. internal->rotaryDragEnabled = false;
  70. internal->dragAngle = NAN;
  71. ParamWidget::onDragStart(e);
  72. }
  73. void Knob::onDragEnd(const DragEndEvent& e) {
  74. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  75. return;
  76. settings::KnobMode km = settings::knobMode;
  77. if (km == settings::KNOB_MODE_LINEAR || km == settings::KNOB_MODE_SCALED_LINEAR) {
  78. APP->window->cursorUnlock();
  79. }
  80. engine::ParamQuantity* pq = getParamQuantity();
  81. if (pq) {
  82. float newValue = pq->getSmoothValue();
  83. if (internal->oldValue != newValue) {
  84. // Push ParamChange history action
  85. history::ParamChange* h = new history::ParamChange;
  86. h->name = "move knob";
  87. h->moduleId = module->id;
  88. h->paramId = paramId;
  89. h->oldValue = internal->oldValue;
  90. h->newValue = newValue;
  91. APP->history->push(h);
  92. }
  93. // Reset snap delta
  94. internal->snapDelta = 0.f;
  95. }
  96. ParamWidget::onDragEnd(e);
  97. }
  98. static float getModSpeed() {
  99. int mods = APP->window->getMods();
  100. if ((mods & RACK_MOD_MASK) == RACK_MOD_CTRL)
  101. return 1 / 10.f;
  102. else if ((mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT)
  103. return 4.f;
  104. else if ((mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT))
  105. return 1 / 100.f;
  106. else
  107. return 1.f;
  108. }
  109. void Knob::onDragMove(const DragMoveEvent& e) {
  110. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  111. return;
  112. settings::KnobMode km = settings::knobMode;
  113. bool linearMode = (km == settings::KNOB_MODE_LINEAR || km == settings::KNOB_MODE_SCALED_LINEAR) || forceLinear;
  114. engine::ParamQuantity* pq = getParamQuantity();
  115. if (pq) {
  116. float value = pq->getSmoothValue();
  117. // Ratio between parameter value scale / (angle range / 2*pi)
  118. float rangeRatio;
  119. if (pq->isBounded()) {
  120. rangeRatio = pq->getRange();
  121. rangeRatio /= (maxAngle - minAngle) / float(2 * M_PI);
  122. }
  123. else {
  124. rangeRatio = 1.f;
  125. }
  126. if (linearMode) {
  127. float delta = (horizontal ? e.mouseDelta.x : -e.mouseDelta.y);
  128. delta *= settings::knobLinearSensitivity;
  129. delta *= getModSpeed();
  130. delta *= rangeRatio;
  131. // Scale delta if in scaled linear knob mode
  132. if (km == settings::KNOB_MODE_SCALED_LINEAR) {
  133. float deltaY = (horizontal ? -e.mouseDelta.y : -e.mouseDelta.x);
  134. const float pixelTau = 200.f;
  135. internal->linearScale *= std::pow(2.f, -deltaY / pixelTau);
  136. delta *= internal->linearScale;
  137. }
  138. // Handle value snapping
  139. if (pq->snapEnabled) {
  140. // Replace delta with an accumulated delta since the last integer knob.
  141. internal->snapDelta += delta;
  142. delta = std::trunc(internal->snapDelta);
  143. internal->snapDelta -= delta;
  144. }
  145. value += delta;
  146. }
  147. else if (internal->rotaryDragEnabled) {
  148. math::Vec origin = getAbsoluteOffset(box.size.div(2));
  149. math::Vec deltaPos = APP->scene->mousePos.minus(origin);
  150. float angle = deltaPos.arg() + float(M_PI) / 2;
  151. bool absoluteRotaryMode = (km == settings::KNOB_MODE_ROTARY_ABSOLUTE) && pq->isBounded();
  152. if (absoluteRotaryMode) {
  153. // Find angle closest to midpoint of angle range, mod 2*pi
  154. float midAngle = (minAngle + maxAngle) / 2;
  155. angle = math::eucMod(angle - midAngle + float(M_PI), float(2 * M_PI)) + midAngle - float(M_PI);
  156. value = math::rescale(angle, minAngle, maxAngle, pq->getMinValue(), pq->getMaxValue());
  157. }
  158. else {
  159. if (!std::isfinite(internal->dragAngle)) {
  160. // Set the starting angle
  161. internal->dragAngle = angle;
  162. }
  163. // Find angle closest to last angle, mod 2*pi
  164. float deltaAngle = math::eucMod(angle - internal->dragAngle + float(M_PI), float(2 * M_PI)) - float(M_PI);
  165. internal->dragAngle = angle;
  166. float delta = deltaAngle / float(2 * M_PI) * rangeRatio;
  167. delta *= getModSpeed();
  168. // Handle value snapping
  169. if (pq->snapEnabled) {
  170. // Replace delta with an accumulated delta since the last integer knob.
  171. internal->snapDelta += delta;
  172. delta = std::trunc(internal->snapDelta);
  173. internal->snapDelta -= delta;
  174. }
  175. value += delta;
  176. }
  177. }
  178. // Set value
  179. pq->setSmoothValue(value);
  180. }
  181. ParamWidget::onDragMove(e);
  182. }
  183. void Knob::onDragLeave(const DragLeaveEvent& e) {
  184. if (e.origin == this) {
  185. internal->rotaryDragEnabled = true;
  186. }
  187. ParamWidget::onDragLeave(e);
  188. }
  189. void Knob::onHoverScroll(const HoverScrollEvent& e) {
  190. ParamWidget::onHoverScroll(e);
  191. if (!settings::knobScroll)
  192. return;
  193. if (APP->scene->rackScroll->isScrolling())
  194. return;
  195. engine::ParamQuantity* pq = getParamQuantity();
  196. if (!pq)
  197. return;
  198. float value = pq->getSmoothValue();
  199. float rangeRatio;
  200. if (pq->isBounded()) {
  201. rangeRatio = pq->getRange();
  202. }
  203. else {
  204. rangeRatio = 1.f;
  205. }
  206. float delta = e.scrollDelta.y;
  207. delta *= settings::knobScrollSensitivity;
  208. delta *= getModSpeed();
  209. delta *= rangeRatio;
  210. // Handle value snapping
  211. if (pq->snapEnabled) {
  212. // Replace delta with an accumulated delta since the last integer knob.
  213. internal->snapDelta += delta;
  214. delta = std::trunc(internal->snapDelta);
  215. internal->snapDelta -= delta;
  216. }
  217. value += delta;
  218. pq->setSmoothValue(value);
  219. e.consume(this);
  220. }
  221. } // namespace app
  222. } // namespace rack