spuder / MuteMe-arduino

Other
3 stars 2 forks source link

Breathing effect improvements #4

Open spuder opened 2 years ago

spuder commented 2 years ago

The current breathing light goes from 0% to 100%.

Change it so it matches the official mute me

https://makersportal.com/blog/2020/3/27/simple-breathing-led-in-arduino

May need to convert to the FastLED library since the RGBLed library can't limit min brightness

spuder commented 2 years ago

Fast https://user-images.githubusercontent.com/242382/184261391-9ba8cd16-b8ee-476c-a5a6-3a1c2333ea8c.MOV

Slow breaths per minute ~25 https://user-images.githubusercontent.com/242382/184261399-7f817831-71d4-4224-99cc-0cbe34375692.MOV

spuder commented 2 years ago

Slow = 2.35 seconds

Values between 460 and 470 are pretty close

// RGB LED Circle Wave Breathing LED

int led_pins[1] = {6};
int jj = 0; // 0 = red, 1 = green, 2 = blue
float smoothness_pts = 463;//larger=slower change in brightness

void setup() {

  pinMode(2, INPUT_PULLUP);
  Serial.begin(9600);
  for (int ii = 0;ii<sizeof(led_pins)/sizeof(int);ii++){
    pinMode(led_pins[ii],OUTPUT);
  }
}

void loop() {

  for (int ii=0;ii<smoothness_pts;ii++){
    float pwm_val = 255.0*sqrt(1.0 -  pow(abs((2.0*(ii/smoothness_pts))-1.0),2.0));
    analogWrite(led_pins[jj],int(pwm_val));
    auto pin = digitalRead(2);
    if (pin == LOW) {
      ii=0;
    }
    delay(5);
//    Serial.println(int(pwm_val));
  }
}

Screen Shot 2022-08-12 at 11 03 31 AM

spuder commented 2 years ago

https://github.com/wilmouths/RGBLed/issues/9

spuder commented 2 years ago

Current breathing is blocking

https://avital.ca/notes/a-closer-look-at-apples-breathing-light

spuder commented 2 years ago

The gausian is very pleasing, however it is too subtile compared to the official mute me. The mute me has more of an urgency feeling to it

// RGB LED Gaussian Wave Breathing LED

int led_pins[1] = {6};
int jj = 1; // 0 = red, 1 = green, 2 = blue
float smoothness_pts = 500;//larger=slower change in brightness  

float gamma = 0.14; // affects the width of peak (more or less darkness)
float beta = 0.5; // shifts the gaussian to be symmetric

void setup() {
  Serial.begin(9600);
  for (int ii = 0;ii<sizeof(led_pins)/sizeof(int);ii++){
    pinMode(led_pins[ii],OUTPUT);
  }
}

void loop() {
  for (int ii=0;ii<smoothness_pts;ii++){
    float pwm_val = 255.0*(exp(-(pow(((ii/smoothness_pts)-beta)/gamma,2.0))/2.0));
    analogWrite(led_pins[jj],int(pwm_val));
    delay(5);
    Serial.println(int(pwm_val));
  }
}
spuder commented 2 years ago

This is really really close.

https://thingpulse.com/breathing-leds-cracking-the-algorithm-behind-our-breathing-pattern

#include <math.h>

