Skip to content

Commit

Permalink
added bpm label to clock
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremywen committed Nov 29, 2017
1 parent b1bd274 commit 0b5f558
Show file tree
Hide file tree
Showing 7 changed files with 1,111 additions and 238 deletions.
503 changes: 503 additions & 0 deletions res/BouncyBall.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
429 changes: 422 additions & 7 deletions res/XYPad.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
268 changes: 146 additions & 122 deletions src/BouncyBall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,54 @@

struct BouncyBall : Module {
enum ParamIds {
X_POS_PARAM,
Y_POS_PARAM,
GATE_PARAM,
SCALE_X_PARAM,
SCALE_Y_PARAM,
OFFSET_X_VOLTS_PARAM,
OFFSET_Y_VOLTS_PARAM,
VELOCITY_X_PARAM,
VELOCITY_Y_PARAM,
SPEED_MULT_PARAM,
DECAY_PARAM,
GRAVITY_PARAM,
NUM_PARAMS
};
enum InputIds {
RESET_INPUT,
BUMP_INPUT,
NUM_INPUTS
};
enum OutputIds {
X_OUTPUT,
Y_OUTPUT,
GATE_OUTPUT,
NORTH_GATE_OUTPUT,
EAST_GATE_OUTPUT,
SOUTH_GATE_OUTPUT,
WEST_GATE_OUTPUT,
NUM_OUTPUTS
};

float minX = 0, minY = 0, maxX = 0, maxY = 0;
float displayWidth = 0, displayHeight = 0;
float totalBallSize = 12;
float ballRadius = 10;
float ballStrokeWidth = 2;
float minVolt = -5, maxVolt = 5;
PulseGenerator gatePulse;
float velScale = 0.01;

bool lastTickWasEdgeHit = false;
long consecutiveEdgeHits = 0;

Vec velocity;
Vec ballPos;
Vec ballVel;

PulseGenerator northGatePulse, eastGatePulse, southGatePulse, westGatePulse;
SchmittTrigger resetTrigger;
SchmittTrigger bumpTrigger;

BouncyBall() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {}
void step();
void reset(){
defaultPos();
}
void reset(){}

json_t *toJson() {
json_t *rootJ = json_object();
Expand All @@ -41,76 +61,95 @@ struct BouncyBall : Module {
void fromJson(json_t *rootJ) {
}

void defaultPos() {
params[BouncyBall::X_POS_PARAM].value = displayWidth / 2.0;
params[BouncyBall::Y_POS_PARAM].value = displayHeight / 2.0;
}

void updateMinMax(){
minX = totalBallSize;
minY = totalBallSize;
maxX = displayWidth - totalBallSize;
maxY = displayHeight - totalBallSize;
float distToMid = ballRadius + ballStrokeWidth;
minX = distToMid;
minY = distToMid;
maxX = displayWidth - distToMid;
maxY = displayHeight - distToMid;
}

void resetBall(){
ballPos.x = displayWidth * 0.5;
ballPos.y = displayHeight * 0.5;
consecutiveEdgeHits = 0;
lastTickWasEdgeHit = false;
}
};

void BouncyBall::step() {
outputs[X_OUTPUT].value = rescalef(params[X_POS_PARAM].value, minX, maxX, minVolt, maxVolt) + params[OFFSET_X_VOLTS_PARAM].value;
outputs[Y_OUTPUT].value = rescalef(params[Y_POS_PARAM].value, minY, maxY, maxVolt, minVolt) + params[OFFSET_Y_VOLTS_PARAM].value;//y is inverted because gui coords

bool pulse = gatePulse.process(1.0 / engineGetSampleRate());
outputs[GATE_OUTPUT].value = pulse ? 10.0 : 0.0;
}

struct BouncyBallDisplay : Widget {
BouncyBall *module;
Vec velocity;
Vec gravity;
Vec ballPos;
float slowRate;
int totalBallSize = 15;
velocity = Vec(params[VELOCITY_X_PARAM].value, params[VELOCITY_Y_PARAM].value);

BouncyBallDisplay(){ init(); }
if (resetTrigger.process(inputs[RESET_INPUT].value)) {
resetBall();
ballVel = Vec(velocity.mult(velScale));
}

void onMouseDown(EventMouseDown &e) override {
init();
module->params[BouncyBall::X_POS_PARAM].value = e.pos.x;
module->params[BouncyBall::Y_POS_PARAM].value = e.pos.y;
if (bumpTrigger.process(inputs[BUMP_INPUT].value)) {
ballVel = ballVel.plus(velocity.mult(velScale));
}

void init(){
velocity = Vec(randomf()*4-2, randomf()*4-2);
gravity = Vec(0, randomf()*1);
slowRate = 0.75;
if(consecutiveEdgeHits < 5){//don't want to get stuck triggering on edges

bool edgeHit = false;
if(ballPos.x >= maxX){
ballVel.x *= -params[DECAY_PARAM].value;//-1 reverses ball with no speed change
eastGatePulse.trigger(1e-3);
edgeHit = true;
}

if(ballPos.x <= minX){
ballVel.x *= -params[DECAY_PARAM].value;//-1 reverses ball with no speed change
westGatePulse.trigger(1e-3);
edgeHit = true;
}

if(ballPos.y >= maxY){
ballVel.y *= -params[DECAY_PARAM].value;//-1 reverses ball with no speed change
southGatePulse.trigger(1e-3);
edgeHit = true;
}

if(ballPos.y <= minY){
ballVel.y *= -params[DECAY_PARAM].value;//-1 reverses ball with no speed change
northGatePulse.trigger(1e-3);
edgeHit = true;
}

if(edgeHit){
consecutiveEdgeHits++;
lastTickWasEdgeHit = true;
} else {
consecutiveEdgeHits = 0;
lastTickWasEdgeHit = false;
}
}

void draw(NVGcontext *vg) override {
ballPos.x = module->params[BouncyBall::X_POS_PARAM].value;
ballPos.y = module->params[BouncyBall::Y_POS_PARAM].value;
ballPos = ballPos.plus(velocity);
velocity = velocity.plus(gravity);

if(ballPos.x+totalBallSize > box.size.x || ballPos.x-totalBallSize < 0){
velocity.x *= -1;
}

if(ballPos.y+totalBallSize+2 > box.size.y){
// if(slowRate > 0.1){
velocity.y *= -(slowRate);
velocity.x *= slowRate;
ballPos.y = box.size.y-totalBallSize;
if(velocity.x < -0.02 || velocity.x > 0.02){
printf("%f, %f\n", velocity.x, velocity.y);
module->gatePulse.trigger(1e-3);
}
// slowRate *= 0.5;
// } else {
// velocity.x = 0;
// velocity.y = 0;
// }
}
bool northPulse = northGatePulse.process(1.0 / engineGetSampleRate());
bool eastPulse = eastGatePulse.process(1.0 / engineGetSampleRate());
bool southPulse = southGatePulse.process(1.0 / engineGetSampleRate());
bool westPulse = westGatePulse.process(1.0 / engineGetSampleRate());
outputs[NORTH_GATE_OUTPUT].value = northPulse ? 10.0 : 0.0;
outputs[EAST_GATE_OUTPUT].value = eastPulse ? 10.0 : 0.0;
outputs[SOUTH_GATE_OUTPUT].value = southPulse ? 10.0 : 0.0;
outputs[WEST_GATE_OUTPUT].value = westPulse ? 10.0 : 0.0;

outputs[X_OUTPUT].value = (rescalef(ballPos.x, minX, maxX, minVolt, maxVolt) + params[OFFSET_X_VOLTS_PARAM].value) * params[SCALE_X_PARAM].value;
outputs[Y_OUTPUT].value = (rescalef(ballPos.y, minY, maxY, maxVolt, minVolt) + params[OFFSET_Y_VOLTS_PARAM].value) * params[SCALE_Y_PARAM].value;//y is inverted because gui coords

Vec newPos = ballPos.plus(ballVel.mult(params[SPEED_MULT_PARAM].value));
ballPos.x = clampf(newPos.x, minX, maxX);
ballPos.y = clampf(newPos.y, minY, maxY);

//TODO ADD GRAVITY for a more realistic bouncy ball
// ballVel = ballVel.plus(gravity);
}

struct BouncyBallDisplay : Widget {
BouncyBall *module;
BouncyBallDisplay(){}

void draw(NVGcontext *vg) override {
//background
nvgFillColor(vg, nvgRGB(20, 30, 33));
nvgBeginPath(vg);
Expand All @@ -122,12 +161,9 @@ struct BouncyBallDisplay : Widget {
nvgStrokeColor(vg, nvgRGB(25, 150, 252));
nvgStrokeWidth(vg, 2);
nvgBeginPath(vg);
nvgCircle(vg, ballPos.x, ballPos.y, totalBallSize - 2/*stroke*/);
nvgCircle(vg, module->ballPos.x, module->ballPos.y, module->ballRadius);
nvgFill(vg);
nvgStroke(vg);

module->params[BouncyBall::X_POS_PARAM].value = ballPos.x;
module->params[BouncyBall::Y_POS_PARAM].value = ballPos.y;
}
};

Expand All @@ -136,60 +172,48 @@ BouncyBallWidget::BouncyBallWidget() {
setModule(module);
box.size = Vec(RACK_GRID_WIDTH*24, RACK_GRID_HEIGHT);

{
LightPanel *panel = new LightPanel();
panel->box.size = box.size;
addChild(panel);
}
SVGPanel *panel = new SVGPanel();
panel->box.size = box.size;
panel->setBackground(SVG::load(assetPlugin(plugin, "res/BouncyBall.svg")));
addChild(panel);

BouncyBallDisplay *display = new BouncyBallDisplay();
display->module = module;
display->box.pos = Vec(2, 40);
display->box.size = Vec(box.size.x - 4, RACK_GRID_HEIGHT - 80);
addChild(display);
module->displayWidth = display->box.size.x;
module->displayHeight = display->box.size.y;
module->updateMinMax();
module->resetBall();

addChild(createScrew<Screw_J>(Vec(40, 20)));
addChild(createScrew<Screw_W>(Vec(55, 20)));

//top row
addParam(createParam<JwTinyKnob>(Vec(140, 20), module, BouncyBall::SCALE_X_PARAM, 0.01, 1.0, 0.5));
addParam(createParam<JwTinyKnob>(Vec(200, 20), module, BouncyBall::SCALE_Y_PARAM, 0.01, 1.0, 0.5));
addParam(createParam<JwTinyKnob>(Vec(260, 20), module, BouncyBall::OFFSET_X_VOLTS_PARAM, -5.0, 5.0, 5.0));
addParam(createParam<JwTinyKnob>(Vec(320, 20), module, BouncyBall::OFFSET_Y_VOLTS_PARAM, -5.0, 5.0, 5.0));

//bottom row
addInput(createInput<TinyPJ301MPort>(Vec(20, 360), module, BouncyBall::RESET_INPUT));
addInput(createInput<TinyPJ301MPort>(Vec(60, 360), module, BouncyBall::BUMP_INPUT));

{
BouncyBallDisplay *display = new BouncyBallDisplay();
display->module = module;
display->box.pos = Vec(2, 2);
display->box.size = Vec(box.size.x - 4, RACK_GRID_HEIGHT - 40);
addChild(display);

module->displayWidth = display->box.size.x;
module->displayHeight = display->box.size.y;
module->updateMinMax();
module->defaultPos();
}
addParam(createParam<JwTinyKnob>(Vec(100, 360), module, BouncyBall::VELOCITY_X_PARAM, -1.0, 1.0, 0.0));
addParam(createParam<JwTinyKnob>(Vec(130, 360), module, BouncyBall::VELOCITY_Y_PARAM, -1.0, 1.0, 0.0));
addParam(createParam<JwTinyKnob>(Vec(165, 360), module, BouncyBall::SPEED_MULT_PARAM, 1.0, 20.0, 1.0));

rack::Label* const titleLabel = new rack::Label;
titleLabel->box.pos = Vec(3, 350);
titleLabel->text = "Bouncy Ball";
addChild(titleLabel);

////////////////////////////////////////////////////////////
rack::Label* const xOffsetLabel = new rack::Label;
xOffsetLabel->box.pos = Vec(120-20, 340);
xOffsetLabel->text = "X OFST";
addChild(xOffsetLabel);

rack::Label* const yOffsetLabel = new rack::Label;
yOffsetLabel->box.pos = Vec(180-20, 340);
yOffsetLabel->text = "Y OFST";
addChild(yOffsetLabel);

rack::Label* const xLabel = new rack::Label;
xLabel->box.pos = Vec(240-4, 340);
xLabel->text = "X";
addChild(xLabel);

rack::Label* const yLabel = new rack::Label;
yLabel->box.pos = Vec(260-4, 340);
yLabel->text = "Y";
addChild(yLabel);

rack::Label* const gLabel = new rack::Label;
gLabel->box.pos = Vec(340-5, 340);
gLabel->text = "G";
addChild(gLabel);

addParam(createParam<JwTinyKnob>(Vec(120, 360), module, BouncyBall::OFFSET_X_VOLTS_PARAM, -5.0, 5.0, 0.0));
addParam(createParam<JwTinyKnob>(Vec(180, 360), module, BouncyBall::OFFSET_Y_VOLTS_PARAM, -5.0, 5.0, 0.0));
addOutput(createOutput<TinyPJ301MPort>(Vec(240, 360), module, BouncyBall::X_OUTPUT));
addOutput(createOutput<TinyPJ301MPort>(Vec(260, 360), module, BouncyBall::Y_OUTPUT));
addOutput(createOutput<TinyPJ301MPort>(Vec(340, 360), module, BouncyBall::GATE_OUTPUT));
//TODO ADD Gravity knob back in and add labels in SVG
// addParam(createParam<JwTinyKnob>(Vec(195, 360), module, BouncyBall::GRAVITY_PARAM, -1.0, 1.0, 0.0));

addParam(createParam<JwTinyKnob>(Vec(195, 360), module, BouncyBall::DECAY_PARAM, 1.0, 0.0, 1.0));

addOutput(createOutput<TinyPJ301MPort>(Vec(225, 360), module, BouncyBall::X_OUTPUT));
addOutput(createOutput<TinyPJ301MPort>(Vec(250, 360), module, BouncyBall::Y_OUTPUT));
addOutput(createOutput<TinyPJ301MPort>(Vec(280, 360), module, BouncyBall::NORTH_GATE_OUTPUT));
addOutput(createOutput<TinyPJ301MPort>(Vec(300, 360), module, BouncyBall::EAST_GATE_OUTPUT));
addOutput(createOutput<TinyPJ301MPort>(Vec(320, 360), module, BouncyBall::SOUTH_GATE_OUTPUT));
addOutput(createOutput<TinyPJ301MPort>(Vec(340, 360), module, BouncyBall::WEST_GATE_OUTPUT));
}

17 changes: 9 additions & 8 deletions src/JWModules.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ void init(rack::Plugin *p) {
p->version = TOSTRING(VERSION);
#endif
std::string me = "JW-Modules";
p->addModel(createModel<FullScopeWidget>(me, "FullScope", "FullScope", VISUAL_TAG));
p->addModel(createModel<GridSeqWidget>(me, "GridSeq", "GridSeq", SEQUENCER_TAG));
p->addModel(createModel<QuantizerWidget>(me, "Quantizer", "Quantizer", QUANTIZER_TAG));
p->addModel(createModel<MinMaxWidget>(me, "MinMax", "MinMax", UTILITY_TAG));
p->addModel(createModel<SimpleClockWidget>(me, "SimpleClock", "SimpleClock", CLOCK_TAG, RANDOM_TAG));
p->addModel(createModel<WavHeadWidget>("JW-Modules", "WavHead", "WavHead", VISUAL_TAG));
p->addModel(createModel<XYPadWidget>(me, "XYPad", "XYPad", LFO_TAG, ENVELOPE_GENERATOR_TAG, RANDOM_TAG, OSCILLATOR_TAG, SAMPLE_AND_HOLD_TAG));
// p->addModel(createModel<BouncyBallWidget>("JW-Modules", "JW-Modules", "BouncyBall", "BouncyBall"));
// DON'T CHANGE SLUG LABEL
p->addModel(createModel<FullScopeWidget>(me, "FullScope", "FullScope", VISUAL_TAG));
p->addModel(createModel<GridSeqWidget>(me, "GridSeq", "GridSeq", SEQUENCER_TAG));
p->addModel(createModel<QuantizerWidget>(me, "Quantizer", "Quantizer", QUANTIZER_TAG));
p->addModel(createModel<MinMaxWidget>(me, "MinMax", "MinMax", UTILITY_TAG));
p->addModel(createModel<SimpleClockWidget>(me, "SimpleClock", "SimpleClock", CLOCK_TAG, RANDOM_TAG));
p->addModel(createModel<WavHeadWidget>(me, "WavHead", "WavHead", VISUAL_TAG));
p->addModel(createModel<XYPadWidget>(me, "XYPad", "XYPad", LFO_TAG, ENVELOPE_GENERATOR_TAG, RANDOM_TAG, OSCILLATOR_TAG, SAMPLE_AND_HOLD_TAG));
// p->addModel(createModel<BouncyBallWidget>(me, "BouncyBall", "BouncyBall", SEQUENCER_TAG, VISUAL_TAG));
}
7 changes: 7 additions & 0 deletions src/JWModules.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,10 @@ struct ScaleKnob : SmallWhiteKnob {
return quantizeUtils->scaleName(int(value));
}
};

struct BPMKnob : SmallWhiteKnob {
BPMKnob(){}
std::string formatCurrentValue() {
return std::to_string(static_cast<unsigned int>(powf(2.0, value)*60.0)) + " BPM";
}
};
14 changes: 10 additions & 4 deletions src/SimpleClock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ void SimpleClock::step() {
if (phase >= 1.0) {
phase -= 1.0;
nextStep = true;
// lights[CLOCK_LIGHT].value = 1.0;
}
}
if (nextStep) {
Expand Down Expand Up @@ -132,9 +131,16 @@ SimpleClockWidget::SimpleClockWidget() {

addParam(createParam<TinyButton>(Vec(23, 40), module, SimpleClock::RUN_PARAM, 0.0, 1.0, 0.0));
addChild(createLight<SmallLight<MyBlueValueLight>>(Vec(23+3.75, 40+3.75), module, SimpleClock::RUNNING_LIGHT));

addParam(createParam<SmallWhiteKnob>(Vec(17, 60), module, SimpleClock::CLOCK_PARAM, -2.0, 6.0, 2.0));
addOutput(createOutput<PJ301MPort>(Vec(18, 90), module, SimpleClock::CLOCK_OUTPUT));

BPMKnob *clockKnob = dynamic_cast<BPMKnob*>(createParam<BPMKnob>(Vec(17, 60), module, SimpleClock::CLOCK_PARAM, -2.0, 6.0, 1.0));
CenteredLabel* const bpmLabel = new CenteredLabel;
bpmLabel->box.pos = Vec(15, 50);
bpmLabel->text = "0";
clockKnob->connectLabel(bpmLabel);
addChild(bpmLabel);
addParam(clockKnob);

addOutput(createOutput<PJ301MPort>(Vec(18, 105), module, SimpleClock::CLOCK_OUTPUT));

CenteredLabel* const resetLabel = new CenteredLabel;
resetLabel->box.pos = Vec(15, 75);
Expand Down
Loading

0 comments on commit 0b5f558

Please sign in to comment.