/*************************************************************************** * __________ __ ___. * 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" /* 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 */ static 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; 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->lcd_putsf(0, 2, "Channels: %s", header.numchannels == 1 ? "mono" : "stereo"); rb->lcd_putsf(0, 3, "Bits/sample: %d", header.bitspersample); rb->lcd_putsf(0, 4, "Samplerate: %"PRIu32" Hz", header.samplerate); seconds = header.datachunksize / header.byterate; hours = seconds / 3600; seconds -= hours * 3600; minutes = seconds / 60; seconds -= minutes * 60; rb->lcd_putsf(0, 5, "Length (hh:mm:ss): %02d:%02d:%02d", hours, minutes, seconds); 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->lcd_putsf(0, 6, "Searching for peaks... %"PRIu32"%%", (total_bytes_read / ((header.datachunksize + sizeof(struct wav_header)) / 100))); 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; } static 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; } static 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; }