void setup() {
  pinMode(6, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  //1300 = slower
  //1100 = faster
  auto period = 1200.0;// 2000.0
  float val = (exp(sin(millis()/period * PI)) - 0.368) * 108.0;
  Serial.print(0);
  Serial.print(" ");
  Serial.print(val);
  Serial.print(" ");
  Serial.println(255);
  analogWrite(6, val);
}
spuder commented 2 years ago

Here is how this library solves it. Note that it only runs the calculation once per millisecond

https://github.com/SethSenpai/singleLEDLibrary/blob/master/singleLEDLibrary.cpp#L108-L112

spuder commented 2 years ago

I tried to pregenerate all the values, but it takes too much memory.


float ledBrightnessTable[2400];

float table[2400];
auto now = millis();
auto counter = 0;

void setup() {

  while (millis() < (now + 2400)) {

    if (now != millis() ) {
      auto period = 1200.0;// 2000.0
      float val = (exp(sin(millis()/period * PI)) - 0.368) * 108.0;
      table[counter] = val;
      counter++;
      now=millis();
    }

  }

  Serial.begin(9600);
  Serial.print("Generated table");
  for (auto i = 0; i < sizeof(table); i++) {
    Serial.print(table[i]);
    Serial.print(',');
  }
}

void loop() {

}
spuder commented 2 years ago

New problem. Whenever the values is ~ 127 the led turns off for a split second.

void Led::pulse(int period) {
    if((last_refresh_time + 1) < millis() ){
        last_refresh_time = millis();
        // https://thingpulse.com/breathing-leds-cracking-the-algorithm-behind-our-breathing-pattern/
        float min =  0.36787944;
        float amplitude = 42.54590641;
        uint8_t b = (exp(sin(millis()/(float)period*PI)) - min)* amplitude;
        this->brightness = (byte)b * 255 / 100;
        Serial.print(0);
        Serial.print(" ");
        Serial.print(255);
        Serial.print(" ");
        Serial.print(127);
        Serial.print(" ");
        Serial.println(this->brightness);

        byte red_brightness = (mapRed(this->color) * this->brightness) / 100;
        byte green_brightness = (mapGreen(this->color) * this->brightness) / 100;
        byte blue_brightness = (mapBlue(this->color) * this->brightness) / 100;

        invertAnalogWrite(this->red_pin, red_brightness);
        invertAnalogWrite(this->green_pin, green_brightness);
        invertAnalogWrite(this->blue_pin, blue_brightness);
    }
}
Screen Shot 2022-08-14 at 3 35 35 PM

Here is a dump of the data. I expect to see a 0 somwhere around 127, but I never see that. There doesn't appear to be any gaps that would explain why the LED turns off briefly in the pulse sequence. (Captured at 115200)

output.txt

spuder commented 2 years ago

This does not clip

#include <math.h>

void setup() {
  pinMode(6, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  auto period = 1200.0;
  float min =  0.381966011;
  float amplitude = 42.0; //42.54590641;
  uint8_t b = (exp(sin(millis()/(float)period*PI)) - min)* amplitude;
  b = b * 255 / 100; 

  Serial.print(0);
  Serial.print(" ");
  Serial.print(255);
  Serial.print(" ");
  Serial.print(127);
  Serial.print(" ");
  Serial.println(b);
  analogWrite(6, 255 - b);
}
spuder commented 2 years ago

Solved with constrain b = constrain(b * 255 / 100, 0, 255) ;

#include <math.h>

void setup() {
  pinMode(6, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  //1300 = slower
  //1100 = faster
  auto period = 1200.0;// 2000.0

  float min =  0.25; // how curved the bell is (also has an impact on max altitude)
  float amplitude = 42.0; //42.54590641;
  uint8_t b = (exp(sin(millis()/(float)period*PI)) - min)* amplitude;
  b = constrain(b * 255 / 100, 0, 255) ; 

  Serial.print(0);
  Serial.print(" ");
  Serial.print(255);
  Serial.print(" ");
  Serial.print(127);
  Serial.print(" ");
  Serial.println(b);
  analogWrite(6, 255 - b);
}
spuder commented 2 years ago

I tried to create a histogram showing the function. Unfortunatly 30 frames per second just isn't that much data ( I really need data every 10/100 milliseconds).

Convert video to images

docker run -it --entrypoint /bin/bash --workdir /data -v $PWD:/data linuxserver/ffmpeg:version-4.4-cli
ffmpeg -i ../breathing-left.mp4 %04d.png

Convert images to histogram

docker run -it  -v $PWD:/data --workdir /data --entrypoint /bin/bash dpokidov/imagemagick
# https://stackoverflow.com/a/51332870/1626687
function foobar { 
echo "$1" && convert "$1" -colorspace HSI -channel b -separate +channel -scale 1x1 -format "%[fx:100*u]\n" info: >> /tmp/foobar.txt;
}
export -f foobar
rm /tmp/foobar.txt
find . -type f -name "*.png" -exec bash -c 'foobar "$1"' _ {} \;
cp /tmp/foobar.txt /data
Screen Shot 2022-08-14 at 9 10 25 PM

Nevertheless it still appears to be a gaussian or a sinusoid function

https://makersportal.com/blog/2020/3/27/simple-breathing-led-in-arduino

foobar.txt

spuder commented 2 years ago

This is pretty good. It never goes fully dark. However it is blocking which isn't ideal

// RGB LED Gaussian Wave Breathing LED

int led_pins[1] = {10};
int jj = 1; // 0 = red, 1 = green, 2 = blue
float smoothness_pts = 485;//larger=slower change in brightness  

float gamma = 0.250; // affects the width of peak (more or less darkness)
float beta = 0.5; // shifts the gaussian to be symmetric

void setup() {
  Serial.begin(9600);
  for (int ii = 0;ii<sizeof(led_pins)/sizeof(int);ii++){
    pinMode(led_pins[ii],OUTPUT);
  }
}

void loop() {
  for (int ii=0;ii<smoothness_pts;ii++){
    float pwm_val = 255.0*(exp(-(pow(((ii/smoothness_pts)-beta)/gamma,2.0))/2.0));
    analogWrite(led_pins[jj],255-int(pwm_val));
    delay(5);
    Serial.println(255-int(pwm_val));
  }
}
Screen Shot 2022-08-14 at 9 59 22 PM
spuder commented 2 years ago

More testing. I setup a 2400 second loop and discovered that the period isn't exactly 2400 seconds. After 20-30 loops the leds get out of sync. 🤔 The ramp up and ramp down might be different durations.

void setup() {
  pinMode(6, OUTPUT);
}

void loop() {
  analogWrite(6, 0);
  delay(1200);
  analogWrite(6, 255);
  delay(1200);
}
spuder commented 2 years ago

Tried to visualize the graph with processing.

String[] data; 
float[] newData;
int x = 100;
int counter = 0;
void setup() {
  frameRate(24);
  data = loadStrings("/Users/spencer.owen/Desktop/muteme-single-wave.txt");
  newData = new float[data.length];
  for (int i = 0; i < data.length; i++) {
    print("data: ");
    print(data[i]);
    newData[i] = map(float(data[i]),42.0, 66.0, 0, 255);
    print(" newData: ");
    print(newData[i]);
    println();
  }
  size(400, 400);
  noStroke();
}
void draw() {
  background(155);
  ellipse(counter, newData[counter], 50, 50);
  if (counter+1 >= newData.length) {
    counter = 0;
  }
  else {
    counter++;
  }
}

I'm realizing that only capturing 30fps on the phone isn't enough data points

https://user-images.githubusercontent.com/242382/184798221-5c4b51ce-f0f5-49e9-8682-b0c6484ddc90.mov

spuder commented 2 years ago

I refilmed at 240 hertz (slow mo) on my iphone with locked exposure. I believe my phone was compensating exposure which would explain why the top of the wave looked slanted.

Running for ~12 seconds gave 2305 datapoints.

cd ~/Desktop/breathing
docker run -it --entrypoint /bin/bash --workdir /data -v $PWD:/data linuxserver/ffmpeg:version-4.4-cli
ffmpeg -i ./breathing-slowmo.mov %04d.png
docker run -it  -v $PWD:/data --workdir /data --entrypoint /bin/bash dpokidov/imagemagick
# https://stackoverflow.com/a/51332870/1626687
function foobar { 
echo "$1" && convert "$1" -colorspace HSI -channel b -separate +channel -scale 1x1 -format "%[fx:100*u]\n" info: >> /tmp/foobar.txt;
}
export -f foobar
rm /tmp/foobar.txt
find . -type f -name "*.png" -exec bash -c 'foobar "$1"' _ {} \;
cp /tmp/foobar.txt /data

Screen Shot 2022-08-16 at 2 55 34 PM

spuder commented 2 years ago

I can see how it is clipping/flickering, but no idea why

float b = (exp(sin(millis()/(float)period*PI)) - 0.36787944)* 108.0;

Screen Shot 2022-08-16 at 5 28 33 PM

clipping.txt

spuder commented 2 years ago

Timed several more and found 10 pulses exactly equaled 23.5 seconds.

23.5 * 6 = 14.1 pulses per minute

T (period) = 2.35 seconds F = 1flash / 2.35 seconds = 0.42553191 HZ

spuder commented 2 years ago

Fixed studdering

byte red_brightness = (mapRed(this->color) * this->brightness) / 100;

Should be byte red_brightness = (mapRed(this->color) * this->brightness) / 255;

spuder commented 2 years ago

Additional tests on the period

1175 should theoretically be exact, but feels fast
1175 = 23.4 seconds for 10 cycles
1180 = 23.6 seconds for 10 cycles
1190 = 23.8 seconds for 10 cycles
1200 = 23.8 seconds for 10 cycles
1202 = 23.84 seconds for 10 cycles
1210 = 24.3 seconds for 10 cycles
1204 = 23.9 seconds for 10 cycles
1205 = 23.93 seconds for 10 cycles

mute me = 23.9 seconds for 10 cycles
mute me = 23.9 seconds for 10 cycles
mute me = 23.9 seconds for 10 cycles