23d9812273
struct plugin_api *rb is declared in PLUGIN_HEADER, and pointed to by __header.api the loader uses this pointer to initialize rb before calling entry_point entry_point is no longer passed a pointer to the plugin API all plugins, and pluginlib functions, are modified to refer to the global rb pluginlib functions which only served to copy the API pointer are removed git-svn-id: svn://svn.rockbox.org/rockbox/trunk@19776 a1c6a512-1295-4272-9138-f99709370657
476 lines
13 KiB
C
476 lines
13 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2007 Peter D'Hoye
|
|
*
|
|
* 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 "plugin.h"
|
|
|
|
PLUGIN_HEADER
|
|
|
|
/* temp byte buffer */
|
|
uint8_t samples[10 * 1024]; /* read 10KB at the time */
|
|
|
|
static struct wav_header
|
|
{
|
|
int8_t chunkid[4];
|
|
int32_t chunksize;
|
|
int8_t format[4];
|
|
int8_t formatchunkid[4];
|
|
int32_t formatchunksize;
|
|
int16_t audioformat;
|
|
int16_t numchannels;
|
|
uint32_t samplerate;
|
|
uint32_t byterate;
|
|
uint16_t blockalign;
|
|
uint16_t bitspersample;
|
|
int8_t datachunkid[4];
|
|
int32_t datachunksize;
|
|
} header;
|
|
|
|
#define WAV_HEADER_FORMAT "4L44LSSLLSS4L"
|
|
|
|
struct peakstruct
|
|
{
|
|
int lmin;
|
|
int lmax;
|
|
int rmin;
|
|
int rmax;
|
|
};
|
|
|
|
/* TO DO: limit used LCD height, so the waveform isn't streched vertically? */
|
|
|
|
#define LEFTZERO (LCD_HEIGHT / 4)
|
|
#define RIGHTZERO (3 * (LCD_HEIGHT / 4))
|
|
#define YSCALE ((0x8000 / (LCD_HEIGHT / 4)) + 1)
|
|
|
|
/* global vars */
|
|
static char *audiobuf;
|
|
static ssize_t audiobuflen;
|
|
static uint32_t mempeakcount = 0;
|
|
static uint32_t filepeakcount = 0;
|
|
static uint32_t fppmp = 0; /* file peaks per mem peaks */
|
|
static uint32_t zoomlevel = 1;
|
|
static uint32_t leftmargin = 0;
|
|
static uint32_t center = 0;
|
|
static uint32_t ppp = 1;
|
|
|
|
/* helper function copied from libwavpack bits.c */
|
|
void little_endian_to_native (void *data, char *format)
|
|
{
|
|
unsigned char *cp = (unsigned char *) data;
|
|
|
|
while (*format) {
|
|
switch (*format) {
|
|
case 'L':
|
|
*(long *)cp = letoh32(*(long *)cp);
|
|
cp += 4;
|
|
break;
|
|
|
|
case 'S':
|
|
*(short *)cp = letoh16(*(short *)cp);
|
|
cp += 2;
|
|
break;
|
|
|
|
default:
|
|
if (*format >= '0' && *format <= '9')
|
|
cp += *format - '0';
|
|
|
|
break;
|
|
}
|
|
format++;
|
|
}
|
|
}
|
|
/* --- */
|
|
|
|
/* read WAV file
|
|
display some info about it
|
|
store peak info in aufiobuf for display routine */
|
|
static int readwavpeaks(const char *filename)
|
|
{
|
|
register uint32_t bytes_read;
|
|
register uint32_t fppmp_count;
|
|
register int16_t sampleval;
|
|
register uint16_t* sampleshort = NULL;
|
|
|
|
int file;
|
|
uint32_t total_bytes_read = 0;
|
|
char tstr[128];
|
|
int hours;
|
|
int minutes;
|
|
int seconds;
|
|
uint32_t peakcount = 0;
|
|
struct peakstruct* peak = NULL;
|
|
|
|
if(rb->strcasecmp (filename + rb->strlen (filename) - 3, "wav"))
|
|
{
|
|
rb->splash(HZ*2, "Only for wav files!");
|
|
return -1;
|
|
}
|
|
|
|
file = rb->open(filename, O_RDONLY);
|
|
|
|
if(file < 0)
|
|
{
|
|
rb->splash(HZ*2, "Could not open file!");
|
|
return -1;
|
|
}
|
|
|
|
if(rb->read(file, &header, sizeof (header)) != sizeof (header))
|
|
{
|
|
rb->splash(HZ*2, "Could not read file!");
|
|
rb->close (file);
|
|
return -1;
|
|
}
|
|
|
|
total_bytes_read += sizeof (header);
|
|
little_endian_to_native(&header, WAV_HEADER_FORMAT);
|
|
|
|
if (rb->strncmp(header.chunkid, "RIFF", 4) ||
|
|
rb->strncmp(header.formatchunkid, "fmt ", 4) ||
|
|
rb->strncmp(header.datachunkid, "data", 4) ||
|
|
(header.bitspersample != 16) ||
|
|
header.audioformat != 1)
|
|
{
|
|
rb->splash(HZ*2, "Incompatible wav file!");
|
|
rb->close (file);
|
|
return -1;
|
|
}
|
|
|
|
rb->lcd_clear_display();
|
|
rb->lcd_puts(0, 0, "Viewing file:");
|
|
rb->lcd_puts_scroll(0, 1, (unsigned char *)filename);
|
|
rb->lcd_update();
|
|
|
|
rb->snprintf(tstr,127, "Channels: %s",
|
|
header.numchannels == 1 ? "mono" : "stereo");
|
|
rb->lcd_puts(0, 2, tstr);
|
|
|
|
rb->snprintf(tstr,127, "Bits/sample: %d", header.bitspersample);
|
|
rb->lcd_puts(0, 3, tstr);
|
|
|
|
rb->snprintf(tstr,127, "Samplerate: %d Hz", (int)(header.samplerate));
|
|
rb->lcd_puts(0, 4, tstr);
|
|
|
|
seconds = header.datachunksize / header.byterate;
|
|
hours = seconds / 3600;
|
|
seconds -= hours * 3600;
|
|
minutes = seconds / 60;
|
|
seconds -= minutes * 60;
|
|
rb->snprintf(tstr,127, "Length (hh:mm:ss): %02d:%02d:%02d", hours,
|
|
minutes,
|
|
seconds);
|
|
rb->lcd_puts(0, 5, tstr);
|
|
rb->lcd_puts(0, 6, "Searching for peaks... ");
|
|
rb->lcd_update();
|
|
|
|
/* calculate room for peaks */
|
|
filepeakcount = header.datachunksize /
|
|
(header.numchannels * (header.bitspersample / 8));
|
|
mempeakcount = audiobuflen / sizeof(struct peakstruct);
|
|
fppmp = (filepeakcount / mempeakcount) + 1;
|
|
peak = (struct peakstruct*)audiobuf;
|
|
|
|
fppmp_count = fppmp;
|
|
mempeakcount = 0;
|
|
while(total_bytes_read < (header.datachunksize +
|
|
sizeof(struct wav_header)))
|
|
{
|
|
bytes_read = rb->read(file, &samples, sizeof(samples));
|
|
total_bytes_read += bytes_read;
|
|
|
|
if(0 == bytes_read)
|
|
{
|
|
rb->splash(HZ*2, "File read error!");
|
|
rb->close (file);
|
|
return -1;
|
|
}
|
|
if(((bytes_read/4)*4) != bytes_read)
|
|
{
|
|
rb->splashf(HZ*2, "bytes_read/*4 err: %ld",(long int)bytes_read);
|
|
rb->close (file);
|
|
return -1;
|
|
}
|
|
|
|
sampleshort = (int16_t*)samples;
|
|
sampleval = letoh16(*sampleshort);
|
|
peak->lmin = sampleval;
|
|
peak->lmax = sampleval;
|
|
sampleval = letoh16(*(sampleshort+1));
|
|
peak->rmin = sampleval;
|
|
peak->rmax = sampleval;
|
|
|
|
while(bytes_read)
|
|
{
|
|
sampleval = letoh16(*sampleshort++);
|
|
if(sampleval < peak->lmin)
|
|
peak->lmin = sampleval;
|
|
else if (sampleval > peak->lmax)
|
|
peak->lmax = sampleval;
|
|
|
|
sampleval = letoh16(*sampleshort++);
|
|
if(sampleval < peak->rmin)
|
|
peak->rmin = sampleval;
|
|
else if (sampleval > peak->rmax)
|
|
peak->rmax = sampleval;
|
|
|
|
bytes_read -= 4;
|
|
peakcount++;
|
|
fppmp_count--;
|
|
if(!fppmp_count)
|
|
{
|
|
peak++;
|
|
mempeakcount++;
|
|
fppmp_count = fppmp;
|
|
sampleval = letoh16(*sampleshort);
|
|
peak->lmin = sampleval;
|
|
peak->lmax = sampleval;
|
|
sampleval = letoh16(*(sampleshort+1));
|
|
peak->rmin = sampleval;
|
|
peak->rmax = sampleval;
|
|
}
|
|
}
|
|
|
|
/* update progress */
|
|
rb->snprintf(tstr,127, "Searching for peaks... %d%%",(int)
|
|
(total_bytes_read / ((header.datachunksize +
|
|
sizeof(struct wav_header)) / 100)));
|
|
rb->lcd_puts(0, 6, tstr);
|
|
rb->lcd_update();
|
|
|
|
/* allow user to abort */
|
|
if(ACTION_KBD_ABORT == rb->get_action(CONTEXT_KEYBOARD,TIMEOUT_NOBLOCK))
|
|
{
|
|
rb->splash(HZ*2, "ABORTED");
|
|
rb->close (file);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
rb->lcd_puts(0, 6, "Searching for peaks... done");
|
|
rb->lcd_update();
|
|
|
|
rb->close (file);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int displaypeaks(void)
|
|
{
|
|
register int x = 0;
|
|
register int lymin = INT_MAX;
|
|
register int lymax = INT_MIN;
|
|
register int rymin = INT_MAX;
|
|
register int rymax = INT_MIN;
|
|
register unsigned int peakcount = 0;
|
|
struct peakstruct* peak = (struct peakstruct*)audiobuf + leftmargin;
|
|
|
|
#if LCD_DEPTH > 1
|
|
unsigned org_forecolor = rb->lcd_get_foreground();
|
|
rb->lcd_set_foreground(LCD_LIGHTGRAY);
|
|
#endif
|
|
|
|
if(!zoomlevel) zoomlevel = 1;
|
|
ppp = (mempeakcount / LCD_WIDTH) / zoomlevel; /* peaks per pixel */
|
|
|
|
rb->lcd_clear_display();
|
|
|
|
rb->lcd_hline(0, LCD_WIDTH-1, LEFTZERO - (0x8000 / YSCALE));
|
|
rb->lcd_hline(0, LCD_WIDTH-1, LEFTZERO);
|
|
rb->lcd_hline(0, LCD_WIDTH-1, LEFTZERO + (0x8000 / YSCALE));
|
|
rb->lcd_hline(0, LCD_WIDTH-1, RIGHTZERO - (0x8000 / YSCALE));
|
|
rb->lcd_hline(0, LCD_WIDTH-1, RIGHTZERO);
|
|
rb->lcd_hline(0, LCD_WIDTH-1, RIGHTZERO + (0x8000 / YSCALE));
|
|
|
|
#if LCD_DEPTH > 1
|
|
rb->lcd_set_foreground(LCD_BLACK);
|
|
#endif
|
|
|
|
/* draw zoombar */
|
|
rb->lcd_hline(leftmargin / (mempeakcount / LCD_WIDTH),
|
|
(leftmargin / (mempeakcount / LCD_WIDTH)) +
|
|
(LCD_WIDTH / zoomlevel),
|
|
LCD_HEIGHT / 2);
|
|
|
|
while((x < LCD_WIDTH) && (peakcount < mempeakcount))
|
|
{
|
|
if(peak->lmin < lymin)
|
|
lymin = peak->lmin;
|
|
if(peak->lmax > lymax)
|
|
lymax = peak->lmax;
|
|
if(peak->rmin < rymin)
|
|
rymin = peak->rmin;
|
|
if(peak->rmax > rymax)
|
|
rymax = peak->rmax;
|
|
peak++;
|
|
if(0 == (peakcount % ppp))
|
|
{
|
|
/* drawing time */
|
|
rb->lcd_vline(x, LEFTZERO - (lymax / YSCALE),
|
|
LEFTZERO - (lymin / YSCALE));
|
|
rb->lcd_vline(x, RIGHTZERO - (rymax / YSCALE),
|
|
RIGHTZERO - (rymin / YSCALE));
|
|
lymin = INT_MAX;
|
|
lymax = INT_MIN;
|
|
rymin = INT_MAX;
|
|
rymax = INT_MIN;
|
|
x++;
|
|
rb->lcd_update();
|
|
}
|
|
peakcount++;
|
|
}
|
|
|
|
#if LCD_DEPTH > 1
|
|
rb->lcd_set_foreground(org_forecolor);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
void show_help(void)
|
|
{
|
|
rb->lcd_clear_display();
|
|
rb->lcd_puts(0, 0, "WAVVIEW USAGE:");
|
|
rb->lcd_puts(0, 2, "up/down: zoom out/in");
|
|
rb->lcd_puts(0, 3, "left/right: pan left/right");
|
|
rb->lcd_puts(0, 4, "select: refresh/continue");
|
|
rb->lcd_puts(0, 5, "stop/off: quit");
|
|
rb->lcd_update();
|
|
}
|
|
|
|
enum plugin_status plugin_start(const void *parameter)
|
|
{
|
|
unsigned int quit = 0;
|
|
unsigned int action = 0;
|
|
unsigned int dodisplay = 1;
|
|
int retval;
|
|
|
|
if (!parameter)
|
|
return PLUGIN_ERROR;
|
|
|
|
audiobuf = rb->plugin_get_audio_buffer((size_t *)&audiobuflen);
|
|
|
|
if (!audiobuf)
|
|
{
|
|
rb->splash(HZ*2, "unable to get audio buffer!");
|
|
return PLUGIN_ERROR;
|
|
}
|
|
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
rb->cpu_boost(true);
|
|
#endif
|
|
|
|
retval = readwavpeaks(parameter); /* read WAV file and create peaks array */
|
|
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
rb->cpu_boost(false);
|
|
#endif
|
|
|
|
if(retval)
|
|
return 0;
|
|
|
|
/* press any key to continue */
|
|
while(1)
|
|
{
|
|
retval = rb->get_action(CONTEXT_KEYBOARD,TIMEOUT_BLOCK);
|
|
if(ACTION_KBD_ABORT == retval)
|
|
return 0;
|
|
else if(ACTION_KBD_SELECT == retval)
|
|
break;
|
|
}
|
|
|
|
/* start with the overview */
|
|
zoomlevel = 1;
|
|
leftmargin = 0;
|
|
|
|
while(!quit)
|
|
{
|
|
if(!center)
|
|
center = mempeakcount / 2;
|
|
if(zoomlevel <= 1)
|
|
{
|
|
zoomlevel = 1;
|
|
leftmargin = 0;
|
|
}
|
|
else
|
|
{
|
|
if(center < (mempeakcount / (zoomlevel * 2)))
|
|
center = mempeakcount / (zoomlevel * 2);
|
|
if(center > (((zoomlevel * 2) - 1) * (mempeakcount /
|
|
(zoomlevel * 2))))
|
|
center = ((zoomlevel * 2) - 1) * (mempeakcount /
|
|
(zoomlevel * 2));
|
|
leftmargin = center - (mempeakcount / (zoomlevel * 2));
|
|
}
|
|
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
rb->cpu_boost(true);
|
|
#endif
|
|
if(dodisplay)
|
|
displaypeaks();
|
|
dodisplay = 1;
|
|
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
rb->cpu_boost(false);
|
|
#endif
|
|
|
|
action = rb->get_action(CONTEXT_KEYBOARD, TIMEOUT_BLOCK);
|
|
switch(action)
|
|
{
|
|
case ACTION_KBD_UP:
|
|
/* zoom out */
|
|
if(zoomlevel > 1)
|
|
zoomlevel /= 2;
|
|
rb->splashf(HZ/2, "ZOOM: %dx",(int)zoomlevel);
|
|
break;
|
|
case ACTION_KBD_DOWN:
|
|
if(zoomlevel < (mempeakcount / LCD_WIDTH / 2))
|
|
zoomlevel *= 2;
|
|
rb->splashf(HZ/2, "ZOOM: %dx",(int)zoomlevel);
|
|
break;
|
|
case ACTION_KBD_LEFT:
|
|
center -= 10 * (mempeakcount / LCD_WIDTH) / zoomlevel;
|
|
break;
|
|
case ACTION_KBD_RIGHT:
|
|
center += 10 * (mempeakcount / LCD_WIDTH) / zoomlevel;
|
|
break;
|
|
case ACTION_KBD_ABORT:
|
|
quit = 1;
|
|
break;
|
|
case ACTION_KBD_SELECT:
|
|
/* refresh */
|
|
break;
|
|
case ACTION_KBD_PAGE_FLIP:
|
|
/* menu key shows help */
|
|
show_help();
|
|
while(1)
|
|
{
|
|
retval = rb->get_action(CONTEXT_KEYBOARD,TIMEOUT_BLOCK);
|
|
if((ACTION_KBD_SELECT == retval) ||
|
|
(ACTION_KBD_ABORT == retval))
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
/* eat it */
|
|
dodisplay = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|