cc7c665d9b
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@21781 a1c6a512-1295-4272-9138-f99709370657
908 lines
29 KiB
C
908 lines
29 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2002 Björn Stenberg
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
#include "config.h"
|
|
#include "sprintf.h"
|
|
#include "action.h"
|
|
#include "dsp.h"
|
|
#include "sound.h"
|
|
#include "pcmbuf.h"
|
|
#include "lang.h"
|
|
#include "icons.h"
|
|
#include "screens.h"
|
|
#include "viewport.h"
|
|
#include "font.h"
|
|
#include "system.h"
|
|
#include "misc.h"
|
|
#include "pitchscreen.h"
|
|
#include "settings.h"
|
|
#if CONFIG_CODEC == SWCODEC
|
|
#include "tdspeed.h"
|
|
#endif
|
|
|
|
#define ABS(x) ((x) > 0 ? (x) : -(x))
|
|
|
|
#define ICON_BORDER 12 /* icons are currently 7x8, so add ~2 pixels */
|
|
/* on both sides when drawing */
|
|
|
|
#define PITCH_MAX (200 * PITCH_SPEED_PRECISION)
|
|
#define PITCH_MIN (50 * PITCH_SPEED_PRECISION)
|
|
#define PITCH_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* .1% */
|
|
#define PITCH_BIG_DELTA (PITCH_SPEED_PRECISION) /* 1% */
|
|
#define PITCH_NUDGE_DELTA (2 * PITCH_SPEED_PRECISION) /* 2% */
|
|
|
|
#define SPEED_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* .1% */
|
|
#define SPEED_BIG_DELTA (PITCH_SPEED_PRECISION) /* 1% */
|
|
|
|
#define SEMITONE_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* 10 cents */
|
|
#define SEMITONE_BIG_DELTA PITCH_SPEED_PRECISION /* 1 semitone */
|
|
|
|
enum
|
|
{
|
|
PITCH_TOP = 0,
|
|
PITCH_MID,
|
|
PITCH_BOTTOM,
|
|
PITCH_ITEM_COUNT,
|
|
};
|
|
|
|
|
|
/* This is a table of semitone percentage values of the appropriate
|
|
precision (based on PITCH_SPEED_PRECISION). Note that these are
|
|
all constant expressions, which will be evaluated at compile time,
|
|
so no need to worry about how complex the expressions look.
|
|
That's just to get the precision right.
|
|
|
|
I calculated these values, starting from 50, as
|
|
|
|
x(n) = 50 * 2^(n/12)
|
|
|
|
All that math in each entry simply converts the float constant
|
|
to an integer equal to PITCH_SPEED_PRECISION times the float value,
|
|
with as little precision loss as possible.
|
|
*/
|
|
#define SEMITONE_VALUE(x) \
|
|
( (int)(((x) + 0.5 / PITCH_SPEED_PRECISION) * PITCH_SPEED_PRECISION) )
|
|
|
|
static const int semitone_table[] =
|
|
{
|
|
SEMITONE_VALUE(50),
|
|
SEMITONE_VALUE(52.97315472),
|
|
SEMITONE_VALUE(56.12310242),
|
|
SEMITONE_VALUE(59.46035575),
|
|
SEMITONE_VALUE(62.99605249),
|
|
SEMITONE_VALUE(66.74199271),
|
|
SEMITONE_VALUE(70.71067812),
|
|
SEMITONE_VALUE(74.91535384),
|
|
SEMITONE_VALUE(79.3700526 ),
|
|
SEMITONE_VALUE(84.08964153),
|
|
SEMITONE_VALUE(89.08987181),
|
|
SEMITONE_VALUE(94.38743127),
|
|
SEMITONE_VALUE(100 ),
|
|
SEMITONE_VALUE(105.9463094),
|
|
SEMITONE_VALUE(112.2462048),
|
|
SEMITONE_VALUE(118.9207115),
|
|
SEMITONE_VALUE(125.992105 ),
|
|
SEMITONE_VALUE(133.4839854),
|
|
SEMITONE_VALUE(141.4213562),
|
|
SEMITONE_VALUE(149.8307077),
|
|
SEMITONE_VALUE(158.7401052),
|
|
SEMITONE_VALUE(168.1792831),
|
|
SEMITONE_VALUE(178.1797436),
|
|
SEMITONE_VALUE(188.7748625),
|
|
SEMITONE_VALUE(200 )
|
|
};
|
|
|
|
#define NUM_SEMITONES ((int)(sizeof(semitone_table) / sizeof(int)))
|
|
#define SEMITONE_START -12
|
|
#define SEMITONE_END 12
|
|
|
|
/* A table of values for approximating the cent curve with
|
|
linear interpolation. Multipy the next lowest semitone
|
|
by this much to find the corresponding cent percentage.
|
|
|
|
These values were calculated as
|
|
x(n) = 100 * 2^(n * 20/1200)
|
|
*/
|
|
|
|
#define CENT_INTERP(x) \
|
|
( (int)(((x) + 0.5 / PITCH_SPEED_PRECISION) * PITCH_SPEED_PRECISION) )
|
|
|
|
|
|
static const int cent_interp[] =
|
|
{
|
|
PITCH_SPEED_100,
|
|
CENT_INTERP(101.1619440),
|
|
CENT_INTERP(102.3373892),
|
|
CENT_INTERP(103.5264924),
|
|
CENT_INTERP(104.7294123),
|
|
/* this one's the next semitone but we have it here for convenience */
|
|
CENT_INTERP(105.9463094),
|
|
};
|
|
|
|
/* Number of cents between entries in the cent_interp table */
|
|
#define CENT_INTERP_INTERVAL 20
|
|
#define CENT_INTERP_NUM ((int)(sizeof(cent_interp)/sizeof(int)))
|
|
|
|
/* This stores whether the pitch and speed are at their own limits */
|
|
/* or that of the timestretching algorithm */
|
|
static bool at_limit = false;
|
|
|
|
static void pitchscreen_fix_viewports(struct viewport *parent,
|
|
struct viewport pitch_viewports[PITCH_ITEM_COUNT])
|
|
{
|
|
int i, font_height;
|
|
font_height = font_get(parent->font)->height;
|
|
for (i = 0; i < PITCH_ITEM_COUNT; i++)
|
|
{
|
|
pitch_viewports[i] = *parent;
|
|
pitch_viewports[i].height = font_height;
|
|
}
|
|
pitch_viewports[PITCH_TOP].y += ICON_BORDER;
|
|
|
|
pitch_viewports[PITCH_MID].x += ICON_BORDER;
|
|
pitch_viewports[PITCH_MID].width = parent->width - ICON_BORDER*2;
|
|
pitch_viewports[PITCH_MID].height = parent->height - ICON_BORDER*2
|
|
- font_height * 2;
|
|
if(pitch_viewports[PITCH_MID].height < font_height * 2)
|
|
pitch_viewports[PITCH_MID].height = font_height * 2;
|
|
pitch_viewports[PITCH_MID].y += parent->height / 2 -
|
|
pitch_viewports[PITCH_MID].height / 2;
|
|
|
|
pitch_viewports[PITCH_BOTTOM].y += parent->height - font_height
|
|
- ICON_BORDER;
|
|
}
|
|
|
|
/* must be called before pitchscreen_draw, or within
|
|
* since it neither clears nor updates the display */
|
|
static void pitchscreen_draw_icons(struct screen *display,
|
|
struct viewport *parent)
|
|
{
|
|
display->set_viewport(parent);
|
|
display->mono_bitmap(bitmap_icons_7x8[Icon_UpArrow],
|
|
parent->width/2 - 3,
|
|
2, 7, 8);
|
|
display->mono_bitmap(bitmap_icons_7x8[Icon_DownArrow],
|
|
parent->width /2 - 3,
|
|
parent->height - 10, 7, 8);
|
|
display->mono_bitmap(bitmap_icons_7x8[Icon_FastForward],
|
|
parent->width - 10,
|
|
parent->height /2 - 4, 7, 8);
|
|
display->mono_bitmap(bitmap_icons_7x8[Icon_FastBackward],
|
|
2,
|
|
parent->height /2 - 4, 7, 8);
|
|
display->update_viewport();
|
|
}
|
|
|
|
static void pitchscreen_draw(struct screen *display, int max_lines,
|
|
struct viewport pitch_viewports[PITCH_ITEM_COUNT],
|
|
int32_t pitch, int32_t semitone
|
|
#if CONFIG_CODEC == SWCODEC
|
|
,int32_t speed
|
|
#endif
|
|
)
|
|
{
|
|
unsigned char* ptr;
|
|
char buf[32];
|
|
int w, h;
|
|
bool show_lang_pitch;
|
|
|
|
/* "Pitch up/Pitch down" - hide for a small screen */
|
|
if (max_lines >= 5)
|
|
{
|
|
/* UP: Pitch Up */
|
|
display->set_viewport(&pitch_viewports[PITCH_TOP]);
|
|
if (global_settings.pitch_mode_semitone)
|
|
ptr = str(LANG_PITCH_UP_SEMITONE);
|
|
else
|
|
ptr = str(LANG_PITCH_UP);
|
|
display->getstringsize(ptr, &w, &h);
|
|
display->clear_viewport();
|
|
/* draw text */
|
|
display->putsxy((pitch_viewports[PITCH_TOP].width / 2) -
|
|
(w / 2), 0, ptr);
|
|
display->update_viewport();
|
|
|
|
/* DOWN: Pitch Down */
|
|
display->set_viewport(&pitch_viewports[PITCH_BOTTOM]);
|
|
if (global_settings.pitch_mode_semitone)
|
|
ptr = str(LANG_PITCH_DOWN_SEMITONE);
|
|
else
|
|
ptr = str(LANG_PITCH_DOWN);
|
|
display->getstringsize(ptr, &w, &h);
|
|
display->clear_viewport();
|
|
/* draw text */
|
|
display->putsxy((pitch_viewports[PITCH_BOTTOM].width / 2) -
|
|
(w / 2), 0, ptr);
|
|
display->update_viewport();
|
|
}
|
|
|
|
/* Middle section */
|
|
display->set_viewport(&pitch_viewports[PITCH_MID]);
|
|
display->clear_viewport();
|
|
int width_used = 0;
|
|
|
|
/* Middle section upper line - hide for a small screen */
|
|
if ((show_lang_pitch = (max_lines >= 3)))
|
|
{
|
|
#if CONFIG_CODEC == SWCODEC
|
|
if(global_settings.pitch_mode_timestretch)
|
|
{
|
|
/* Pitch:XXX.X% */
|
|
if(global_settings.pitch_mode_semitone)
|
|
{
|
|
snprintf(buf, sizeof(buf), "%s: %s%ld.%02ld", str(LANG_PITCH),
|
|
semitone >= 0 ? "+" : "-",
|
|
ABS(semitone / PITCH_SPEED_PRECISION),
|
|
ABS((semitone % PITCH_SPEED_PRECISION) /
|
|
(PITCH_SPEED_PRECISION / 100))
|
|
);
|
|
}
|
|
else
|
|
{
|
|
snprintf(buf, sizeof(buf), "%s: %ld.%ld%%", str(LANG_PITCH),
|
|
pitch / PITCH_SPEED_PRECISION,
|
|
(pitch % PITCH_SPEED_PRECISION) /
|
|
(PITCH_SPEED_PRECISION / 10));
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
/* Rate */
|
|
snprintf(buf, sizeof(buf), "%s:", str(LANG_PLAYBACK_RATE));
|
|
}
|
|
display->getstringsize(buf, &w, &h);
|
|
display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
|
|
(pitch_viewports[PITCH_MID].height / 2) - h, buf);
|
|
if (w > width_used)
|
|
width_used = w;
|
|
}
|
|
|
|
/* Middle section lower line */
|
|
/* "Speed:XXX%" */
|
|
#if CONFIG_CODEC == SWCODEC
|
|
if(global_settings.pitch_mode_timestretch)
|
|
{
|
|
snprintf(buf, sizeof(buf), "%s: %ld.%ld%%", str(LANG_SPEED),
|
|
speed / PITCH_SPEED_PRECISION,
|
|
(speed % PITCH_SPEED_PRECISION) / (PITCH_SPEED_PRECISION / 10));
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
if(global_settings.pitch_mode_semitone)
|
|
{
|
|
snprintf(buf, sizeof(buf), "%s%ld.%02ld",
|
|
semitone >= 0 ? "+" : "-",
|
|
ABS(semitone / PITCH_SPEED_PRECISION),
|
|
ABS((semitone % PITCH_SPEED_PRECISION) /
|
|
(PITCH_SPEED_PRECISION / 100))
|
|
);
|
|
}
|
|
else
|
|
{
|
|
snprintf(buf, sizeof(buf), "%ld.%ld%%",
|
|
pitch / PITCH_SPEED_PRECISION,
|
|
(pitch % PITCH_SPEED_PRECISION) / (PITCH_SPEED_PRECISION / 10));
|
|
}
|
|
}
|
|
|
|
display->getstringsize(buf, &w, &h);
|
|
display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
|
|
show_lang_pitch ? (pitch_viewports[PITCH_MID].height / 2) :
|
|
(pitch_viewports[PITCH_MID].height / 2) - (h / 2),
|
|
buf);
|
|
if (w > width_used)
|
|
width_used = w;
|
|
|
|
/* "limit" and "timestretch" labels */
|
|
if (max_lines >= 7)
|
|
{
|
|
if(at_limit)
|
|
{
|
|
snprintf(buf, sizeof(buf), "%s", str(LANG_STRETCH_LIMIT));
|
|
display->getstringsize(buf, &w, &h);
|
|
display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
|
|
(pitch_viewports[PITCH_MID].height / 2) + h, buf);
|
|
if (w > width_used)
|
|
width_used = w;
|
|
}
|
|
}
|
|
|
|
/* Middle section left/right labels */
|
|
const char *leftlabel = "-2%";
|
|
const char *rightlabel = "+2%";
|
|
#if CONFIG_CODEC == SWCODEC
|
|
if (global_settings.pitch_mode_timestretch)
|
|
{
|
|
leftlabel = "<<";
|
|
rightlabel = ">>";
|
|
}
|
|
#endif
|
|
|
|
/* Only display if they fit */
|
|
display->getstringsize(leftlabel, &w, &h);
|
|
width_used += w;
|
|
display->getstringsize(rightlabel, &w, &h);
|
|
width_used += w;
|
|
|
|
if (width_used <= pitch_viewports[PITCH_MID].width)
|
|
{
|
|
display->putsxy(0, (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
|
|
leftlabel);
|
|
display->putsxy((pitch_viewports[PITCH_MID].width - w),
|
|
(pitch_viewports[PITCH_MID].height / 2) - (h / 2),
|
|
rightlabel);
|
|
}
|
|
display->update_viewport();
|
|
display->set_viewport(NULL);
|
|
}
|
|
|
|
static int32_t pitch_increase(int32_t pitch, int32_t pitch_delta, bool allow_cutoff
|
|
#if CONFIG_CODEC == SWCODEC
|
|
/* need this to maintain correct pitch/speed caps */
|
|
, int32_t speed
|
|
#endif
|
|
)
|
|
{
|
|
int32_t new_pitch;
|
|
#if CONFIG_CODEC == SWCODEC
|
|
int32_t new_stretch;
|
|
#endif
|
|
at_limit = false;
|
|
|
|
if (pitch_delta < 0)
|
|
{
|
|
/* for large jumps, snap up to whole numbers */
|
|
if(allow_cutoff && pitch_delta <= -PITCH_SPEED_PRECISION &&
|
|
(pitch + pitch_delta) % PITCH_SPEED_PRECISION != 0)
|
|
{
|
|
pitch_delta += PITCH_SPEED_PRECISION - ((pitch + pitch_delta) % PITCH_SPEED_PRECISION);
|
|
}
|
|
|
|
new_pitch = pitch + pitch_delta;
|
|
|
|
if (new_pitch < PITCH_MIN)
|
|
{
|
|
if (!allow_cutoff)
|
|
{
|
|
return pitch;
|
|
}
|
|
new_pitch = PITCH_MIN;
|
|
at_limit = true;
|
|
}
|
|
}
|
|
else if (pitch_delta > 0)
|
|
{
|
|
/* for large jumps, snap down to whole numbers */
|
|
if(allow_cutoff && pitch_delta >= PITCH_SPEED_PRECISION &&
|
|
(pitch + pitch_delta) % PITCH_SPEED_PRECISION != 0)
|
|
{
|
|
pitch_delta -= (pitch + pitch_delta) % PITCH_SPEED_PRECISION;
|
|
}
|
|
|
|
new_pitch = pitch + pitch_delta;
|
|
|
|
if (new_pitch > PITCH_MAX)
|
|
{
|
|
if (!allow_cutoff)
|
|
return pitch;
|
|
new_pitch = PITCH_MAX;
|
|
at_limit = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* pitch_delta == 0 -> no real change */
|
|
return pitch;
|
|
}
|
|
#if CONFIG_CODEC == SWCODEC
|
|
if (dsp_timestretch_available())
|
|
{
|
|
/* increase the multiple to increase precision of this calculation */
|
|
new_stretch = GET_STRETCH(new_pitch, speed);
|
|
if(new_stretch < STRETCH_MIN)
|
|
{
|
|
/* we have to ignore allow_cutoff, because we can't have the */
|
|
/* stretch go higher than STRETCH_MAX */
|
|
new_pitch = GET_PITCH(speed, STRETCH_MIN);
|
|
}
|
|
else if(new_stretch > STRETCH_MAX)
|
|
{
|
|
/* we have to ignore allow_cutoff, because we can't have the */
|
|
/* stretch go higher than STRETCH_MAX */
|
|
new_pitch = GET_PITCH(speed, STRETCH_MAX);
|
|
}
|
|
|
|
if(new_stretch >= STRETCH_MAX ||
|
|
new_stretch <= STRETCH_MIN)
|
|
{
|
|
at_limit = true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
sound_set_pitch(new_pitch);
|
|
|
|
return new_pitch;
|
|
}
|
|
|
|
static int32_t get_semitone_from_pitch(int32_t pitch)
|
|
{
|
|
int semitone = 0;
|
|
int32_t fractional_index = 0;
|
|
|
|
while(semitone < NUM_SEMITONES - 1 &&
|
|
pitch >= semitone_table[semitone + 1])
|
|
{
|
|
semitone++;
|
|
}
|
|
|
|
|
|
/* now find the fractional part */
|
|
while(pitch > (cent_interp[fractional_index + 1] *
|
|
semitone_table[semitone] / PITCH_SPEED_100))
|
|
{
|
|
/* Check to make sure fractional_index isn't too big */
|
|
/* This should never happen. */
|
|
if(fractional_index >= CENT_INTERP_NUM - 1)
|
|
{
|
|
break;
|
|
}
|
|
fractional_index++;
|
|
}
|
|
|
|
int32_t semitone_pitch_a = cent_interp[fractional_index] *
|
|
semitone_table[semitone] /
|
|
PITCH_SPEED_100;
|
|
int32_t semitone_pitch_b = cent_interp[fractional_index + 1] *
|
|
semitone_table[semitone] /
|
|
PITCH_SPEED_100;
|
|
/* this will be the integer offset from the cent_interp entry */
|
|
int32_t semitone_frac_ofs = (pitch - semitone_pitch_a) * CENT_INTERP_INTERVAL /
|
|
(semitone_pitch_b - semitone_pitch_a);
|
|
semitone = (semitone + SEMITONE_START) * PITCH_SPEED_PRECISION +
|
|
fractional_index * CENT_INTERP_INTERVAL +
|
|
semitone_frac_ofs;
|
|
|
|
return semitone;
|
|
}
|
|
|
|
static int32_t get_pitch_from_semitone(int32_t semitone)
|
|
{
|
|
int32_t adjusted_semitone = semitone - SEMITONE_START * PITCH_SPEED_PRECISION;
|
|
|
|
/* Find the index into the semitone table */
|
|
int32_t semitone_index = (adjusted_semitone / PITCH_SPEED_PRECISION);
|
|
|
|
/* set pitch to the semitone's integer part value */
|
|
int32_t pitch = semitone_table[semitone_index];
|
|
/* get the range of the cent modification for future calculation */
|
|
int32_t pitch_mod_a =
|
|
cent_interp[(adjusted_semitone % PITCH_SPEED_PRECISION) /
|
|
CENT_INTERP_INTERVAL];
|
|
int32_t pitch_mod_b =
|
|
cent_interp[(adjusted_semitone % PITCH_SPEED_PRECISION) /
|
|
CENT_INTERP_INTERVAL + 1];
|
|
/* figure out the cent mod amount based on the semitone fractional value */
|
|
int32_t pitch_mod = pitch_mod_a + (pitch_mod_b - pitch_mod_a) *
|
|
(adjusted_semitone % CENT_INTERP_INTERVAL) / CENT_INTERP_INTERVAL;
|
|
|
|
/* modify pitch based on the mod amount we just calculated */
|
|
return (pitch * pitch_mod + PITCH_SPEED_100 / 2) / PITCH_SPEED_100;
|
|
}
|
|
|
|
static int32_t pitch_increase_semitone(int32_t pitch,
|
|
int32_t current_semitone,
|
|
int32_t semitone_delta
|
|
#if CONFIG_CODEC == SWCODEC
|
|
, int32_t speed
|
|
#endif
|
|
)
|
|
{
|
|
int32_t new_semitone = current_semitone;
|
|
|
|
/* snap to the delta interval */
|
|
if(current_semitone % semitone_delta != 0)
|
|
{
|
|
if(current_semitone > 0 && semitone_delta > 0)
|
|
new_semitone += semitone_delta;
|
|
else if(current_semitone < 0 && semitone_delta < 0)
|
|
new_semitone += semitone_delta;
|
|
|
|
new_semitone -= new_semitone % semitone_delta;
|
|
}
|
|
else
|
|
new_semitone += semitone_delta;
|
|
|
|
/* clamp the pitch so it doesn't go beyond the pitch limits */
|
|
if(new_semitone < (SEMITONE_START * PITCH_SPEED_PRECISION))
|
|
{
|
|
new_semitone = SEMITONE_START * PITCH_SPEED_PRECISION;
|
|
at_limit = true;
|
|
}
|
|
else if(new_semitone > (SEMITONE_END * PITCH_SPEED_PRECISION))
|
|
{
|
|
new_semitone = SEMITONE_END * PITCH_SPEED_PRECISION;
|
|
at_limit = true;
|
|
}
|
|
|
|
int32_t new_pitch = get_pitch_from_semitone(new_semitone);
|
|
|
|
#if CONFIG_CODEC == SWCODEC
|
|
int32_t new_stretch = GET_STRETCH(new_pitch, speed);
|
|
|
|
/* clamp the pitch so it doesn't go beyond the stretch limits */
|
|
if( new_stretch > STRETCH_MAX)
|
|
{
|
|
new_pitch = GET_PITCH(speed, STRETCH_MAX);
|
|
new_semitone = get_semitone_from_pitch(new_pitch);
|
|
at_limit = true;
|
|
}
|
|
else if (new_stretch < STRETCH_MIN)
|
|
{
|
|
new_pitch = GET_PITCH(speed, STRETCH_MIN);
|
|
new_semitone = get_semitone_from_pitch(new_pitch);
|
|
at_limit = true;
|
|
}
|
|
#endif
|
|
|
|
pitch_increase(pitch, new_pitch - pitch, false
|
|
#if CONFIG_CODEC == SWCODEC
|
|
, speed
|
|
#endif
|
|
);
|
|
|
|
return new_semitone;
|
|
}
|
|
|
|
/*
|
|
returns:
|
|
0 on exit
|
|
1 if USB was connected
|
|
*/
|
|
|
|
int gui_syncpitchscreen_run(void)
|
|
{
|
|
int button, i;
|
|
int32_t pitch = sound_get_pitch();
|
|
int32_t semitone;
|
|
|
|
int32_t new_pitch;
|
|
int32_t pitch_delta;
|
|
bool nudged = false;
|
|
bool exit = false;
|
|
/* should maybe be passed per parameter later, not needed for now */
|
|
struct viewport parent[NB_SCREENS];
|
|
struct viewport pitch_viewports[NB_SCREENS][PITCH_ITEM_COUNT];
|
|
int max_lines[NB_SCREENS];
|
|
|
|
#if CONFIG_CODEC == SWCODEC
|
|
int32_t new_speed = 0, new_stretch;
|
|
|
|
/* the speed variable holds the apparent speed of the playback */
|
|
int32_t speed;
|
|
if (dsp_timestretch_available())
|
|
{
|
|
speed = GET_SPEED(pitch, dsp_get_timestretch());
|
|
}
|
|
else
|
|
{
|
|
speed = pitch;
|
|
}
|
|
|
|
/* Figure out whether to be in timestretch mode */
|
|
if (global_settings.pitch_mode_timestretch && !dsp_timestretch_available())
|
|
{
|
|
global_settings.pitch_mode_timestretch = false;
|
|
settings_save();
|
|
}
|
|
#endif
|
|
|
|
/* set the semitone index based on the current pitch */
|
|
semitone = get_semitone_from_pitch(pitch);
|
|
|
|
/* initialize pitchscreen vps */
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
screens[i].clear_display();
|
|
viewport_set_defaults(&parent[i], i);
|
|
max_lines[i] = viewport_get_nb_lines(&parent[i]);
|
|
pitchscreen_fix_viewports(&parent[i], pitch_viewports[i]);
|
|
|
|
/* also, draw the icons now, it's only needed once */
|
|
pitchscreen_draw_icons(&screens[i], &parent[i]);
|
|
}
|
|
#if CONFIG_CODEC == SWCODEC
|
|
pcmbuf_set_low_latency(true);
|
|
#endif
|
|
|
|
while (!exit)
|
|
{
|
|
FOR_NB_SCREENS(i)
|
|
pitchscreen_draw(&screens[i], max_lines[i],
|
|
pitch_viewports[i], pitch, semitone
|
|
#if CONFIG_CODEC == SWCODEC
|
|
, speed
|
|
#endif
|
|
);
|
|
pitch_delta = 0;
|
|
#if CONFIG_CODEC == SWCODEC
|
|
new_speed = 0;
|
|
#endif
|
|
button = get_action(CONTEXT_PITCHSCREEN, HZ);
|
|
switch (button)
|
|
{
|
|
case ACTION_PS_INC_SMALL:
|
|
if(global_settings.pitch_mode_semitone)
|
|
pitch_delta = SEMITONE_SMALL_DELTA;
|
|
else
|
|
pitch_delta = PITCH_SMALL_DELTA;
|
|
break;
|
|
|
|
case ACTION_PS_INC_BIG:
|
|
if(global_settings.pitch_mode_semitone)
|
|
pitch_delta = SEMITONE_BIG_DELTA;
|
|
else
|
|
pitch_delta = PITCH_BIG_DELTA;
|
|
break;
|
|
|
|
case ACTION_PS_DEC_SMALL:
|
|
if(global_settings.pitch_mode_semitone)
|
|
pitch_delta = -SEMITONE_SMALL_DELTA;
|
|
else
|
|
pitch_delta = -PITCH_SMALL_DELTA;
|
|
break;
|
|
|
|
case ACTION_PS_DEC_BIG:
|
|
if(global_settings.pitch_mode_semitone)
|
|
pitch_delta = -SEMITONE_BIG_DELTA;
|
|
else
|
|
pitch_delta = -PITCH_BIG_DELTA;
|
|
break;
|
|
|
|
case ACTION_PS_NUDGE_RIGHT:
|
|
#if CONFIG_CODEC == SWCODEC
|
|
if (!global_settings.pitch_mode_timestretch)
|
|
{
|
|
#endif
|
|
new_pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false
|
|
#if CONFIG_CODEC == SWCODEC
|
|
, speed
|
|
#endif
|
|
);
|
|
nudged = (new_pitch != pitch);
|
|
pitch = new_pitch;
|
|
semitone = get_semitone_from_pitch(pitch);
|
|
#if CONFIG_CODEC == SWCODEC
|
|
speed = pitch;
|
|
#endif
|
|
break;
|
|
#if CONFIG_CODEC == SWCODEC
|
|
}
|
|
else
|
|
{
|
|
new_speed = speed + SPEED_SMALL_DELTA;
|
|
at_limit = false;
|
|
}
|
|
break;
|
|
|
|
case ACTION_PS_FASTER:
|
|
if (global_settings.pitch_mode_timestretch)
|
|
{
|
|
new_speed = speed + SPEED_BIG_DELTA;
|
|
/* snap to whole numbers */
|
|
if(new_speed % PITCH_SPEED_PRECISION != 0)
|
|
new_speed -= new_speed % PITCH_SPEED_PRECISION;
|
|
at_limit = false;
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
case ACTION_PS_NUDGE_RIGHTOFF:
|
|
if (nudged)
|
|
{
|
|
pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false
|
|
#if CONFIG_CODEC == SWCODEC
|
|
, speed
|
|
#endif
|
|
);
|
|
#if CONFIG_CODEC == SWCODEC
|
|
speed = pitch;
|
|
#endif
|
|
semitone = get_semitone_from_pitch(pitch);
|
|
nudged = false;
|
|
}
|
|
break;
|
|
|
|
case ACTION_PS_NUDGE_LEFT:
|
|
#if CONFIG_CODEC == SWCODEC
|
|
if (!global_settings.pitch_mode_timestretch)
|
|
{
|
|
#endif
|
|
new_pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false
|
|
#if CONFIG_CODEC == SWCODEC
|
|
, speed
|
|
#endif
|
|
);
|
|
nudged = (new_pitch != pitch);
|
|
pitch = new_pitch;
|
|
semitone = get_semitone_from_pitch(pitch);
|
|
#if CONFIG_CODEC == SWCODEC
|
|
speed = pitch;
|
|
#endif
|
|
break;
|
|
#if CONFIG_CODEC == SWCODEC
|
|
}
|
|
else
|
|
{
|
|
new_speed = speed - SPEED_SMALL_DELTA;
|
|
at_limit = false;
|
|
}
|
|
break;
|
|
|
|
case ACTION_PS_SLOWER:
|
|
if (global_settings.pitch_mode_timestretch)
|
|
{
|
|
new_speed = speed - SPEED_BIG_DELTA;
|
|
/* snap to whole numbers */
|
|
if(new_speed % PITCH_SPEED_PRECISION != 0)
|
|
new_speed += PITCH_SPEED_PRECISION - speed % PITCH_SPEED_PRECISION;
|
|
at_limit = false;
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
case ACTION_PS_NUDGE_LEFTOFF:
|
|
if (nudged)
|
|
{
|
|
pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false
|
|
#if CONFIG_CODEC == SWCODEC
|
|
, speed
|
|
#endif
|
|
);
|
|
#if CONFIG_CODEC == SWCODEC
|
|
speed = pitch;
|
|
#endif
|
|
semitone = get_semitone_from_pitch(pitch);
|
|
nudged = false;
|
|
}
|
|
break;
|
|
|
|
case ACTION_PS_RESET:
|
|
pitch = PITCH_SPEED_100;
|
|
sound_set_pitch(pitch);
|
|
#if CONFIG_CODEC == SWCODEC
|
|
speed = PITCH_SPEED_100;
|
|
if (dsp_timestretch_available())
|
|
{
|
|
dsp_set_timestretch(PITCH_SPEED_100);
|
|
at_limit = false;
|
|
}
|
|
#endif
|
|
semitone = get_semitone_from_pitch(pitch);
|
|
break;
|
|
|
|
case ACTION_PS_TOGGLE_MODE:
|
|
global_settings.pitch_mode_semitone = !global_settings.pitch_mode_semitone;
|
|
#if CONFIG_CODEC == SWCODEC
|
|
|
|
if (dsp_timestretch_available() && !global_settings.pitch_mode_semitone)
|
|
{
|
|
global_settings.pitch_mode_timestretch = !global_settings.pitch_mode_timestretch;
|
|
if(!global_settings.pitch_mode_timestretch)
|
|
{
|
|
/* no longer in timestretch mode. Reset speed */
|
|
speed = pitch;
|
|
dsp_set_timestretch(PITCH_SPEED_100);
|
|
}
|
|
}
|
|
settings_save();
|
|
#endif
|
|
break;
|
|
|
|
case ACTION_PS_EXIT:
|
|
exit = true;
|
|
break;
|
|
|
|
default:
|
|
if (default_event_handler(button) == SYS_USB_CONNECTED)
|
|
return 1;
|
|
break;
|
|
}
|
|
if (pitch_delta)
|
|
{
|
|
if (global_settings.pitch_mode_semitone)
|
|
{
|
|
semitone = pitch_increase_semitone(pitch, semitone, pitch_delta
|
|
#if CONFIG_CODEC == SWCODEC
|
|
, speed
|
|
#endif
|
|
);
|
|
pitch = get_pitch_from_semitone(semitone);
|
|
}
|
|
else
|
|
{
|
|
pitch = pitch_increase(pitch, pitch_delta, true
|
|
#if CONFIG_CODEC == SWCODEC
|
|
, speed
|
|
#endif
|
|
);
|
|
semitone = get_semitone_from_pitch(pitch);
|
|
}
|
|
#if CONFIG_CODEC == SWCODEC
|
|
if (global_settings.pitch_mode_timestretch)
|
|
{
|
|
/* do this to make sure we properly obey the stretch limits */
|
|
new_speed = speed;
|
|
}
|
|
else
|
|
{
|
|
speed = pitch;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if CONFIG_CODEC == SWCODEC
|
|
if(new_speed)
|
|
{
|
|
new_stretch = GET_STRETCH(pitch, new_speed);
|
|
|
|
/* limit the amount of stretch */
|
|
if(new_stretch > STRETCH_MAX)
|
|
{
|
|
new_stretch = STRETCH_MAX;
|
|
new_speed = GET_SPEED(pitch, new_stretch);
|
|
}
|
|
else if(new_stretch < STRETCH_MIN)
|
|
{
|
|
new_stretch = STRETCH_MIN;
|
|
new_speed = GET_SPEED(pitch, new_stretch);
|
|
}
|
|
|
|
new_stretch = GET_STRETCH(pitch, new_speed);
|
|
if(new_stretch >= STRETCH_MAX ||
|
|
new_stretch <= STRETCH_MIN)
|
|
{
|
|
at_limit = true;
|
|
}
|
|
|
|
/* set the amount of stretch */
|
|
dsp_set_timestretch(new_stretch);
|
|
|
|
/* update the speed variable with the new speed */
|
|
speed = new_speed;
|
|
|
|
/* Reset new_speed so we only call dsp_set_timestretch */
|
|
/* when needed */
|
|
new_speed = 0;
|
|
}
|
|
#endif
|
|
}
|
|
#if CONFIG_CODEC == SWCODEC
|
|
pcmbuf_set_low_latency(false);
|
|
#endif
|
|
return 0;
|
|
}
|