Improved ripple effect

This commit is contained in:
cschwinne 2019-02-02 15:31:43 +01:00
parent 10c51eea2c
commit b7b6d0a6bc
3 changed files with 89 additions and 126 deletions

View File

@ -26,23 +26,15 @@
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
CHANGELOG
2016-05-28 Initial beta release Modified heavily for WLED
2016-06-03 Code cleanup, minor improvements, new modes
2016-06-04 2 new fx, fixed setColor (now also resets _mode_color)
2017-02-02 added external trigger functionality (e.g. for sound-to-light)
2017-02-02 removed "blackout" on mode, speed or color-change
2017-09-26 implemented segment and reverse features
2017-11-16 changed speed calc, reduced memory footprint
2018-02-24 added hooks for user created custom effects
Modified for WLED
*/ */
#include "WS2812FX.h" #include "WS2812FX.h"
#include "palettes.h" #include "palettes.h"
#define IBN 5100
#define LED_SKIP_AMOUNT 24 #define LED_SKIP_AMOUNT 24
void WS2812FX::init(bool supportWhite, uint16_t countPixels, bool skipFirst) void WS2812FX::init(bool supportWhite, uint16_t countPixels, bool skipFirst)
@ -93,11 +85,6 @@ void WS2812FX::service() {
} }
} }
void WS2812FX::clear()
{
bus->ClearTo(RgbColor(0));
}
bool WS2812FX::modeUsesLock(uint8_t m) bool WS2812FX::modeUsesLock(uint8_t m)
{ {
if (m < FX_MODE_FIRE_2012) return false; if (m < FX_MODE_FIRE_2012) return false;
@ -514,14 +501,22 @@ uint32_t WS2812FX::color_blend(uint32_t color1, uint32_t color2, uint8_t blend)
int g2 = (color2 >> 8) & 0xff; int g2 = (color2 >> 8) & 0xff;
int b2 = color2 & 0xff; int b2 = color2 & 0xff;
uint32_t w3 = ((w2 * blend) + (w1 * (255 - blend))) / 256; uint32_t w3 = ((w2 * blend) + (w1 * (255 - blend))) >> 8;
uint32_t r3 = ((r2 * blend) + (r1 * (255 - blend))) / 256; uint32_t r3 = ((r2 * blend) + (r1 * (255 - blend))) >> 8;
uint32_t g3 = ((g2 * blend) + (g1 * (255 - blend))) / 256; uint32_t g3 = ((g2 * blend) + (g1 * (255 - blend))) >> 8;
uint32_t b3 = ((b2 * blend) + (b1 * (255 - blend))) / 256; uint32_t b3 = ((b2 * blend) + (b1 * (255 - blend))) >> 8;
return ((w3 << 24) | (r3 << 16) | (g3 << 8) | (b3)); return ((w3 << 24) | (r3 << 16) | (g3 << 8) | (b3));
} }
/*
* Fills segment with color
*/
void WS2812FX::fill(uint32_t c) {
for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) {
setPixelColor(i, c);
}
}
/* ##################################################### /* #####################################################
# #
@ -529,15 +524,6 @@ uint32_t WS2812FX::color_blend(uint32_t color1, uint32_t color2, uint8_t blend)
# #
##################################################### */ ##################################################### */
/*
* Turns everything off. Doh.
*/
void WS2812FX::strip_off() {
clear();
show();
}
/* /*
* Put a value 0 to 255 in to get a color value. * Put a value 0 to 255 in to get a color value.
* The colours are a transition r -> g -> b -> back to r * The colours are a transition r -> g -> b -> back to r
@ -562,10 +548,7 @@ uint32_t WS2812FX::color_wheel(uint8_t pos) {
* Returns a new, random wheel index with a minimum distance of 42 from pos. * Returns a new, random wheel index with a minimum distance of 42 from pos.
*/ */
uint8_t WS2812FX::get_random_wheel_index(uint8_t pos) { uint8_t WS2812FX::get_random_wheel_index(uint8_t pos) {
uint8_t r = 0; uint8_t r = 0, x = 0, y = 0, d = 0;
uint8_t x = 0;
uint8_t y = 0;
uint8_t d = 0;
while(d < 42) { while(d < 42) {
r = random8(); r = random8();
@ -573,7 +556,6 @@ uint8_t WS2812FX::get_random_wheel_index(uint8_t pos) {
y = 255 - x; y = 255 - x;
d = min(x, y); d = min(x, y);
} }
return r; return r;
} }
@ -582,9 +564,7 @@ uint8_t WS2812FX::get_random_wheel_index(uint8_t pos) {
* No blinking. Just plain old static light. * No blinking. Just plain old static light.
*/ */
uint16_t WS2812FX::mode_static(void) { uint16_t WS2812FX::mode_static(void) {
for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { fill(SEGMENT.colors[0]);
setPixelColor(i, SEGMENT.colors[0]);
}
return (SEGMENT_RUNTIME.trans_act == 1) ? 20 : 500; return (SEGMENT_RUNTIME.trans_act == 1) ? 20 : 500;
} }
@ -603,9 +583,7 @@ uint16_t WS2812FX::blink(uint32_t color1, uint32_t color2, bool strobe, bool do_
setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0));
} }
} else { } else {
for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { fill(color);
setPixelColor(i, color);
}
} }
if((SEGMENT_RUNTIME.counter_mode_call & 1) == 0) { if((SEGMENT_RUNTIME.counter_mode_call & 1) == 0) {
@ -763,10 +741,10 @@ uint16_t WS2812FX::mode_breath(void) {
if (SEGMENT.palette == 0) if (SEGMENT.palette == 0)
{ {
uint32_t color = SEGMENT.colors[0]; uint32_t color = SEGMENT.colors[0];
uint8_t w = (color >> 24 & 0xFF) * lum / 256; uint8_t w = ((color >> 24 & 0xFF) * lum) >> 8;
uint8_t r = (color >> 16 & 0xFF) * lum / 256; uint8_t r = ((color >> 16 & 0xFF) * lum) >> 8;
uint8_t g = (color >> 8 & 0xFF) * lum / 256; uint8_t g = ((color >> 8 & 0xFF) * lum) >> 8;
uint8_t b = (color & 0xFF) * lum / 256; uint8_t b = ((color & 0xFF) * lum) >> 8;
for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) {
setPixelColor(i, r, g, b, w); setPixelColor(i, r, g, b, w);
} }
@ -789,7 +767,7 @@ uint16_t WS2812FX::mode_breath(void) {
uint16_t WS2812FX::mode_fade(void) { uint16_t WS2812FX::mode_fade(void) {
int lum = SEGMENT_RUNTIME.counter_mode_step; int lum = SEGMENT_RUNTIME.counter_mode_step;
if(lum > 255) lum = 511 - lum; // lum = 0 -> 255 -> 0 if(lum > 255) lum = 511 - lum; // lum = 0 -> 255 -> 0
for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) {
setPixelColor(i, color_blend(color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), SEGMENT.colors[1], lum)); setPixelColor(i, color_blend(color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), SEGMENT.colors[1], lum));
} }
@ -809,9 +787,7 @@ uint16_t WS2812FX::scan(bool dual)
SEGMENT_RUNTIME.counter_mode_step = 0; SEGMENT_RUNTIME.counter_mode_step = 0;
} }
for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { fill(SEGMENT.colors[1]);
setPixelColor(i, SEGMENT.colors[1]);
}
int led_offset = SEGMENT_RUNTIME.counter_mode_step - (SEGMENT_LENGTH - 1); int led_offset = SEGMENT_RUNTIME.counter_mode_step - (SEGMENT_LENGTH - 1);
led_offset = abs(led_offset); led_offset = abs(led_offset);
@ -851,9 +827,7 @@ uint16_t WS2812FX::mode_dual_scan(void) {
*/ */
uint16_t WS2812FX::mode_rainbow(void) { uint16_t WS2812FX::mode_rainbow(void) {
uint32_t color = color_wheel(SEGMENT_RUNTIME.counter_mode_step); uint32_t color = color_wheel(SEGMENT_RUNTIME.counter_mode_step);
for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { fill(color);
setPixelColor(i, color);
}
SEGMENT_RUNTIME.counter_mode_step = (SEGMENT_RUNTIME.counter_mode_step + 1) & 0xFF; SEGMENT_RUNTIME.counter_mode_step = (SEGMENT_RUNTIME.counter_mode_step + 1) & 0xFF;
return 1 + (((uint32_t)(255 - SEGMENT.speed)) / 5); return 1 + (((uint32_t)(255 - SEGMENT.speed)) / 5);
@ -934,9 +908,7 @@ uint16_t WS2812FX::mode_running_lights(void) {
*/ */
uint16_t WS2812FX::twinkle(uint32_t color) { uint16_t WS2812FX::twinkle(uint32_t color) {
if(SEGMENT_RUNTIME.counter_mode_step == 0) { if(SEGMENT_RUNTIME.counter_mode_step == 0) {
for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { fill(SEGMENT.colors[1]);
setPixelColor(i, SEGMENT.colors[1]);
}
SEGMENT_RUNTIME.counter_mode_step = map(SEGMENT.intensity, 0, 255, 1, SEGMENT_LENGTH); // make sure, at least one LED is on SEGMENT_RUNTIME.counter_mode_step = map(SEGMENT.intensity, 0, 255, 1, SEGMENT_LENGTH); // make sure, at least one LED is on
} }
@ -986,27 +958,23 @@ void WS2812FX::fade_out(uint8_t rate) {
for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) {
color = getPixelColor(i); color = getPixelColor(i);
if(rate == 0) { // old fade-to-black algorithm int w1 = (color >> 24) & 0xff;
setPixelColor(i, (color >> 1) & 0x7F7F7F7F); int r1 = (color >> 16) & 0xff;
} else { // new fade-to-color algorithm int g1 = (color >> 8) & 0xff;
int w1 = (color >> 24) & 0xff; int b1 = color & 0xff;
int r1 = (color >> 16) & 0xff;
int g1 = (color >> 8) & 0xff;
int b1 = color & 0xff;
int wdelta = (w2 - w1) / mappedRate; int wdelta = (w2 - w1) / mappedRate;
int rdelta = (r2 - r1) / mappedRate; int rdelta = (r2 - r1) / mappedRate;
int gdelta = (g2 - g1) / mappedRate; int gdelta = (g2 - g1) / mappedRate;
int bdelta = (b2 - b1) / mappedRate; int bdelta = (b2 - b1) / mappedRate;
// if fade isn't complete, make sure delta is at least 1 (fixes rounding issues) // if fade isn't complete, make sure delta is at least 1 (fixes rounding issues)
wdelta += (w2 == w1) ? 0 : (w2 > w1) ? 1 : -1; wdelta += (w2 == w1) ? 0 : (w2 > w1) ? 1 : -1;
rdelta += (r2 == r1) ? 0 : (r2 > r1) ? 1 : -1; rdelta += (r2 == r1) ? 0 : (r2 > r1) ? 1 : -1;
gdelta += (g2 == g1) ? 0 : (g2 > g1) ? 1 : -1; gdelta += (g2 == g1) ? 0 : (g2 > g1) ? 1 : -1;
bdelta += (b2 == b1) ? 0 : (b2 > b1) ? 1 : -1; bdelta += (b2 == b1) ? 0 : (b2 > b1) ? 1 : -1;
setPixelColor(i, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); setPixelColor(i, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta);
}
} }
} }
@ -1584,13 +1552,13 @@ uint16_t WS2812FX::mode_fire_flicker(void) {
uint16_t WS2812FX::mode_gradient(void) { uint16_t WS2812FX::mode_gradient(void) {
if (SEGMENT_RUNTIME.counter_mode_call == 0) SEGMENT_RUNTIME.counter_mode_step = 0; if (SEGMENT_RUNTIME.counter_mode_call == 0) SEGMENT_RUNTIME.counter_mode_step = 0;
byte p_w = (SEGMENT.colors[0] & 0xFF000000) >> 24; byte p_w = (SEGMENT.colors[0] & 0xFF000000) >> 24;
byte p_r = (SEGMENT.colors[0] & 0x00FF0000) >> 16; byte p_r = (SEGMENT.colors[0] & 0xFF0000) >> 16;
byte p_g = (SEGMENT.colors[0] & 0x0000FF00) >> 8; byte p_g = (SEGMENT.colors[0] & 0xFF00) >> 8;
byte p_b = (SEGMENT.colors[0] & 0x000000FF) >> 0; byte p_b = SEGMENT.colors[0] & 0xFF;
byte p_w2 = (SEGMENT.colors[1] & 0xFF000000) >> 24; byte p_w2 = (SEGMENT.colors[1] & 0xFF000000) >> 24;
byte p_r2 = (SEGMENT.colors[1] & 0x00FF0000) >> 16; byte p_r2 = (SEGMENT.colors[1] & 0xFF0000) >> 16;
byte p_g2 = (SEGMENT.colors[1] & 0x0000FF00) >> 8; byte p_g2 = (SEGMENT.colors[1] & 0xFF00) >> 8;
byte p_b2 = (SEGMENT.colors[1] & 0x000000FF) >> 0; byte p_b2 = SEGMENT.colors[1] & 0xFF;
byte nw,nr,ng,nb; byte nw,nr,ng,nb;
float per,val; //0.0 = sec 1.0 = pri float per,val; //0.0 = sec 1.0 = pri
float brd = SEGMENT.intensity/2; if (brd <1.0) brd = 1.0; float brd = SEGMENT.intensity/2; if (brd <1.0) brd = 1.0;
@ -1878,10 +1846,7 @@ uint16_t WS2812FX::mode_tricolor_chase(void) {
uint16_t WS2812FX::mode_icu(void) { uint16_t WS2812FX::mode_icu(void) {
uint16_t dest = SEGMENT_RUNTIME.counter_mode_step & 0xFFFF; uint16_t dest = SEGMENT_RUNTIME.counter_mode_step & 0xFFFF;
for (uint16_t i = SEGMENT.start; i <= SEGMENT.stop; i++) fill(SEGMENT.colors[1]);
{
setPixelColor(i, SEGMENT.colors[1]);
}
byte pindex = map(dest, 0, SEGMENT_LENGTH/2, 0, 255); byte pindex = map(dest, 0, SEGMENT_LENGTH/2, 0, 255);
uint32_t col = color_from_palette(pindex, false, false, 0); uint32_t col = color_from_palette(pindex, false, false, 0);
@ -2117,10 +2082,7 @@ uint16_t WS2812FX::mode_lightning(void)
SEGMENT_RUNTIME.aux_param2 = 1; SEGMENT_RUNTIME.aux_param2 = 1;
} }
for (int i = SEGMENT.start; i <= SEGMENT.stop; i++) fill(SEGMENT.colors[1]);
{
setPixelColor(i,SEGMENT.colors[1]);
}
if (SEGMENT_RUNTIME.aux_param2) { if (SEGMENT_RUNTIME.aux_param2) {
for (int i = ledstart; i < ledstart + ledlen; i++) for (int i = ledstart; i < ledstart + ledlen; i++)
@ -2422,7 +2384,7 @@ uint16_t WS2812FX::mode_colorwaves(void)
for ( uint16_t i = SEGMENT.start ; i <= SEGMENT.stop; i++) { for ( uint16_t i = SEGMENT.start ; i <= SEGMENT.stop; i++) {
hue16 += hueinc16; hue16 += hueinc16;
uint8_t hue8 = hue16 / 256; uint8_t hue8 = hue16 >> 8;
uint16_t h16_128 = hue16 >> 7; uint16_t h16_128 = hue16 >> 7;
if ( h16_128 & 0x100) { if ( h16_128 & 0x100) {
hue8 = 255 - (h16_128 >> 1); hue8 = 255 - (h16_128 >> 1);
@ -2650,11 +2612,9 @@ uint16_t WS2812FX::mode_meteor() {
byte meteorSize= 1+ SEGMENT_LENGTH / 10; byte meteorSize= 1+ SEGMENT_LENGTH / 10;
uint16_t in = SEGMENT.start + SEGMENT_RUNTIME.counter_mode_step; uint16_t in = SEGMENT.start + SEGMENT_RUNTIME.counter_mode_step;
byte decayProb = 255 - SEGMENT.intensity;
// fade all leds to colors[1] in LEDs one step // fade all leds to colors[1] in LEDs one step
for (uint16_t i = SEGMENT.start; i <= SEGMENT.stop; i++) { for (uint16_t i = SEGMENT.start; i <= SEGMENT.stop; i++) {
if (random8() <= decayProb) if (random8() <= 255 - SEGMENT.intensity)
{ {
byte meteorTrailDecay = 128 + random8(127); byte meteorTrailDecay = 128 + random8(127);
_locked[i] = scale8(_locked[i], meteorTrailDecay); _locked[i] = scale8(_locked[i], meteorTrailDecay);
@ -2685,11 +2645,9 @@ uint16_t WS2812FX::mode_meteor_smooth() {
byte meteorSize= 1+ SEGMENT_LENGTH / 10; byte meteorSize= 1+ SEGMENT_LENGTH / 10;
uint16_t in = map((SEGMENT_RUNTIME.counter_mode_step >> 6 & 0xFF), 0, 255, SEGMENT.start, SEGMENT.stop); uint16_t in = map((SEGMENT_RUNTIME.counter_mode_step >> 6 & 0xFF), 0, 255, SEGMENT.start, SEGMENT.stop);
byte decayProb = 255 - SEGMENT.intensity;
// fade all leds to colors[1] in LEDs one step // fade all leds to colors[1] in LEDs one step
for (uint16_t i = SEGMENT.start; i <= SEGMENT.stop; i++) { for (uint16_t i = SEGMENT.start; i <= SEGMENT.stop; i++) {
if (_locked[i] != 0 && random8() <= decayProb) if (_locked[i] != 0 && random8() <= 255 - SEGMENT.intensity)
{ {
int change = 3 - random8(12); //change each time between -8 and +3 int change = 3 - random8(12); //change each time between -8 and +3
_locked[i] += change; _locked[i] += change;
@ -2749,50 +2707,56 @@ uint16_t WS2812FX::mode_railway()
//fade duration is random 2-5sec //fade duration is random 2-5sec
//propagation velocity from speed //propagation velocity from speed
//drop rate from intensity //drop rate from intensity
//? smooth sine instead of shifting +2
uint16_t WS2812FX::mode_ripple() uint16_t WS2812FX::mode_ripple()
{ {
uint16_t maxripples = SEGMENT_LENGTH / 5; uint16_t maxripples = SEGMENT_LENGTH / 4;
if (maxripples == 0) return mode_static(); if (maxripples == 0) return mode_static();
//set background fill(SEGMENT.colors[1]);
for (uint16_t i = SEGMENT.start; i <= SEGMENT.stop; i++)
{
setPixelColor(i, SEGMENT.colors[1]);
}
//draw wave //draw wave
for (uint16_t rippleI = 0; rippleI < maxripples; rippleI++) for (uint16_t rippleI = 0; rippleI < maxripples; rippleI++)
{ {
uint16_t storeI = SEGMENT.start + 5*rippleI; uint16_t storeI = SEGMENT.start + 4*rippleI;
uint8_t ripplestate = _locked[storeI]; uint16_t ripplestate = _locked[storeI];
if (ripplestate) if (ripplestate)
{ {
uint16_t rippleorigin = ((_locked[storeI+2] << 8) & 0xFF00) + _locked[storeI+3]; uint8_t rippledecay = (SEGMENT.speed >> 4) +1; //faster decay if faster propagation
uint8_t rippledur = _locked[storeI+1]; uint16_t rippleorigin = (_locked[storeI+1] << 8) + _locked[storeI+2];
uint8_t ripplecolor = _locked[storeI+4]; uint32_t col = color_from_palette(_locked[storeI+3], false, false, 255);
int16_t progress = ripplestate - rippledur -1; uint16_t propagation = ((ripplestate/rippledecay -1) * SEGMENT.speed);
int16_t propagation = (progress * (SEGMENT.speed >> 1)) >> 8; int16_t propI = propagation >> 8;
int16_t left = rippleorigin - propagation; uint8_t propF = propagation & 0xFF;
int16_t right = rippleorigin +1 + propagation; int16_t left = rippleorigin - propI -1;
uint8_t mix = (ripplestate > 127) ? (ripplestate-128)*2 : 0; uint8_t amp = (ripplestate < 17) ? triwave8((ripplestate-1)*8) : map(ripplestate,17,255,255,2);
uint32_t col = color_blend(color_from_palette(ripplecolor, false, false, 255), SEGMENT.colors[1], mix);
if (left >= SEGMENT.start) setPixelColor(left, col); for (int16_t v = left; v < left +4; v++)
if (right <= SEGMENT.stop) setPixelColor(right, col);
_locked[storeI]++;
} else //randomly create new ripple
{
if (random16(4096) <= SEGMENT.intensity)
{ {
_locked[storeI] = random8(120); //vary ripple strenght uint8_t mag = scale8(cubicwave8((propF>>2)+(v-left)*64), amp);
_locked[storeI+1] = _locked[storeI]; if (v >= SEGMENT.start)
{
setPixelColor(v, color_blend(getPixelColor(v), col, mag));
}
int16_t w = left + propI*2 + 3 -(v-left);
if (w <= SEGMENT.stop && w >= SEGMENT.start)
{
setPixelColor(w, color_blend(getPixelColor(w), col, mag));
}
}
ripplestate += rippledecay;
_locked[storeI] = (ripplestate > 254) ? 0 : ripplestate;
} else //randomly create new wave
{
if (random16(IBN + 10000) <= SEGMENT.intensity)
{
_locked[storeI] = 1;
uint16_t origin = SEGMENT.start + random16(SEGMENT_LENGTH); uint16_t origin = SEGMENT.start + random16(SEGMENT_LENGTH);
_locked[storeI+2] = origin >> 8; _locked[storeI+1] = origin >> 8;
_locked[storeI+3] = origin & 0xFF; _locked[storeI+2] = origin & 0xFF;
_locked[storeI+4] = random8(); _locked[storeI+3] = random8();
} }
} }
} }
return 20; return 20;
} }

View File

@ -301,8 +301,6 @@ class WS2812FX {
void void
init(bool supportWhite, uint16_t countPixels, bool skipFirst), init(bool supportWhite, uint16_t countPixels, bool skipFirst),
service(void), service(void),
clear(void),
strip_off(void),
fade_out(uint8_t r), fade_out(uint8_t r),
setMode(uint8_t m), setMode(uint8_t m),
setSpeed(uint8_t s), setSpeed(uint8_t s),
@ -473,6 +471,7 @@ class WS2812FX {
uint8_t _brightness; uint8_t _brightness;
void handle_palette(void); void handle_palette(void);
void fill(uint32_t);
bool modeUsesLock(uint8_t); bool modeUsesLock(uint8_t);
boolean boolean

View File

@ -78,7 +78,7 @@
//version code in format yymmddb (b = daily build) //version code in format yymmddb (b = daily build)
#define VERSION 1901314 #define VERSION 1902021
char versionString[] = "0.8.3-dev"; char versionString[] = "0.8.3-dev";