fef82552e1
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@17083 a1c6a512-1295-4272-9138-f99709370657
1277 lines
36 KiB
C
1277 lines
36 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2002 Philipp Pertermann
|
|
*
|
|
* All files in this archive are subject to the GNU General Public License.
|
|
* See the file COPYING in the source tree root for full license agreement.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include "plugin.h"
|
|
|
|
#ifndef SIMULATOR
|
|
#ifdef HAVE_LCD_BITMAP
|
|
|
|
PLUGIN_HEADER
|
|
|
|
/* variable button definitions */
|
|
#if CONFIG_KEYPAD == RECORDER_PAD
|
|
#define SPLITEDIT_QUIT BUTTON_OFF
|
|
#define SPLITEDIT_PLAY BUTTON_PLAY
|
|
#define SPLITEDIT_SAVE BUTTON_F1
|
|
#define SPLITEDIT_LOOP_MODE BUTTON_F2
|
|
#define SPLITEDIT_SCALE BUTTON_F3
|
|
#define SPLITEDIT_SPEED50 (BUTTON_ON | BUTTON_LEFT)
|
|
#define SPLITEDIT_SPEED100 (BUTTON_ON | BUTTON_PLAY)
|
|
#define SPLITEDIT_SPEED150 (BUTTON_ON | BUTTON_RIGHT)
|
|
#define SPLITEDIT_MENU_RUN BUTTON_PLAY
|
|
|
|
#elif CONFIG_KEYPAD == ONDIO_PAD
|
|
#define SPLITEDIT_QUIT BUTTON_OFF
|
|
#define SPLITEDIT_PLAY_PRE BUTTON_MENU
|
|
#define SPLITEDIT_PLAY (BUTTON_MENU | BUTTON_REL)
|
|
#define SPLITEDIT_SAVE (BUTTON_MENU | BUTTON_LEFT)
|
|
#define SPLITEDIT_LOOP_MODE (BUTTON_MENU | BUTTON_UP)
|
|
#define SPLITEDIT_SCALE (BUTTON_MENU | BUTTON_RIGHT)
|
|
#define SPLITEDIT_MENU_RUN BUTTON_RIGHT
|
|
|
|
#elif CONFIG_KEYPAD == IRIVER_H100_PAD
|
|
#define SPLITEDIT_QUIT BUTTON_OFF
|
|
#define SPLITEDIT_PLAY BUTTON_ON
|
|
#define SPLITEDIT_SAVE BUTTON_SELECT
|
|
#define SPLITEDIT_LOOP_MODE BUTTON_MODE
|
|
#define SPLITEDIT_SCALE (BUTTON_REC | BUTTON_UP)
|
|
#define SPLITEDIT_SPEED50 (BUTTON_REC | BUTTON_LEFT)
|
|
#define SPLITEDIT_SPEED100 (BUTTON_REC | BUTTON_DOWN)
|
|
#define SPLITEDIT_SPEED150 (BUTTON_REC | BUTTON_RIGHT)
|
|
#define SPLITEDIT_MENU_RUN BUTTON_RIGHT
|
|
|
|
#define SPLITEDIT_RC_QUIT BUTTON_RC_STOP
|
|
#endif
|
|
|
|
#define BMPHEIGHT 7
|
|
#define BMPWIDTH 13
|
|
unsigned char LOOP_BMP[][13] =
|
|
{
|
|
{0xfc,0x00,0x10,0x11,0x93,0x7f,0x13,0x11,0x7c,0x38,0x10,0x00,0x7c}, /*ALL */
|
|
{0x81,0x03,0x7f,0x03,0x91,0x10,0x10,0x10,0x7c,0x38,0x10,0x00,0x7c}, /*FROM*/
|
|
{0xfc,0x00,0x10,0x10,0x90,0x10,0x7c,0x38,0x11,0x03,0x7f,0x03,0x01}, /*TO */
|
|
{0x80,0x10,0x10,0x11,0x93,0x7f,0x13,0x11,0x10,0x7c,0x38,0x10,0x00}, /*FREE*/
|
|
};
|
|
|
|
unsigned char CUT_BMP[] =
|
|
{
|
|
0xc1,0x63,0x63,0x36,0xb6,0x1c,0x1c,0x36,0x77,0x55,0x55,0x55,0x32,
|
|
};
|
|
|
|
unsigned char SCALE_BMP[][13] =
|
|
{
|
|
{0x80,0x06,0x49,0x66,0xb0,0x18,0x0c,0x06,0x33,0x49,0x30,0x00,0x00}, /*lin*/
|
|
{0x80,0x30,0x78,0x48,0xff,0x7f,0x00,0x7f,0x7f,0x48,0x78,0x30,0x00}, /*db*/
|
|
};
|
|
|
|
#define TIMEBAR_Y 9
|
|
#define TIMEBAR_HEIGHT 4
|
|
|
|
#define OSCI_X 0
|
|
#define OSCI_Y (TIMEBAR_Y + TIMEBAR_HEIGHT + 1)
|
|
#define OSCI_WIDTH LCD_WIDTH
|
|
#define OSCI_HEIGHT (LCD_HEIGHT - BMPHEIGHT - OSCI_Y - 1)
|
|
|
|
/* Indices of the menu items in the save editor, see save_editor */
|
|
#define SE_PART1_SAVE 0
|
|
#define SE_PART1_NAME 1
|
|
#define SE_PART2_SAVE 2
|
|
#define SE_PART2_NAME 3
|
|
#define SE_SAVE 4
|
|
#define SE_COUNT 5
|
|
|
|
/* the global api pointer */
|
|
static struct plugin_api* rb;
|
|
|
|
/* contains the file name of the song that is to be split */
|
|
static char path_mp3[MAX_PATH];
|
|
|
|
/* Exit code of this plugin */
|
|
static enum plugin_status splitedit_exit_code = PLUGIN_OK;
|
|
|
|
/* The range in time that the displayed aerea comprises */
|
|
static unsigned int range_start = 0;
|
|
static unsigned int range_end = 0;
|
|
|
|
/* The range in time that is being looped */
|
|
static unsigned int play_start = 0;
|
|
static unsigned int play_end = 0;
|
|
|
|
/* Point in time (pixel) at which the split mark is set */
|
|
static int split_x = OSCI_X + (OSCI_WIDTH / 2);
|
|
|
|
/* Contains the peak values */
|
|
static unsigned char osci_buffer[OSCI_WIDTH];
|
|
|
|
/* if true peak values from a previous loop are only overwritten
|
|
if the new value is greater than the old value */
|
|
static bool osci_valid = false;
|
|
|
|
/**
|
|
* point in time from which on the osci_buffer is invalid
|
|
* if set to ~(unsigned int)0 the entire osci_buffer is invalid
|
|
*/
|
|
static unsigned int validation_start = ~(unsigned int)0;
|
|
|
|
/* all the visible aerea is looped */
|
|
#define LOOP_MODE_ALL 0
|
|
|
|
/* loop starts at split point, ends at right visible border */
|
|
#define LOOP_MODE_FROM 1
|
|
|
|
/* loop start at left visible border, ends at split point */
|
|
#define LOOP_MODE_TO 2
|
|
|
|
/* let the song play without looping */
|
|
#define LOOP_MODE_FREE 3
|
|
|
|
/* see LOOP_MODE_XXX constants vor valid values */
|
|
static int loop_mode = LOOP_MODE_FREE;
|
|
|
|
/* minimal allowed timespan (ms) of the visible (and looped) aerea*/
|
|
#define MIN_RANGE_SIZE 1000
|
|
|
|
/* Format time into buf.
|
|
*
|
|
* buf - buffer to format to.
|
|
* buf_size - size of buffer.
|
|
* time - time to format, in milliseconds.
|
|
*/
|
|
static void format_time_ms(char* buf, int buf_size, int time)
|
|
{
|
|
rb->snprintf(buf, buf_size, "%d:%02d:%03d", time / 60000,
|
|
time % 60000 / 1000, (time % 60000) % 1000);
|
|
}
|
|
|
|
/**
|
|
* converts screen coordinate (pixel) to time (ms)
|
|
*/
|
|
static int xpos_to_time(int xpos)
|
|
{
|
|
int retval = 0;
|
|
int range = range_end - range_start;
|
|
retval = range_start + (((xpos - OSCI_X) * range) / OSCI_WIDTH);
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* Converts time (ms) to screen coordinates (pixel).
|
|
*/
|
|
static int time_to_xpos(unsigned int time)
|
|
{
|
|
int retval = OSCI_X;
|
|
|
|
/* clip the range */
|
|
if (time < range_start)
|
|
{
|
|
retval = OSCI_X;
|
|
}
|
|
else
|
|
if (time >= range_end)
|
|
{
|
|
retval = OSCI_X + OSCI_WIDTH;
|
|
}
|
|
|
|
/* do the calculation */
|
|
else
|
|
{
|
|
int range = range_end - range_start;
|
|
retval = OSCI_X + ((time - range_start) * OSCI_WIDTH) / range ;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* Updates the display of the textual data only.
|
|
*/
|
|
static void update_data(void)
|
|
{
|
|
char buf[20];
|
|
char timebuf[10];
|
|
int w, h;
|
|
|
|
/* split point */
|
|
format_time_ms(timebuf, sizeof buf, xpos_to_time(split_x));
|
|
rb->snprintf(buf, sizeof buf, "Split at: %s", timebuf);
|
|
|
|
rb->lcd_getstringsize(buf, &w, &h);
|
|
|
|
rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
|
|
rb->lcd_fillrect(0, 0, LCD_WIDTH, h);
|
|
rb->lcd_set_drawmode(DRMODE_SOLID);
|
|
rb->lcd_puts(0, 0, buf);
|
|
rb->lcd_update_rect(0, 0, LCD_WIDTH, h);
|
|
}
|
|
|
|
/**
|
|
* Displays which part of the song is visible
|
|
* in the osci.
|
|
*/
|
|
static void update_timebar(struct mp3entry *mp3)
|
|
{
|
|
rb->gui_scrollbar_draw
|
|
(
|
|
rb->screens[SCREEN_MAIN],0, TIMEBAR_Y, LCD_WIDTH, TIMEBAR_HEIGHT,
|
|
mp3->length, range_start, range_end,
|
|
HORIZONTAL
|
|
);
|
|
rb->lcd_update_rect(0, TIMEBAR_Y, LCD_WIDTH, TIMEBAR_HEIGHT);
|
|
}
|
|
|
|
/**
|
|
* Marks the entire area of the osci buffer invalid.
|
|
* It will be drawn with new values in the next loop.
|
|
*/
|
|
void splitedit_invalidate_osci(void)
|
|
{
|
|
osci_valid = false;
|
|
validation_start = ~(unsigned int)0;
|
|
}
|
|
|
|
/**
|
|
* Returns the loop mode. See the LOOP_MODE_XXX constants above.
|
|
*/
|
|
int splitedit_get_loop_mode(void)
|
|
{
|
|
return loop_mode;
|
|
}
|
|
|
|
/**
|
|
* Updates the icons that display the Fn key hints.
|
|
*/
|
|
static void update_icons(void)
|
|
{
|
|
rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
|
|
rb->lcd_fillrect(0, LCD_HEIGHT - BMPHEIGHT, LCD_WIDTH, BMPHEIGHT);
|
|
rb->lcd_set_drawmode(DRMODE_SOLID);
|
|
|
|
/* The CUT icon */
|
|
rb->lcd_mono_bitmap(CUT_BMP,
|
|
LCD_WIDTH / 3 / 2 - BMPWIDTH / 2, LCD_HEIGHT - BMPHEIGHT,
|
|
BMPWIDTH, BMPHEIGHT);
|
|
|
|
/* The loop mode icon */
|
|
rb->lcd_mono_bitmap(LOOP_BMP[splitedit_get_loop_mode()],
|
|
LCD_WIDTH/3 + LCD_WIDTH/3 / 2 - BMPWIDTH/2, LCD_HEIGHT - BMPHEIGHT,
|
|
BMPWIDTH, BMPHEIGHT);
|
|
|
|
#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F)
|
|
/* The scale icon */
|
|
rb->lcd_mono_bitmap(SCALE_BMP[rb->peak_meter_get_use_dbfs() ? 1 : 0],
|
|
2 *LCD_WIDTH/3 + LCD_WIDTH/3 / 2 - BMPWIDTH/2, LCD_HEIGHT - BMPHEIGHT,
|
|
BMPWIDTH, BMPHEIGHT);
|
|
#else
|
|
{
|
|
static int idx;
|
|
if (idx < 0 || idx > 1) idx = 0;
|
|
idx = 1 - idx;
|
|
rb->lcd_mono_bitmap(SCALE_BMP[idx],
|
|
2 *LCD_WIDTH/3 + LCD_WIDTH/3 / 2 - BMPWIDTH/2, LCD_HEIGHT - BMPHEIGHT,
|
|
BMPWIDTH, BMPHEIGHT);
|
|
}
|
|
#endif
|
|
|
|
rb->lcd_update_rect(0, LCD_HEIGHT - BMPHEIGHT, LCD_WIDTH, BMPHEIGHT);
|
|
}
|
|
|
|
/**
|
|
* Sets the loop mode. See the LOOP_MODE_XXX constants above.
|
|
*/
|
|
void splitedit_set_loop_mode(int mode)
|
|
{
|
|
int old_loop_mode = loop_mode;
|
|
/* range restriction */
|
|
loop_mode = mode % (LOOP_MODE_FREE + 1);
|
|
switch (loop_mode)
|
|
{
|
|
case LOOP_MODE_ALL:
|
|
play_start = range_start;
|
|
play_end = range_end;
|
|
break;
|
|
|
|
case LOOP_MODE_FROM:
|
|
play_start = xpos_to_time(split_x);
|
|
play_end = range_end;
|
|
break;
|
|
|
|
case LOOP_MODE_TO:
|
|
play_start = range_start;
|
|
play_end = xpos_to_time(split_x);
|
|
break;
|
|
|
|
case LOOP_MODE_FREE:
|
|
/* play_start is used when the song plays beyond its end */
|
|
play_start = range_start;
|
|
play_end = range_end;
|
|
break;
|
|
}
|
|
|
|
if (loop_mode != old_loop_mode)
|
|
{
|
|
update_icons();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Readraws the osci without clear.
|
|
*/
|
|
static void redraw_osci(void)
|
|
{
|
|
int x;
|
|
for (x = 0; x < OSCI_WIDTH; x++)
|
|
{
|
|
if (osci_buffer[x] > 0)
|
|
{
|
|
rb->lcd_vline
|
|
(
|
|
OSCI_X + x, OSCI_Y + OSCI_HEIGHT - 1,
|
|
OSCI_Y + OSCI_HEIGHT - osci_buffer[x] - 1
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the range of time in which the user can finetune the split
|
|
* point. The split point is the center of the time range.
|
|
*/
|
|
static void set_range_by_time(
|
|
struct mp3entry *mp3,
|
|
unsigned int split_time,
|
|
unsigned int range)
|
|
{
|
|
if (mp3 != NULL)
|
|
{
|
|
if (range < MIN_RANGE_SIZE)
|
|
{
|
|
range = MIN_RANGE_SIZE;
|
|
}
|
|
range_start = (split_time > range / 2) ? (split_time - range / 2) : 0;
|
|
range_end = MIN(range_start + range, mp3->length);
|
|
split_x = time_to_xpos(split_time);
|
|
|
|
splitedit_invalidate_osci();
|
|
|
|
/* this sets the play_start / play_end */
|
|
splitedit_set_loop_mode(splitedit_get_loop_mode());
|
|
|
|
update_data();
|
|
update_timebar(mp3);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the split point in screen coordinates
|
|
*/
|
|
void splitedit_set_split_x(int newx)
|
|
{
|
|
int minx = split_x - 2 > 0 ? split_x - 2: 0;
|
|
|
|
/* remove old split point from screen, only if moved */
|
|
if (split_x != newx)
|
|
{
|
|
rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
|
|
rb->lcd_fillrect(minx, OSCI_Y, 5, 1);
|
|
rb->lcd_fillrect(split_x-1 > 0 ? split_x - 1: 0, OSCI_Y + 1, 3, 1);
|
|
rb->lcd_fillrect(split_x, OSCI_Y + 2, 1, OSCI_HEIGHT - 2);
|
|
rb->lcd_set_drawmode(DRMODE_SOLID);
|
|
rb->lcd_update_rect(minx, OSCI_Y, 5, OSCI_HEIGHT);
|
|
}
|
|
|
|
if (newx >= OSCI_X && newx < OSCI_X + OSCI_WIDTH)
|
|
{
|
|
split_x = newx;
|
|
/* in LOOP_FROM / LOOP_TO modes play_start /play_end must be updated */
|
|
splitedit_set_loop_mode(splitedit_get_loop_mode());
|
|
|
|
/* display new split time */
|
|
update_data();
|
|
}
|
|
|
|
/* display new split point */
|
|
minx = split_x - 2 > 0 ? split_x - 2: 0;
|
|
rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
|
|
rb->lcd_fillrect(minx, OSCI_Y, 5, 1);
|
|
rb->lcd_fillrect(split_x - 1 > 0 ? split_x - 1: 0, OSCI_Y + 1, 3, 1);
|
|
rb->lcd_fillrect(split_x, OSCI_Y + 2, 1, OSCI_HEIGHT - 2);
|
|
rb->lcd_set_drawmode(DRMODE_SOLID);
|
|
rb->lcd_update_rect(minx, OSCI_Y, 5, OSCI_HEIGHT);
|
|
}
|
|
|
|
/**
|
|
* returns the split point in screen coordinates
|
|
*/
|
|
int splitedit_get_split_x(void)
|
|
{
|
|
return split_x;
|
|
}
|
|
|
|
/**
|
|
* Clears the osci area and redraws it
|
|
*/
|
|
static void update_osci(void)
|
|
{
|
|
rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
|
|
rb->lcd_fillrect(OSCI_X, OSCI_Y, OSCI_WIDTH, OSCI_HEIGHT);
|
|
rb->lcd_set_drawmode(DRMODE_SOLID);
|
|
redraw_osci();
|
|
splitedit_set_split_x(splitedit_get_split_x());
|
|
rb->lcd_update_rect(OSCI_X, OSCI_Y, OSCI_WIDTH, OSCI_HEIGHT);
|
|
}
|
|
|
|
/**
|
|
* Zooms the visable and loopable range by the factor
|
|
* (counter / denominator). The split point is used as
|
|
* center point of the new selected range.
|
|
*/
|
|
static void zoom(struct mp3entry *mp3, int counter, int denominator)
|
|
{
|
|
unsigned char oldbuf[OSCI_WIDTH];
|
|
int oldrange = range_end - range_start;
|
|
int range = oldrange * counter / denominator;
|
|
int i;
|
|
int oldindex;
|
|
int oldsplitx;
|
|
int splitx;
|
|
int split;
|
|
|
|
/* for stretching / shrinking a second buffer is needed */
|
|
rb->memcpy(&oldbuf, &osci_buffer, sizeof osci_buffer);
|
|
|
|
/* recalculate the new range and split point */
|
|
oldsplitx = split_x;
|
|
split = xpos_to_time(split_x);
|
|
|
|
set_range_by_time(mp3, split, range);
|
|
range = range_end - range_start;
|
|
|
|
splitx = time_to_xpos(split);
|
|
|
|
/* strech / shrink the existing osci buffer */
|
|
for (i = 0; i < OSCI_WIDTH; i++)
|
|
{
|
|
/* oldindex = (i + OSCI_X - splitx) * range / oldrange + oldsplitx ;*/
|
|
oldindex = (i*range / oldrange) + oldsplitx - (splitx*range /oldrange);
|
|
if (oldindex >= 0 && oldindex < OSCI_WIDTH)
|
|
{
|
|
osci_buffer[i] = oldbuf[oldindex];
|
|
}
|
|
else
|
|
{
|
|
osci_buffer[i] = 0;
|
|
}
|
|
}
|
|
|
|
splitx = time_to_xpos(split);
|
|
splitedit_set_split_x(splitx);
|
|
splitedit_invalidate_osci();
|
|
|
|
}
|
|
|
|
static void scroll(struct mp3entry *mp3)
|
|
{
|
|
zoom(mp3, 1, 1);
|
|
rb->lcd_update_rect(OSCI_X, OSCI_Y, LCD_WIDTH, OSCI_HEIGHT);
|
|
update_osci();
|
|
update_data();
|
|
}
|
|
|
|
/**
|
|
* Zooms in by 3/4
|
|
*/
|
|
void splitedit_zoom_in(struct mp3entry *mp3)
|
|
{
|
|
rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
|
|
rb->lcd_fillrect(OSCI_X, OSCI_Y, OSCI_WIDTH, OSCI_HEIGHT);
|
|
rb->lcd_set_drawmode(DRMODE_SOLID);
|
|
zoom(mp3, 3, 4);
|
|
rb->lcd_update_rect(OSCI_X, OSCI_Y, LCD_WIDTH, OSCI_HEIGHT);
|
|
update_osci();
|
|
update_data();
|
|
}
|
|
|
|
/**
|
|
* Zooms out by 4/3
|
|
*/
|
|
void splitedit_zoom_out(struct mp3entry *mp3)
|
|
{
|
|
rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
|
|
rb->lcd_fillrect(OSCI_X, OSCI_Y, LCD_WIDTH, OSCI_HEIGHT);
|
|
rb->lcd_set_drawmode(DRMODE_SOLID);
|
|
zoom(mp3, 4, 3);
|
|
rb->lcd_update_rect(OSCI_X, OSCI_Y, LCD_WIDTH, OSCI_HEIGHT);
|
|
update_osci();
|
|
update_data();
|
|
}
|
|
|
|
/**
|
|
* Append part_no to the file name.
|
|
*/
|
|
static void generateFileName(char* file_name, int part_no)
|
|
{
|
|
if (rb->strlen(file_name) <MAX_PATH)
|
|
{
|
|
int len = rb->strlen(file_name);
|
|
int ext_len = rb->strlen(".mp3");
|
|
if (rb->strcasecmp(
|
|
&file_name[len - ext_len],
|
|
".mp3") == 0)
|
|
{
|
|
int i = 0;
|
|
/* shift the extension one position to the right*/
|
|
for (i = len; i > len - ext_len; i--)
|
|
{
|
|
file_name[i] = file_name[i - 1];
|
|
}
|
|
file_name[len - ext_len] = '0' + part_no;
|
|
}
|
|
else
|
|
{
|
|
rb->splash(0, "wrong extension");
|
|
rb->button_get(true);
|
|
rb->button_get(true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rb->splash(0, "name too long");
|
|
rb->button_get(true);
|
|
rb->button_get(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Copy bytes from src to dest while displaying a progressbar.
|
|
* The files must be already open.
|
|
*/
|
|
static int copy_file(
|
|
int dest,
|
|
int src,
|
|
unsigned int bytes,
|
|
int prg_y,
|
|
int prg_h)
|
|
{
|
|
long button;
|
|
unsigned char *buffer;
|
|
unsigned int i = 0;
|
|
ssize_t bytes_read = 1; /* ensure the for loop is executed */
|
|
size_t buffer_size;
|
|
buffer = rb->plugin_get_buffer(&buffer_size);
|
|
|
|
for (i = 0; i < bytes && bytes_read > 0; i += bytes_read)
|
|
{
|
|
ssize_t bytes_written;
|
|
unsigned int bytes_to_read =
|
|
bytes - i > buffer_size ? buffer_size : bytes - i;
|
|
bytes_read = rb->read(src, buffer, bytes_to_read);
|
|
bytes_written = rb->write(dest, buffer, bytes_read);
|
|
|
|
if (bytes_written < 0) {
|
|
rb->splash(0, "Write failed in copy.");
|
|
rb->button_get(true);
|
|
rb->button_get(true);
|
|
return -1;
|
|
}
|
|
|
|
button = rb->button_get(false);
|
|
|
|
if (button == SPLITEDIT_QUIT
|
|
#ifdef SPLITEDIT_RC_QUIT
|
|
|| button == SPLITEDIT_RC_QUIT:
|
|
#endif
|
|
) {
|
|
rb->splash(0, "Aborting copy.");
|
|
rb->button_get(true);
|
|
rb->button_get(true);
|
|
return -1;
|
|
}
|
|
|
|
rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN],0, prg_y, LCD_WIDTH,
|
|
prg_h, bytes, 0, i, HORIZONTAL);
|
|
rb->lcd_update_rect(0, prg_y, LCD_WIDTH, prg_h);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Save the files, if the file_name is not NULL
|
|
*/
|
|
static int save(
|
|
struct mp3entry *mp3,
|
|
char *file_name1,
|
|
char *file_name2,
|
|
int splittime)
|
|
{
|
|
int file1, file2, src_file;
|
|
unsigned int end = 0;
|
|
int retval = 0;
|
|
|
|
/* Verify that file 1 doesn't exit yet */
|
|
if (file_name1 != NULL)
|
|
{
|
|
file1 = rb->open(file_name1, O_RDONLY);
|
|
if (file1 >= 0)
|
|
{
|
|
rb->close(file1);
|
|
rb->splash(0, "File 1 exists. Please rename.");
|
|
rb->button_get(true);
|
|
rb->button_get(true);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Verify that file 2 doesn't exit yet */
|
|
if (file_name2 != NULL)
|
|
{
|
|
file2 = rb->open(file_name2, O_RDONLY);
|
|
if (file2 >= 0)
|
|
{
|
|
rb->close(file2);
|
|
rb->splash(0, "File 2 exists. Please rename.");
|
|
rb->button_get(true);
|
|
rb->button_get(true);
|
|
return -2;
|
|
}
|
|
}
|
|
|
|
/* find the file position of the split point */
|
|
rb->audio_pause();
|
|
rb->audio_ff_rewind(splittime);
|
|
rb->yield();
|
|
rb->yield();
|
|
end = rb->audio_get_file_pos();
|
|
|
|
/* open the source file */
|
|
src_file = rb->open(mp3->path, O_RDONLY);
|
|
if (src_file >= 0)
|
|
{
|
|
int close_stat = 0;
|
|
int x, y;
|
|
long offset;
|
|
unsigned long last_header = rb->mpeg_get_last_header();
|
|
|
|
rb->lcd_getstringsize("M", &x, &y);
|
|
|
|
/* Find the next frame boundary */
|
|
rb->lseek(src_file, end, SEEK_SET);
|
|
rb->find_next_frame(src_file, &offset, 8000, last_header);
|
|
rb->lseek(src_file, 0, SEEK_SET);
|
|
end += offset;
|
|
|
|
/* write the file 1 */
|
|
if (file_name1 != NULL)
|
|
{
|
|
file1 = rb->open (file_name1, O_WRONLY | O_CREAT);
|
|
if (file1 >= 0)
|
|
{
|
|
int rc = copy_file(file1, src_file, end, y*2 + 1, y -1);
|
|
close_stat = rb->close(file1);
|
|
|
|
if (close_stat != 0)
|
|
{
|
|
rb->splash(0, "failed closing file1: error %d", close_stat);
|
|
rb->button_get(true);
|
|
rb->button_get(true);
|
|
} else {
|
|
/* If there was an error, cleanup */
|
|
if (rc) {
|
|
rb->remove(file_name1);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rb->splash(0, "Can't write File1: error %d", file1);
|
|
rb->button_get(true);
|
|
rb->button_get(true);
|
|
retval = -1;
|
|
}
|
|
}
|
|
/* if file1 hasn't been written we're not at the split point yet */
|
|
else
|
|
{
|
|
if (rb->lseek(src_file, end, SEEK_SET) < (off_t)end)
|
|
{
|
|
rb->splash(0, "Src file to short: error %d", src_file);
|
|
rb->button_get(true);
|
|
rb->button_get(true);
|
|
}
|
|
}
|
|
|
|
if (file_name2 != NULL)
|
|
{
|
|
/* write file 2 */
|
|
file2 = rb->open (file_name2, O_WRONLY | O_CREAT);
|
|
if (file2 >= 0)
|
|
{
|
|
end = mp3->filesize - end;
|
|
int rc = copy_file(file2, src_file, end, y * 5 + 1, y -1);
|
|
close_stat = rb->close(file2);
|
|
|
|
if (close_stat != 0)
|
|
{
|
|
rb->splash(0, "failed: closing file2: error %d",
|
|
close_stat);
|
|
rb->button_get(true);
|
|
rb->button_get(true);
|
|
} else {
|
|
/* If there was an error, cleanup */
|
|
if (rc) {
|
|
rb->remove(file_name2);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rb->splash(0, "Can't write File2: error %d", file2);
|
|
rb->button_get(true);
|
|
rb->button_get(true);
|
|
retval = -2;
|
|
}
|
|
}
|
|
|
|
close_stat = rb->close(src_file);
|
|
if (close_stat != 0)
|
|
{
|
|
rb->splash(0, "failed: closing src: error %d", close_stat);
|
|
rb->button_get(true);
|
|
rb->button_get(true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rb->splash(0, "Source file not found");
|
|
rb->button_get(true);
|
|
rb->button_get(true);
|
|
retval = -3;
|
|
}
|
|
|
|
rb->audio_resume();
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* Let the user choose which file to save with which name
|
|
*/
|
|
static void save_editor(struct mp3entry *mp3, int splittime)
|
|
{
|
|
bool exit_request = false;
|
|
int choice = 0;
|
|
int button = BUTTON_NONE;
|
|
char part1_name [MAX_PATH];
|
|
char part2_name [MAX_PATH];
|
|
bool part1_save = true;
|
|
bool part2_save = true;
|
|
|
|
/* file name for left part */
|
|
rb->strncpy(part1_name, mp3->path, MAX_PATH);
|
|
generateFileName(part1_name, 1);
|
|
|
|
/* file name for right part */
|
|
rb->strncpy(part2_name, mp3->path, MAX_PATH);
|
|
generateFileName(part2_name, 2);
|
|
|
|
while (!exit_request)
|
|
{
|
|
int pos;
|
|
rb->lcd_clear_display();
|
|
|
|
/* Save file1? */
|
|
rb->lcd_puts_style(0, 0, "Save part 1?", choice == SE_PART1_SAVE);
|
|
rb->lcd_puts(13, 0, part1_save?"yes":"no");
|
|
|
|
/* trim to display the filename without path */
|
|
for (pos = rb->strlen(part1_name); pos > 0; pos--)
|
|
{
|
|
if (part1_name[pos] == '/')
|
|
break;
|
|
}
|
|
pos++;
|
|
|
|
/* File name 1 */
|
|
rb->lcd_puts_scroll_style(0, 1,
|
|
&part1_name[pos], choice == SE_PART1_NAME);
|
|
|
|
/* Save file2? */
|
|
rb->lcd_puts_style(0, 3, "Save part 2?", choice == SE_PART2_SAVE);
|
|
rb->lcd_puts(13, 3, part2_save?"yes":"no");
|
|
|
|
/* trim to display the filename without path */
|
|
for (pos = rb->strlen(part2_name); pos > 0; pos --)
|
|
{
|
|
if (part2_name[pos] == '/')
|
|
break;
|
|
}
|
|
pos++;
|
|
|
|
/* File name 2 */
|
|
rb->lcd_puts_scroll_style(0, 4,
|
|
&part2_name[pos], choice == SE_PART2_NAME);
|
|
|
|
/* Save */
|
|
rb->lcd_puts_style(0, 6, "Save", choice == SE_SAVE);
|
|
|
|
rb->lcd_update();
|
|
|
|
|
|
button = rb->button_get(true);
|
|
switch (button)
|
|
{
|
|
case BUTTON_UP:
|
|
choice = (choice + SE_COUNT - 1) % SE_COUNT;
|
|
break;
|
|
|
|
case BUTTON_DOWN:
|
|
choice = (choice + 1) % SE_COUNT;
|
|
break;
|
|
|
|
case SPLITEDIT_MENU_RUN:
|
|
switch (choice)
|
|
{
|
|
int saved;
|
|
|
|
case SE_PART1_SAVE:
|
|
part1_save = !part1_save;
|
|
break;
|
|
|
|
case SE_PART1_NAME:
|
|
rb->kbd_input(part1_name, MAX_PATH);
|
|
break;
|
|
|
|
case SE_PART2_SAVE:
|
|
part2_save = !part2_save;
|
|
break;
|
|
|
|
case SE_PART2_NAME:
|
|
rb->kbd_input(part2_name, MAX_PATH);
|
|
break;
|
|
|
|
case SE_SAVE:
|
|
rb->lcd_stop_scroll();
|
|
rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
|
|
rb->lcd_fillrect(0, 6*8, LCD_WIDTH, LCD_HEIGHT);
|
|
rb->lcd_set_drawmode(DRMODE_SOLID);
|
|
saved = save
|
|
(
|
|
mp3,
|
|
part1_save?part1_name:NULL,
|
|
part2_save?part2_name:NULL,
|
|
splittime
|
|
);
|
|
|
|
/* if something failed the user may go on choosing */
|
|
if (saved >= 0)
|
|
{
|
|
exit_request = true;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
#ifdef SPLITEDIT_RC_QUIT
|
|
case SPLITEDIT_RC_QUIT:
|
|
#endif
|
|
case SPLITEDIT_QUIT:
|
|
exit_request = true;
|
|
break;
|
|
|
|
default:
|
|
if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
|
|
{
|
|
splitedit_exit_code = PLUGIN_USB_CONNECTED;
|
|
exit_request = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The main loop of the editor
|
|
*/
|
|
unsigned long splitedit_editor(struct mp3entry * mp3_to_split,
|
|
unsigned int split_time,
|
|
unsigned int range)
|
|
{
|
|
int button = BUTTON_NONE;
|
|
int lastbutton = BUTTON_NONE;
|
|
struct mp3entry *mp3 = mp3_to_split;
|
|
unsigned int last_elapsed = 0;
|
|
int lastx = OSCI_X + (OSCI_WIDTH / 2);
|
|
int retval = -1;
|
|
|
|
if (mp3 != NULL)
|
|
{
|
|
/*unsigned short scheme = SCHEME_SPLIT_EDITOR;*/
|
|
bool exit_request = false;
|
|
set_range_by_time(mp3, split_time, range);
|
|
splitedit_set_loop_mode(LOOP_MODE_ALL);
|
|
update_icons();
|
|
|
|
/*while (scheme != SCHEME_RETURN) {*/
|
|
while (!exit_request)
|
|
{
|
|
unsigned int elapsed ;
|
|
int x ;
|
|
|
|
/* get position */
|
|
elapsed = mp3->elapsed;
|
|
x = time_to_xpos(elapsed);
|
|
|
|
/* are we still in the zoomed range? */
|
|
if (elapsed > play_start && elapsed < play_end)
|
|
{
|
|
/* read volume info */
|
|
unsigned short volume;
|
|
#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F)
|
|
volume = rb->mas_codec_readreg(0x0c);
|
|
volume += rb->mas_codec_readreg(0x0d);
|
|
volume = volume / 2;
|
|
volume = rb->peak_meter_scale_value(volume, OSCI_HEIGHT);
|
|
#else
|
|
volume = OSCI_HEIGHT / 2;
|
|
#endif
|
|
|
|
/* update osci_buffer */
|
|
if (osci_valid || lastx == x)
|
|
{
|
|
int index = x - OSCI_X;
|
|
osci_buffer[index] = MAX(osci_buffer[index], volume);
|
|
}
|
|
else
|
|
{
|
|
int i;
|
|
osci_buffer[x - OSCI_X] = volume;
|
|
for (i = lastx + 1; i < x; i++)
|
|
{
|
|
osci_buffer[i - OSCI_X] = 0;
|
|
}
|
|
}
|
|
|
|
/* make room */
|
|
rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
|
|
rb->lcd_fillrect(lastx + 1, OSCI_Y, x - lastx, OSCI_HEIGHT);
|
|
rb->lcd_set_drawmode(DRMODE_SOLID);
|
|
/* draw a value */
|
|
if (osci_buffer[x - OSCI_X] > 0)
|
|
{
|
|
int i;
|
|
for (i = lastx +1; i <= x; i++)
|
|
{
|
|
rb->lcd_vline
|
|
(
|
|
i, OSCI_Y + OSCI_HEIGHT - 1,
|
|
OSCI_Y + OSCI_HEIGHT - osci_buffer[i - OSCI_X]-1
|
|
);
|
|
}
|
|
}
|
|
|
|
/* mark the current position */
|
|
if (lastx != x)
|
|
{
|
|
rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
|
|
rb->lcd_fillrect(lastx, OSCI_Y, 1, OSCI_HEIGHT);
|
|
rb->lcd_fillrect(x, OSCI_Y, 1, OSCI_HEIGHT);
|
|
rb->lcd_set_drawmode(DRMODE_SOLID);
|
|
}
|
|
|
|
/* mark the split point */
|
|
if ((x > split_x - 2) && (lastx < split_x + 3))
|
|
{
|
|
if ((lastx < split_x) && (x >= split_x))
|
|
{
|
|
rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
|
|
rb->lcd_fillrect
|
|
(
|
|
split_x, OSCI_Y + 2,
|
|
1, OSCI_HEIGHT - 2
|
|
);
|
|
rb->lcd_set_drawmode(DRMODE_SOLID);
|
|
}
|
|
rb->lcd_hline(split_x -2, split_x + 2, OSCI_Y);
|
|
rb->lcd_hline(split_x-1, split_x +1,OSCI_Y+1);
|
|
}
|
|
|
|
/* make visible */
|
|
if (lastx <= x)
|
|
{
|
|
rb->lcd_update_rect(lastx, OSCI_Y, x-lastx+1, OSCI_HEIGHT);
|
|
}
|
|
else
|
|
{
|
|
rb->lcd_update_rect
|
|
(
|
|
lastx, OSCI_Y,
|
|
OSCI_X + OSCI_WIDTH - lastx, OSCI_HEIGHT
|
|
);
|
|
rb->lcd_update_rect(0, OSCI_Y, x + 1, OSCI_HEIGHT);
|
|
}
|
|
|
|
lastx = x;
|
|
}
|
|
|
|
/* we're not in the zoom range -> rewind */
|
|
else
|
|
{
|
|
if (elapsed >= play_end)
|
|
{
|
|
switch (splitedit_get_loop_mode())
|
|
{
|
|
unsigned int range_width;
|
|
|
|
case LOOP_MODE_ALL:
|
|
case LOOP_MODE_TO:
|
|
rb->audio_pause();
|
|
rb->audio_ff_rewind(range_start);
|
|
#ifdef HAVE_MMC
|
|
/* MMC is slow - wait some time to allow track reload to finish */
|
|
rb->sleep(HZ/20);
|
|
if (mp3->elapsed > play_end) /* reload in progress */
|
|
rb->splash(10*HZ, "Wait - reloading");
|
|
#endif
|
|
rb->audio_resume();
|
|
break;
|
|
|
|
case LOOP_MODE_FROM:
|
|
rb->audio_pause();
|
|
rb->audio_ff_rewind(xpos_to_time(split_x));
|
|
#ifdef HAVE_MMC
|
|
/* MMC is slow - wait some time to allow track reload to finish */
|
|
rb->sleep(HZ/20);
|
|
if (mp3->elapsed > play_end) /* reload in progress */
|
|
rb->splash(10*HZ, "Wait - reloading");
|
|
#endif
|
|
rb->audio_resume();
|
|
break;
|
|
|
|
case LOOP_MODE_FREE:
|
|
range_width = range_end - range_start;
|
|
set_range_by_time(mp3,
|
|
range_end + range_width / 2, range_width);
|
|
|
|
/* play_end und play_start anpassen */
|
|
splitedit_set_loop_mode(LOOP_MODE_FREE);
|
|
rb->memset(osci_buffer, 0, sizeof osci_buffer);
|
|
update_osci();
|
|
rb->lcd_update();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
button = rb->button_get(false);
|
|
rb->yield();
|
|
|
|
/* here the evaluation of the key scheme starts.
|
|
All functions the user triggers are called from
|
|
within execute_scheme */
|
|
/* key_scheme_execute(button, &scheme); */
|
|
switch (button)
|
|
{
|
|
case SPLITEDIT_PLAY:
|
|
#ifdef SPLITEDIT_PLAY_PRE
|
|
if (lastbutton != SPLITEDIT_PLAY_PRE)
|
|
break;
|
|
#endif
|
|
rb->audio_pause();
|
|
rb->audio_ff_rewind(xpos_to_time(split_x));
|
|
rb->audio_resume();
|
|
break;
|
|
|
|
case BUTTON_UP:
|
|
splitedit_zoom_in(mp3);
|
|
lastx = time_to_xpos(mp3->elapsed);
|
|
break;
|
|
|
|
case BUTTON_DOWN:
|
|
splitedit_zoom_out(mp3);
|
|
lastx = time_to_xpos(mp3->elapsed);
|
|
break;
|
|
|
|
#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F)
|
|
#ifdef SPLITEDIT_SPEED100
|
|
case SPLITEDIT_SPEED150:
|
|
rb->sound_set_pitch(1500);
|
|
splitedit_invalidate_osci();
|
|
break;
|
|
|
|
case SPLITEDIT_SPEED100:
|
|
rb->sound_set_pitch(1000);
|
|
splitedit_invalidate_osci();
|
|
break;
|
|
|
|
case SPLITEDIT_SPEED50:
|
|
rb->sound_set_pitch(500);
|
|
splitedit_invalidate_osci();
|
|
break;
|
|
#endif
|
|
#endif
|
|
|
|
case BUTTON_LEFT:
|
|
case BUTTON_LEFT | BUTTON_REPEAT:
|
|
if (splitedit_get_split_x() > OSCI_X + 2)
|
|
{
|
|
splitedit_set_split_x(splitedit_get_split_x() - 1);
|
|
}
|
|
else
|
|
{
|
|
scroll(mp3);
|
|
lastx = time_to_xpos(mp3->elapsed);
|
|
}
|
|
break;
|
|
|
|
case BUTTON_RIGHT:
|
|
case BUTTON_RIGHT | BUTTON_REPEAT:
|
|
if (splitedit_get_split_x() < OSCI_X + OSCI_WIDTH-3)
|
|
{
|
|
splitedit_set_split_x(splitedit_get_split_x() + 1);
|
|
}
|
|
else
|
|
{
|
|
scroll(mp3);
|
|
lastx = time_to_xpos(mp3->elapsed);
|
|
}
|
|
break;
|
|
|
|
case SPLITEDIT_SAVE:
|
|
save_editor(mp3, xpos_to_time(split_x));
|
|
rb->lcd_clear_display();
|
|
update_osci();
|
|
update_timebar(mp3);
|
|
update_icons();
|
|
break;
|
|
|
|
case SPLITEDIT_LOOP_MODE:
|
|
splitedit_set_loop_mode(splitedit_get_loop_mode() + 1);
|
|
update_icons();
|
|
break;
|
|
|
|
case SPLITEDIT_SCALE:
|
|
#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F)
|
|
rb->peak_meter_set_use_dbfs(!rb->peak_meter_get_use_dbfs());
|
|
#endif
|
|
splitedit_invalidate_osci();
|
|
update_icons();
|
|
break;
|
|
|
|
#ifdef SPLITEDIT_RC_QUIT
|
|
case SPLITEDIT_RC_QUIT:
|
|
#endif
|
|
case SPLITEDIT_QUIT:
|
|
exit_request = true;
|
|
break;
|
|
|
|
default:
|
|
if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
|
|
{
|
|
splitedit_exit_code = PLUGIN_USB_CONNECTED;
|
|
exit_request = true;
|
|
}
|
|
break;
|
|
|
|
}
|
|
if (button != BUTTON_NONE)
|
|
lastbutton = button;
|
|
|
|
if (validation_start == ~(unsigned int)0)
|
|
{
|
|
if (elapsed < range_end && elapsed > range_start)
|
|
{
|
|
validation_start = elapsed;
|
|
}
|
|
else
|
|
{
|
|
int endx = time_to_xpos(range_end);
|
|
validation_start = xpos_to_time(endx - 2);
|
|
}
|
|
last_elapsed = elapsed + 1;
|
|
}
|
|
else
|
|
{
|
|
if ((last_elapsed <= validation_start) &&
|
|
(elapsed > validation_start))
|
|
{
|
|
osci_valid = true;
|
|
}
|
|
|
|
last_elapsed = elapsed;
|
|
}
|
|
update_data();
|
|
|
|
if (mp3 != rb->audio_current_track())
|
|
{
|
|
struct mp3entry *new_mp3 = rb->audio_current_track();
|
|
if (rb->strncasecmp(path_mp3, new_mp3->path,
|
|
sizeof (path_mp3)))
|
|
{
|
|
rb->splash(0, "Abort due to file change");
|
|
rb->button_get(true);
|
|
rb->button_get(true);
|
|
exit_request = true;
|
|
}
|
|
else
|
|
{
|
|
mp3 = new_mp3;
|
|
rb->audio_pause();
|
|
rb->audio_flush_and_reload_tracks();
|
|
rb->audio_ff_rewind(range_start);
|
|
rb->audio_resume();
|
|
}
|
|
}
|
|
}
|
|
#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F)
|
|
#ifdef SPLITEDIT_SPEED100
|
|
rb->sound_set_pitch(1000); /* make sure to reset pitch */
|
|
#endif
|
|
#endif
|
|
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
|
|
{
|
|
struct mp3entry* mp3;
|
|
|
|
(void)parameter;
|
|
rb = api;
|
|
rb->lcd_clear_display();
|
|
rb->lcd_update();
|
|
mp3 = rb->audio_current_track();
|
|
if (mp3 != NULL)
|
|
{
|
|
if (rb->audio_status() & AUDIO_STATUS_PAUSE)
|
|
{
|
|
rb->audio_resume();
|
|
}
|
|
splitedit_editor(mp3, mp3->elapsed, MIN_RANGE_SIZE * 8);
|
|
}
|
|
else
|
|
{
|
|
rb->splash(0, "Play or pause a mp3 file first.");
|
|
rb->button_get(true);
|
|
rb->button_get(true);
|
|
}
|
|
return splitedit_exit_code;
|
|
}
|
|
#endif
|
|
#endif
|