Index: apps/lang/deutsch.lang =================================================================== --- apps/lang/deutsch.lang (revision 15011) +++ apps/lang/deutsch.lang (working copy) @@ -3258,15 +3258,12 @@ user: *: "List Acceleration Speed" - scrollwheel: none *: "Listenbeschleunigung" - scrollwheel: none *: "Listenbeschleunigung" - scrollwheel: none @@ -11170,3 +11167,20 @@ rtc: "Uhr" + + id: LANG_WHEEL_ACCEL_START + desc: wheel acceleration start + user: + + *: none + scrollwheel: "Wheelspeed for Acceleration" + + + *: none + scrollwheel: "Startwert für Listenbeschleunigung" + + + *: none + scrollwheel: "Startwert für Listenbeschleunigung" + + Index: apps/lang/english.lang =================================================================== --- apps/lang/english.lang (revision 15011) +++ apps/lang/english.lang (working copy) @@ -3333,15 +3333,12 @@ user: *: "List Acceleration Speed" - scrollwheel: none *: "List Acceleration Speed" - scrollwheel: none *: "List Acceleration Speed" - scrollwheel: none @@ -11390,3 +11387,21 @@ lcd_color: "Line Selector Colours" + + id: LANG_WHEEL_ACCEL_START + desc: wheel acceleration start + user: + + *: none + scrollwheel: "Wheelspeed for Acceleration" + + + *: none + scrollwheel: "Wheelspeed for Acceleration" + + + *: none + scrollwheel: "Wheelspeed for Acceleration" + + + Index: apps/gui/list.c =================================================================== --- apps/gui/list.c (revision 15011) +++ apps/gui/list.c (working copy) @@ -922,7 +922,8 @@ #ifdef HAVE_SCROLLWHEEL int next_item_modifier = button_apply_acceleration(get_action_data(), - WHEEL_ACCELERATION_FACTOR); + global_settings.wheel_accel_start, + global_settings.wheelacceleration); #else static int next_item_modifier = 1; static int last_accel_tick = 0; Index: apps/settings.h =================================================================== --- apps/settings.h (revision 15011) +++ apps/settings.h (working copy) @@ -747,6 +747,9 @@ #ifndef HAVE_SCROLLWHEEL int list_accel_start_delay; /* ms before we start increaseing step size */ int list_accel_wait; /* ms between increases */ +#else + unsigned int wheel_accel_start; /* value in clicks/sec from which acceleration starts */ + unsigned int wheelacceleration; /* type of acceleration (^2/^3/^4) */ #endif #ifdef HAVE_USBSTACK int usb_stack_mode; /* device or host */ Index: apps/menus/display_menu.c =================================================================== --- apps/menus/display_menu.c (revision 15011) +++ apps/menus/display_menu.c (working copy) @@ -252,6 +252,9 @@ MENUITEM_SETTING(list_accel_start_delay, &global_settings.list_accel_start_delay, NULL); MENUITEM_SETTING(list_accel_wait, &global_settings.list_accel_wait, NULL); +#else +MENUITEM_SETTING(wheel_accel_start, &global_settings.wheel_accel_start, NULL); +MENUITEM_SETTING(wheelacceleration, &global_settings.wheelacceleration, NULL); #endif /* HAVE_SCROLLWHEEL */ #ifdef HAVE_LCD_BITMAP int screenscroll_callback(int action,const struct menu_item_ex *this_item) @@ -289,6 +292,9 @@ &scroll_paginated, #ifndef HAVE_SCROLLWHEEL &list_accel_start_delay, &list_accel_wait +#else + &wheel_accel_start, + &wheelacceleration #endif ); /* SCROLL MENU */ Index: apps/settings_list.c =================================================================== --- apps/settings_list.c (revision 15011) +++ apps/settings_list.c (working copy) @@ -242,6 +242,18 @@ (void)unit; snprintf(buffer, buffer_size, "%ds", ff_rewind_min_stepvals[val]); } +static long wheel_accel_start_getlang(int value) +{ + if (value == 0) + return LANG_OFF; + return TALK_ID(value, UNIT_INT); +} +static long wheel_accel_getlang(int value) +{ + if (value == 0) + return LANG_OFF; + return TALK_ID(value, UNIT_INT); +} static long scanaccel_getlang(int value) { if (value == 0) @@ -257,6 +269,28 @@ else snprintf(buffer, buffer_size, "2x/%ds", val); } +static void wheel_accel_start_formatter(char *buffer, size_t buffer_size, + int val, const char *unit) +{ + (void)unit; + if (val == 0) + strcpy(buffer, str(LANG_OFF)); + else + snprintf(buffer, buffer_size, "%d °/s", val); +} +static void wheel_accel_formatter(char *buffer, size_t buffer_size, + int val, const char *unit) +{ + (void)unit; + if (val == 0) + strcpy(buffer, str(LANG_OFF)); + else if (val==1) + snprintf(buffer, buffer_size, "(°/s)^2"); + else if (val==2) + snprintf(buffer, buffer_size, "(°/s)^3"); + else + snprintf(buffer, buffer_size, "(°/s)^4"); +} static const unsigned char poweroff_idle_timer_times[] = {0,1,2,3,4,5,6,7,8,9,10,15,30,45,60}; static long poweroff_idle_timer_getlang(int value) @@ -1272,6 +1306,13 @@ INT_SETTING(0, list_accel_wait, LANG_LISTACCEL_ACCEL_SPEED, 3, "list_accel_wait", UNIT_SEC, 1, 10, 1, scanaccel_formatter, scanaccel_getlang, NULL), +#else + INT_SETTING(0, wheel_accel_start, LANG_WHEEL_ACCEL_START, DEFAULT_WHEEL_ACCEL_START, + "wheel_accel_start", UNIT_INT, 180, 810, 90, + wheel_accel_start_formatter, wheel_accel_start_getlang, NULL), + INT_SETTING(0, wheelacceleration, LANG_LISTACCEL_ACCEL_SPEED, DEFAULT_WHEEL_ACCELERATION, + "wheel_acceleration", UNIT_INT, 1, 3, 1, + wheel_accel_formatter, wheel_accel_getlang, NULL), #endif /* HAVE_SCROLLWHEEL */ #ifdef HAVE_USBSTACK CHOICE_SETTING(0, usb_stack_mode, LANG_USBSTACK_MODE, 0, "usb mode", Index: firmware/export/button.h =================================================================== --- firmware/export/button.h (revision 15011) +++ firmware/export/button.h (working copy) @@ -51,7 +51,9 @@ #endif #ifdef HAVE_SCROLLWHEEL -int button_apply_acceleration(unsigned int data, unsigned int factor); +int button_apply_acceleration(const unsigned int data, + const unsigned int accel_start, + const unsigned int accel_type); #endif #define BUTTON_NONE 0x00000000 Index: firmware/export/config-e200.h =================================================================== --- firmware/export/config-e200.h (revision 15011) +++ firmware/export/config-e200.h (working copy) @@ -97,9 +97,10 @@ /* define this if the unit uses a scrollwheel for navigation */ #define HAVE_SCROLLWHEEL -/* define wheel acceleration scaling factor */ -/* Range for this target: 0xffffff*(0.0-16.000000894069724921567733381255) */ -#define WHEEL_ACCELERATION_FACTOR (0xffffff*7) +/* define from which rotation speed [degree/sec] on the acceleration starts */ +#define DEFAULT_WHEEL_ACCEL_START 540 +/* define type of acceleration (1 = ^2, 2 = ^3, 3 = ^4) */ +#define DEFAULT_WHEEL_ACCELERATION 1 /* define this if you have a flash memory storage */ #define HAVE_FLASH_STORAGE Index: firmware/export/config-ipodvideo.h =================================================================== --- firmware/export/config-ipodvideo.h (revision 15011) +++ firmware/export/config-ipodvideo.h (working copy) @@ -73,6 +73,13 @@ /* We can fade the backlight by using PWM */ #define HAVE_BACKLIGHT_PWM_FADING +/* define this if the unit uses a scrollwheel for navigation */ +#define HAVE_SCROLLWHEEL +/* define from which rotation speed [degree/sec] on the acceleration starts */ +#define DEFAULT_WHEEL_ACCEL_START 270 +/* define type of acceleration (1 = ^2, 2 = ^3, 3 = ^4) */ +#define DEFAULT_WHEEL_ACCELERATION 3 + /* Define this if you can detect headphones */ #define HAVE_HEADPHONE_DETECTION Index: firmware/export/config-ipodcolor.h =================================================================== --- firmware/export/config-ipodcolor.h (revision 15011) +++ firmware/export/config-ipodcolor.h (working copy) @@ -70,6 +70,13 @@ /* Define this for LCD backlight available */ #define HAVE_BACKLIGHT +/* define this if the unit uses a scrollwheel for navigation */ +#define HAVE_SCROLLWHEEL +/* define from which rotation speed [degree/sec] on the acceleration starts */ +#define DEFAULT_WHEEL_ACCEL_START 270 +/* define type of acceleration (1 = ^2, 2 = ^3, 3 = ^4) */ +#define DEFAULT_WHEEL_ACCELERATION 3 + /* Define this if you can detect headphones */ #define HAVE_HEADPHONE_DETECTION Index: firmware/export/config-ipodmini2g.h =================================================================== --- firmware/export/config-ipodmini2g.h (revision 15011) +++ firmware/export/config-ipodmini2g.h (working copy) @@ -78,6 +78,13 @@ /* We can fade the backlight by using PWM */ #define HAVE_BACKLIGHT_PWM_FADING +/* define this if the unit uses a scrollwheel for navigation */ +#define HAVE_SCROLLWHEEL +/* define from which rotation speed [degree/sec] on the acceleration starts */ +#define DEFAULT_WHEEL_ACCEL_START 270 +/* define type of acceleration (1 = ^2, 2 = ^3, 3 = ^4) */ +#define DEFAULT_WHEEL_ACCELERATION 3 + /* Define this if you can detect headphones */ #define HAVE_HEADPHONE_DETECTION Index: firmware/export/config-ipodnano.h =================================================================== --- firmware/export/config-ipodnano.h (revision 15011) +++ firmware/export/config-ipodnano.h (working copy) @@ -73,6 +73,13 @@ /* We can fade the backlight by using PWM */ #define HAVE_BACKLIGHT_PWM_FADING +/* define this if the unit uses a scrollwheel for navigation */ +#define HAVE_SCROLLWHEEL +/* define from which rotation speed [degree/sec] on the acceleration starts */ +#define DEFAULT_WHEEL_ACCEL_START 270 +/* define type of acceleration (1 = ^2, 2 = ^3, 3 = ^4) */ +#define DEFAULT_WHEEL_ACCELERATION 3 + /* Define this if you can detect headphones */ #define HAVE_HEADPHONE_DETECTION Index: firmware/export/config-ipod4g.h =================================================================== --- firmware/export/config-ipod4g.h (revision 15011) +++ firmware/export/config-ipod4g.h (working copy) @@ -81,6 +81,13 @@ /* Define this for LCD backlight available */ #define HAVE_BACKLIGHT +/* define this if the unit uses a scrollwheel for navigation */ +#define HAVE_SCROLLWHEEL +/* define from which rotation speed [degree/sec] on the acceleration starts */ +#define DEFAULT_WHEEL_ACCEL_START 270 +/* define type of acceleration (1 = ^2, 2 = ^3, 3 = ^4) */ +#define DEFAULT_WHEEL_ACCELERATION 3 + /* Define this if you can detect headphones */ #define HAVE_HEADPHONE_DETECTION Index: firmware/target/arm/ipod/button-clickwheel.c =================================================================== --- firmware/target/arm/ipod/button-clickwheel.c (revision 15011) +++ firmware/target/arm/ipod/button-clickwheel.c (working copy) @@ -40,6 +40,30 @@ #include "system.h" #include "powermgmt.h" +#define WHEEL_FAST_OFF_TIMEOUT 250000 /* timeout for acceleration = 250ms */ +#define WHEEL_REPEAT_TIMEOUT 250000 /* timeout for button repeat = 250ms */ +#define WHEEL_UNTOUCH_TIMEOUT 150000 /* timeout for untouching wheel = 100ms */ +#define WHEELCLICKS_PER_ROTATION 96 /* wheelclicks per full rotation */ + +/* This amount of clicks is needed for at least scrolling 1 item. Choose small values + * to have high sensitivity but few precision, choose large values to have less + * sensitivity and good precision. */ +#if defined(IPOD_NANO) +#define WHEEL_SENSITIVITY 6 /* iPod nano has smaller wheel, lower sensitivity needed */ +#else +#define WHEEL_SENSITIVITY 4 /* default sensitivity */ +#endif + +int old_wheel_value = -1; +int new_wheel_value = 0; +int repeat = 0; +int wheel_delta = 0; +bool wheel_is_touched = false; +unsigned int accumulated_wheel_delta = 0; +unsigned int wheel_repeat = 0; +unsigned int wheel_velocity = 0; +unsigned long last_wheel_usec = 0; + /* Variable to use for setting button status in interrupt handler */ int int_btn = BUTTON_NONE; #ifdef HAVE_WHEEL_POSITION @@ -89,16 +113,15 @@ int btn = BUTTON_NONE; unsigned reg = 0x7000c104; - if ((inl(0x7000c104) & 0x4000000) != 0) { + if ((inl(0x7000c104) & 0x4000000) != 0) + { unsigned status = inl(0x7000c140); reg = reg + 0x3C; /* 0x7000c140 */ outl(0x0, 0x7000c140); /* clear interrupt status? */ - if ((status & 0x800000ff) == 0x8000001a) { - static int old_wheel_value IDATA_ATTR = -1; - static int wheel_repeat = 0; - + if ((status & 0x800000ff) == 0x8000001a) + { if (status & 0x100) btn |= BUTTON_SELECT; if (status & 0x200) @@ -109,63 +132,152 @@ btn |= BUTTON_PLAY; if (status & 0x1000) btn |= BUTTON_MENU; - if (status & 0x40000000) { - /* NB: highest wheel = 0x5F, clockwise increases */ - int new_wheel_value = (status << 9) >> 25; + if (status & 0x40000000) + { + unsigned long usec = USEC_TIMER; + + /* Highest wheel = 0x5F, clockwise increases */ + new_wheel_value = (status >> 16) & 0x7f; whl = new_wheel_value; + + /* switch on backlight (again), reset power-off timer */ backlight_on(); reset_poweroff_timer(); - /* The queue should have no other events when scrolling */ - if (queue_empty(&button_queue) && old_wheel_value >= 0) { + + /* Check whether the scrollwheel was untouched by accident or by will. */ + /* This is needed because wheel may be untoched very shortly during rotation */ + if ( (!wheel_is_touched) && TIME_AFTER(usec, last_wheel_usec + WHEEL_UNTOUCH_TIMEOUT) ) + { + /* wheel has been really untouched -> reset internal variables */ + old_wheel_value = -1; + wheel_velocity = 0; + accumulated_wheel_delta = 0; + wheel_repeat = BUTTON_NONE; + } + else + { + /* wheel was shortly untouched by accident -> leave internal variables */ + wheel_is_touched = true; + } + if (old_wheel_value >= 0) + { /* This is for later = BUTTON_SCROLL_TOUCH;*/ - int wheel_delta = new_wheel_value - old_wheel_value; - unsigned long data; - int wheel_keycode; + wheel_delta = new_wheel_value - old_wheel_value; + unsigned int wheel_keycode = BUTTON_NONE; - if (wheel_delta < -48) - wheel_delta += 96; /* Forward wrapping case */ - else if (wheel_delta > 48) - wheel_delta -= 96; /* Backward wrapping case */ - - if (wheel_delta > 4) { + /* Taking into account wrapping during transition from highest + * to lowest wheel position and back */ + if (wheel_delta < -WHEELCLICKS_PER_ROTATION/2) + wheel_delta += WHEELCLICKS_PER_ROTATION; /* Forward wrapping case */ + else if (wheel_delta > WHEELCLICKS_PER_ROTATION/2) + wheel_delta -= WHEELCLICKS_PER_ROTATION; /* Backward wrapping case */ + + /* Getting direction and wheel_keycode from wheel_delta. + * Need at least some clicks to be sure to avoid haptic fuzziness */ + if (wheel_delta >= WHEEL_SENSITIVITY) wheel_keycode = BUTTON_SCROLL_FWD; - } else if (wheel_delta < -4) { + else if (wheel_delta <= -WHEEL_SENSITIVITY) wheel_keycode = BUTTON_SCROLL_BACK; - } else goto wheel_end; + else + wheel_keycode = BUTTON_NONE; + if (wheel_keycode != BUTTON_NONE) + { + long v = (usec - last_wheel_usec) & 0x7fffffff; + + /* undo signedness */ + wheel_delta = (wheel_delta>0) ? wheel_delta : -wheel_delta; + + /* add the current wheel_delta */ + accumulated_wheel_delta += wheel_delta; + + v = v ? (1000000 * wheel_delta) / v : 0; /* clicks/sec = 1000000 * clicks/usec */ + v = (v * 360) / WHEELCLICKS_PER_ROTATION; /* conversion to degree/sec */ + v = (v<0) ? -v : v; /* undo signedness */ + + /* some velocity filtering to smooth things out */ + wheel_velocity = (31 * wheel_velocity + v) / 32; + /* limit to 24 bit */ + wheel_velocity = (wheel_velocity>0xffffff) ? 0xffffff : wheel_velocity; + + /* assume REPEAT = off */ + repeat = 0; + + /* direction reversals must nullify acceleration and accumulator */ + if (wheel_keycode != wheel_repeat) + { + wheel_repeat = wheel_keycode; + wheel_velocity = 0; + accumulated_wheel_delta = 0; + } + /* on same direction REPEAT is assumed when new click is within timeout */ + else if (TIME_BEFORE(usec, last_wheel_usec + WHEEL_REPEAT_TIMEOUT)) + { + repeat = BUTTON_REPEAT; + } + /* timeout nullifies acceleration and accumulator */ + if (TIME_AFTER(usec, last_wheel_usec + WHEEL_FAST_OFF_TIMEOUT)) + { + wheel_velocity = 0; + accumulated_wheel_delta = 0; + } + #ifdef HAVE_WHEEL_POSITION - if (send_events) + if (send_events) #endif - { - data = (wheel_delta << 16) | new_wheel_value; - queue_post(&button_queue, wheel_keycode | wheel_repeat, - data); + /* The queue should have no other events when scrolling */ + if (queue_empty(&button_queue)) + { + /* each WHEEL_SENSITIVITY clicks = scrolling 1 item */ + accumulated_wheel_delta /= WHEEL_SENSITIVITY; +#ifdef HAVE_SCROLLWHEEL + /* use data-format for HAVE_SCROLLWHEEL */ + /* always use acceleration mode (1<<31) */ + /* always set message post count to (1<<24) for iPod */ + /* this way the scrolling is always calculated from wheel_velocity */ + queue_post(&button_queue, wheel_keycode | repeat, + (1<<31) | (1 << 24) | wheel_velocity); + +#else + queue_post(&button_queue, wheel_keycode | repeat, + (accumulated_wheel_delta << 16) | new_wheel_value); +#endif + accumulated_wheel_delta = 0; + } + last_wheel_usec = usec; + old_wheel_value = new_wheel_value; } - - if (!wheel_repeat) wheel_repeat = BUTTON_REPEAT; } - - old_wheel_value = new_wheel_value; - } else if (old_wheel_value >= 0) { - /* scroll wheel up */ - old_wheel_value = -1; - wheel_repeat = 0; + else + { + /* scrollwheel was touched for the first time after finger lifting */ + old_wheel_value = new_wheel_value; + wheel_is_touched = true; + } } + else + { + /* In this case the finger was lifted from the scrollwheel. */ + wheel_is_touched = false; + } - } else if (status == 0xffffffff) { + } + else if (status == 0xffffffff) + { opto_i2c_init(); } } -wheel_end: - - if ((inl(reg) & 0x8000000) != 0) { + if ((inl(reg) & 0x8000000) != 0) + { outl(0xffffffff, 0x7000c120); outl(0xffffffff, 0x7000c124); } /* Save the new absolute wheel position */ +#ifdef HAVE_WHEEL_POSITION wheel_position = whl; +#endif return btn; } @@ -200,14 +312,18 @@ void button_init_device(void) { opto_i2c_init(); + /* hold button - enable as input */ GPIOA_ENABLE |= 0x20; GPIOA_OUTPUT_EN &= ~0x20; + /* hold button - set interrupt levels */ GPIOA_INT_LEV = ~(GPIOA_INPUT_VAL & 0x20); GPIOA_INT_CLR = GPIOA_INT_STAT & 0x20; + /* enable interrupts */ GPIOA_INT_EN = 0x20; + /* unmask interrupt */ CPU_INT_EN = 0x40000000; CPU_HI_INT_EN = I2C_MASK; Index: firmware/target/arm/sandisk/sansa-e200/button-e200.c =================================================================== --- firmware/target/arm/sandisk/sansa-e200/button-e200.c (revision 15011) +++ firmware/target/arm/sandisk/sansa-e200/button-e200.c (working copy) @@ -27,6 +27,7 @@ #define WHEEL_REPEAT_INTERVAL 300000 #define WHEEL_FAST_ON_INTERVAL 20000 #define WHEEL_FAST_OFF_INTERVAL 60000 +#define WHEELCLICKS_PER_ROTATION 48 /* wheelclicks per full rotation */ /* Clickwheel */ #ifndef BOOTLOADER @@ -137,11 +138,9 @@ unsigned long usec = USEC_TIMER; unsigned v = (usec - last_wheel_usec) & 0x7fffffff; - /* wheel velocity in 0.24 fixed point - clicks/uS */ + v = (v>0) ? 1000000 / v : 0; /* clicks/sec = 1000000 * clicks/usec */ + v = (v>0xffffff) ? 0xffffff : v; /* limit to 24 bit */ - /* velocity cap to 18 bits to allow up to x16 scaling */ - v = (v < 0x40) ? 0xffffff / 0x40 : 0xffffff / v; - /* some velocity filtering to smooth things out */ wheel_velocity = (7*wheel_velocity + v) / 8; @@ -173,12 +172,12 @@ else { /* fast ON gets filtered to avoid inadvertent jumps to fast mode */ - if (repeat && wheel_velocity > 0xffffff/WHEEL_FAST_ON_INTERVAL) + if (repeat && wheel_velocity > 1000000/WHEEL_FAST_ON_INTERVAL) { /* moving into fast mode */ wheel_fast_mode = 1 << 31; wheel_click_count = 0; - wheel_velocity = 0xffffff/WHEEL_FAST_OFF_INTERVAL; + wheel_velocity = 1000000/WHEEL_FAST_OFF_INTERVAL; } else if (++wheel_click_count < 2) { @@ -190,7 +189,7 @@ } if (TIME_AFTER(current_tick, next_backlight_on) || - v <= 0xffffff/(1000000/4)) + v <= 4) { /* poke backlight to turn it on or maintain it no more often than every 1/4 second*/ @@ -214,7 +213,7 @@ if (queue_empty(&button_queue)) { queue_post(&button_queue, btn, wheel_fast_mode | - (wheel_delta << 24) | wheel_velocity); + (wheel_delta << 24) | wheel_velocity*360/WHEELCLICKS_PER_ROTATION); /* message posted - reset delta */ wheel_delta = 1; } Index: firmware/drivers/button.c =================================================================== --- firmware/drivers/button.c (revision 15011) +++ firmware/drivers/button.c (working copy) @@ -491,22 +491,44 @@ * data: * [31] Use acceleration * [30:24] Message post count (skipped + 1) (1-127) - * [23:0] Velocity - clicks/uS - 0.24 fixed point + * [23:0] Velocity - degree/sec * * factor: - * Wheel acceleration scaling factor - x.24 fixed point - - * no greater than what will not overflow 64 bits when multiplied - * by the driver's maximum velocity in (clicks/usec)^2 in 0.24 + * Value in degree/sec -- configurable via settings -- above which + * the accelerated scrolling starts. Factor is internally scaled by + * 1<<16 in respect to the following 32bit integer operations. */ -int button_apply_acceleration(unsigned int data, unsigned int factor) +int button_apply_acceleration(const unsigned int data, + const unsigned int accel_start, + const unsigned int accel_type) { int delta = (data >> 24) & 0x7f; if ((data & (1 << 31)) != 0) { + /* factor = 2^16 / global_settings.wheel_accel_start */ + unsigned int factor = (1<<16)/accel_start; + + /* read driver's velocity from data */ unsigned int v = data & 0xffffff; - v = factor * (unsigned long long)v*v / 0xffffffffffffull; + /* v = 28.4 fixed point */ + v = (factor * v)>>(16-4); + + /* Calculate real numbers item to scroll based upon acceleration + * setting, use correct roundoff */ + switch (accel_type) + { + default: + v = (v*v + (1<< 7))>> 8; + break; + case 2: + v = (v*v*v + (1<<11))>>12; + break; + case 3: + v = (v*v*v*v + (1<<15))>>16; + break; + } if (v > 1) delta *= v;