rockbox/apps/plugins/wavview.c

477 lines
13 KiB
C
Raw Normal View History

/***************************************************************************
* __________ __ ___.
* 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 size_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(&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;
}