I can't say exactly how your program should be written, but here is some general advice which may (or may not) help.
I would add a variable which keeps track of the intensity level you're currently displaying. Maybe this would be a single integer where 0 means off, a certain range of numbers means green, and another high range means red. Or perhaps it would be 2 variables, maybe one for intensity and another for color. It's really a matter of style. If the red display is a natural extension of the green, I would probably prefer to keep the variable simple even if more code is needed. If they really are disjoint meaning, I'd probably go with 2 variables. But whatever way makes most sense to you is probably best.
Before restructuring the program, I would probably create a function which sets the LED color & intensity. The function should take a simple input which intuitively makes sense, even if that means more work inside the function to turn it into the analogWrite() numbers. For example, the function could take 0 to 2.0, where 0 mean off, 0.0001 to 1.0 means green, and 1.00001 to 2.0 means red. For maybe the whole range is just 0 to 1.0, where the top portion from 0.8 to 1.0 means red? The idea is to get the details of analogWrite away from your code which makes the decisions, so don't use a scale of 255 in the rest of your program, unless that truly is the way you think about the intensity. The smaller and simpler your decision code can be, the easier it will be to understand and troubleshoot. I'd make that function the *only* place which writes to that current state variable(s). Then everywhere else in your program can read it, but the code inside that function which deals with conversion to the analogWrite numbers ought to be the only place that updates the variable indicating the currently showing colors/intensity.
In loop(), I would delete all delay() and commit to never adding another delay() call.
For a first attempt, I would try use the 1 elapsedMillis variable you already have. I would try to structure the code where you process new peak info separately from the passage of time. For example:
Code:
float current_intensity = 0.0;
elapsedMillis msecs;
void loop() {
if (peak1.available() && peak2.available()) {
float leftNumber = peak1.read();
if (leftNumber > current_intensity) {
// new peak is higher
show_on_LED(leftNumber);
} else {
// new peak is lower, do nothing
}
}
if (msecs > 20) {
msecs = 0;
if (current_intensity > 0) {
show_on_LED(current_intensity - 0.05); // decrease over time - code inside show_on_LED() to treat negative as zero
}
}
}
This program structure will let you immediately react to new peaks. By putting the time response as separate code, you'll also be able to write code to make the display change over time.
Again, the key to success (at least for me) on this sort of code is representing the state in a way that makes intuitive sense. I would put all the conversion of 0-255 for analogWrite and any other details of updating the hardware into a function that I don't need to view while focusing the main code which decides what to do.
You might decide to intermix the handling of new peaks and handling of time elapsing, but usually this sort of project is simpler if you can keep the immediate "live" response and code which create ongoing animation over time as separate as possible.
Not sure if this really solves your problem, but hopefully it helps.