| @@ -42,6 +42,7 @@ struct Frames : Module { | |||
| frames::Keyframer keyframer; | |||
| frames::PolyLfo poly_lfo; | |||
| bool poly_lfo_mode = true; | |||
| uint16_t lastControls[4] = {}; | |||
| SchmittTrigger addTrigger; | |||
| SchmittTrigger delTrigger; | |||
| @@ -49,6 +50,18 @@ struct Frames : Module { | |||
| Frames(); | |||
| void step(); | |||
| json_t *toJson() { | |||
| json_t *rootJ = json_object(); | |||
| json_object_set_new(rootJ, "polyLfo", json_boolean(poly_lfo_mode)); | |||
| return rootJ; | |||
| } | |||
| void fromJson(json_t *rootJ) { | |||
| json_t *polyLfoJ = json_object_get(rootJ, "polyLfo"); | |||
| if (polyLfoJ) | |||
| poly_lfo_mode = json_boolean_value(polyLfoJ); | |||
| } | |||
| }; | |||
| @@ -57,46 +70,64 @@ Frames::Frames() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { | |||
| keyframer.Init(); | |||
| memset(&poly_lfo, 0, sizeof(poly_lfo)); | |||
| poly_lfo.Init(); | |||
| for (int i = 0; i < 4; i++) { | |||
| keyframer.mutable_settings(i)->easing_curve = frames::EASING_CURVE_LINEAR; | |||
| } | |||
| } | |||
| void Frames::step() { | |||
| // Handle buttons | |||
| if (clearKeyframes) { | |||
| keyframer.Clear(); | |||
| clearKeyframes = false; | |||
| // Set gain and timestamp knobs | |||
| uint16_t controls[4]; | |||
| for (int i = 0; i < 4; i++) { | |||
| controls[i] = params[GAIN1_PARAM + i].value * 65535.0; | |||
| } | |||
| // Set gain knobs | |||
| if (poly_lfo_mode) { | |||
| poly_lfo.set_shape(params[GAIN1_PARAM].value * 65535.0); | |||
| poly_lfo.set_shape_spread(params[GAIN2_PARAM].value * 65535.0); | |||
| poly_lfo.set_spread(params[GAIN3_PARAM].value * 65535.0); | |||
| poly_lfo.set_coupling(params[GAIN4_PARAM].value * 65535.0); | |||
| } | |||
| else { | |||
| for (int i = 0; i < 4; i++) { | |||
| keyframer.set_immediate(i, params[GAIN1_PARAM + i].value * 65535.0); | |||
| } | |||
| int32_t timestamp = clampf(params[FRAME_PARAM].value + params[MODULATION_PARAM].value * inputs[FRAME_INPUT].value / 10.0, 0.0, 1.0) * 65535.0; | |||
| int16_t nearestIndex = -1; | |||
| if (!poly_lfo_mode) { | |||
| nearestIndex = keyframer.FindNearestKeyframe(timestamp, 2048); | |||
| } | |||
| int32_t frame = clampf(params[FRAME_PARAM].value + params[MODULATION_PARAM].value * inputs[FRAME_INPUT].value / 10.0, 0.0, 1.0) * 65535.0; | |||
| // Render, handle buttons | |||
| if (poly_lfo_mode) { | |||
| poly_lfo.Render(frame); | |||
| if (controls[0] != lastControls[0]) | |||
| poly_lfo.set_shape(controls[0]); | |||
| if (controls[1] != lastControls[1]) | |||
| poly_lfo.set_shape_spread(controls[1]); | |||
| if (controls[2] != lastControls[2]) | |||
| poly_lfo.set_spread(controls[2]); | |||
| if (controls[3] != lastControls[3]) | |||
| poly_lfo.set_coupling(controls[3]); | |||
| poly_lfo.Render(timestamp); | |||
| } | |||
| else { | |||
| int16_t nearestFrame = keyframer.FindNearestKeyframe(frame, 2048); | |||
| for (int i = 0; i < 4; i++) { | |||
| if (controls[i] != lastControls[i]) { | |||
| // Update recently moved control | |||
| if (keyframer.num_keyframes() == 0) { | |||
| keyframer.set_immediate(i, controls[i]); | |||
| } | |||
| if (nearestIndex >= 0) { | |||
| frames::Keyframe *nearestKeyframe = keyframer.mutable_keyframe(nearestIndex); | |||
| nearestKeyframe->values[i] = controls[i]; | |||
| } | |||
| } | |||
| } | |||
| if (addTrigger.process(params[ADD_PARAM].value)) { | |||
| uint16_t f[4] = {65000, 30000, 20000, 10000}; | |||
| keyframer.AddKeyframe(frame, f); | |||
| if (nearestIndex < 0) { | |||
| keyframer.AddKeyframe(timestamp, controls); | |||
| } | |||
| } | |||
| if (delTrigger.process(params[DEL_PARAM].value)) { | |||
| keyframer.RemoveKeyframe(frame); | |||
| if (nearestIndex >= 0) { | |||
| int32_t nearestTimestamp = keyframer.keyframe(nearestIndex).timestamp; | |||
| keyframer.RemoveKeyframe(nearestTimestamp); | |||
| } | |||
| } | |||
| keyframer.Evaluate(frame); | |||
| keyframer.Evaluate(timestamp); | |||
| } | |||
| // Get gains | |||
| @@ -111,48 +142,61 @@ void Frames::step() { | |||
| gains[i] = lin; | |||
| } | |||
| } | |||
| // printf("%f %f %f %f\n", gains[0], gains[1], gains[2], gains[3]); | |||
| // Update last controls | |||
| for (int i = 0; i < 4; i++) { | |||
| lastControls[i] = controls[i]; | |||
| } | |||
| // Get inputs | |||
| float all = ((int)params[OFFSET_PARAM].value == 1) ? 10.0 : 0.0; | |||
| if (inputs[ALL_INPUT].active) | |||
| if (inputs[ALL_INPUT].active) { | |||
| all = inputs[ALL_INPUT].value; | |||
| } | |||
| float ins[4]; | |||
| for (int i = 0; i < 4; i++) | |||
| for (int i = 0; i < 4; i++) { | |||
| ins[i] = inputs[IN1_INPUT + i].normalize(all) * gains[i]; | |||
| } | |||
| // Set outputs | |||
| float mix = 0.0; | |||
| for (int i = 0; i < 4; i++) { | |||
| if (outputs[OUT1_OUTPUT + i].active) | |||
| if (outputs[OUT1_OUTPUT + i].active) { | |||
| outputs[OUT1_OUTPUT + i].value = ins[i]; | |||
| else | |||
| } | |||
| else { | |||
| mix += ins[i]; | |||
| } | |||
| } | |||
| outputs[MIX_OUTPUT].value = clampf(mix / 2.0, -10.0, 10.0); | |||
| // Set lights | |||
| for (int i = 0; i < 4; i++) | |||
| for (int i = 0; i < 4; i++) { | |||
| outputs[GAIN1_LIGHT + i].value = gains[i]; | |||
| } | |||
| if (poly_lfo_mode) { | |||
| outputs[EDIT_LIGHT].value = (poly_lfo.level(0) > 128 ? 1.0 : 0.0); | |||
| } | |||
| else { | |||
| outputs[EDIT_LIGHT].value = 1.0; | |||
| // TODO | |||
| outputs[EDIT_LIGHT].value = (nearestIndex >= 0 ? 1.0 : 0.0); | |||
| } | |||
| // Set frame light colors | |||
| const uint8_t *colors; | |||
| if (poly_lfo_mode) | |||
| if (poly_lfo_mode) { | |||
| colors = poly_lfo.color(); | |||
| else | |||
| } | |||
| else { | |||
| colors = keyframer.color(); | |||
| for (int c = 0; c < 3; c++) | |||
| } | |||
| for (int c = 0; c < 3; c++) { | |||
| outputs[FRAME_LIGHT + c].value = colors[c] / 255.0; | |||
| } | |||
| } | |||
| @@ -244,24 +288,48 @@ FramesWidget::FramesWidget() { | |||
| } | |||
| struct FramesCurveItem : MenuItem { | |||
| Frames *frames; | |||
| uint8_t channel; | |||
| frames::EasingCurve curve; | |||
| void onAction() { | |||
| frames->keyframer.mutable_settings(channel)->easing_curve = curve; | |||
| } | |||
| void step() { | |||
| rightText = (frames->keyframer.mutable_settings(channel)->easing_curve == curve) ? "✔" : ""; | |||
| } | |||
| }; | |||
| struct FramesResponseItem : MenuItem { | |||
| Frames *frames; | |||
| uint8_t channel; | |||
| uint8_t response; | |||
| void onAction() { | |||
| frames->keyframer.mutable_settings(channel)->response = response; | |||
| } | |||
| void step() { | |||
| rightText = (frames->keyframer.mutable_settings(channel)->response = response) ? "✔" : ""; | |||
| } | |||
| }; | |||
| struct FramesChannelSettingsItem : MenuItem { | |||
| Frames *frames; | |||
| int channel; | |||
| uint8_t channel; | |||
| Menu *createChildMenu() { | |||
| Menu *menu = new Menu(); | |||
| // TODO | |||
| menu->pushChild(construct<MenuLabel>(&MenuEntry::text, "Interpolation Curve")); | |||
| menu->pushChild(construct<MenuItem>(&MenuEntry::text, "Step")); | |||
| menu->pushChild(construct<MenuItem>(&MenuEntry::text, "Linear")); | |||
| menu->pushChild(construct<MenuItem>(&MenuEntry::text, "Accelerating")); | |||
| menu->pushChild(construct<MenuItem>(&MenuEntry::text, "Decelerating")); | |||
| menu->pushChild(construct<MenuItem>(&MenuEntry::text, "Smooth Departure/Arrival")); | |||
| menu->pushChild(construct<MenuItem>(&MenuEntry::text, "Bouncing")); | |||
| menu->pushChild(construct<FramesCurveItem>(&MenuEntry::text, "Step", &FramesCurveItem::frames, frames, &FramesCurveItem::channel, channel, &FramesCurveItem::curve, frames::EASING_CURVE_STEP)); | |||
| menu->pushChild(construct<FramesCurveItem>(&MenuEntry::text, "Linear", &FramesCurveItem::frames, frames, &FramesCurveItem::channel, channel, &FramesCurveItem::curve, frames::EASING_CURVE_LINEAR)); | |||
| menu->pushChild(construct<FramesCurveItem>(&MenuEntry::text, "Accelerating", &FramesCurveItem::frames, frames, &FramesCurveItem::channel, channel, &FramesCurveItem::curve, frames::EASING_CURVE_IN_QUARTIC)); | |||
| menu->pushChild(construct<FramesCurveItem>(&MenuEntry::text, "Decelerating", &FramesCurveItem::frames, frames, &FramesCurveItem::channel, channel, &FramesCurveItem::curve, frames::EASING_CURVE_OUT_QUARTIC)); | |||
| menu->pushChild(construct<FramesCurveItem>(&MenuEntry::text, "Smooth Departure/Arrival", &FramesCurveItem::frames, frames, &FramesCurveItem::channel, channel, &FramesCurveItem::curve, frames::EASING_CURVE_SINE)); | |||
| menu->pushChild(construct<FramesCurveItem>(&MenuEntry::text, "Bouncing", &FramesCurveItem::frames, frames, &FramesCurveItem::channel, channel, &FramesCurveItem::curve, frames::EASING_CURVE_BOUNCE)); | |||
| menu->pushChild(construct<MenuLabel>()); | |||
| menu->pushChild(construct<MenuLabel>(&MenuEntry::text, "Response Curve")); | |||
| menu->pushChild(construct<MenuItem>(&MenuEntry::text, "Linear")); | |||
| menu->pushChild(construct<MenuItem>(&MenuEntry::text, "Exponential")); | |||
| menu->pushChild(construct<FramesResponseItem>(&MenuEntry::text, "Linear", &FramesResponseItem::frames, frames, &FramesResponseItem::channel, channel, &FramesResponseItem::response, 0)); | |||
| menu->pushChild(construct<FramesResponseItem>(&MenuEntry::text, "Exponential", &FramesResponseItem::frames, frames, &FramesResponseItem::channel, channel, &FramesResponseItem::response, 1)); | |||
| return menu; | |||
| } | |||
| @@ -270,7 +338,7 @@ struct FramesChannelSettingsItem : MenuItem { | |||
| struct FramesClearItem : MenuItem { | |||
| Frames *frames; | |||
| void onAction() { | |||
| frames->clearKeyframes = true; | |||
| frames->keyframer.Clear(); | |||
| } | |||
| }; | |||