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.

266 lines
6.7KB

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