Accept FS#7228 by Dagni McPhee enable pitchscreen on sansa
[Rockbox.git] / apps / plugins / wavview.c
blobfbf2e4383d5c9e267f527830110fd600da720f3a
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2007 Peter D'Hoye
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
19 #include "plugin.h"
21 PLUGIN_HEADER
23 static struct plugin_api* rb;
25 /* temp byte buffer */
26 uint8_t samples[10 * 1024]; /* read 10KB at the time */
28 static struct wav_header
30 int8_t chunkid[4];
31 int32_t chunksize;
32 int8_t format[4];
33 int8_t formatchunkid[4];
34 int32_t formatchunksize;
35 int16_t audioformat;
36 int16_t numchannels;
37 uint32_t samplerate;
38 uint32_t byterate;
39 uint16_t blockalign;
40 uint16_t bitspersample;
41 int8_t datachunkid[4];
42 int32_t datachunksize;
43 } header;
45 #define WAV_HEADER_FORMAT "4L44LSSLLSS4L"
47 struct peakstruct
49 int lmin;
50 int lmax;
51 int rmin;
52 int rmax;
55 /* TO DO: limit used LCD height, so the waveform isn't streched vertically? */
57 #define LEFTZERO (LCD_HEIGHT / 4)
58 #define RIGHTZERO (3 * (LCD_HEIGHT / 4))
59 #define YSCALE ((0x8000 / (LCD_HEIGHT / 4)) + 1)
61 /* global vars */
62 static char *audiobuf;
63 static ssize_t audiobuflen;
64 static uint32_t mempeakcount = 0;
65 static uint32_t filepeakcount = 0;
66 static uint32_t fppmp = 0; /* file peaks per mem peaks */
67 static uint32_t zoomlevel = 1;
68 static uint32_t leftmargin = 0;
69 static uint32_t center = 0;
70 static uint32_t ppp = 1;
72 /* helper function copied from libwavpack bits.c */
73 void little_endian_to_native (void *data, char *format)
75 unsigned char *cp = (unsigned char *) data;
77 while (*format) {
78 switch (*format) {
79 case 'L':
80 *(long *)cp = letoh32(*(long *)cp);
81 cp += 4;
82 break;
84 case 'S':
85 *(short *)cp = letoh16(*(short *)cp);
86 cp += 2;
87 break;
89 default:
90 if (*format >= '0' && *format <= '9')
91 cp += *format - '0';
93 break;
95 format++;
98 /* --- */
100 /* read WAV file
101 display some info about it
102 store peak info in aufiobuf for display routine */
103 static int readwavpeaks(char *filename)
105 register uint32_t bytes_read;
106 register uint32_t fppmp_count;
107 register int16_t sampleval;
108 register uint16_t* sampleshort = NULL;
110 int file;
111 uint32_t total_bytes_read = 0;
112 char tstr[128];
113 int hours;
114 int minutes;
115 int seconds;
116 uint32_t peakcount = 0;
117 struct peakstruct* peak = NULL;
119 if(rb->strcasecmp (filename + rb->strlen (filename) - 3, "wav"))
121 rb->splash(HZ*2, "Only for wav files!");
122 return -1;
125 file = rb->open(filename, O_RDONLY);
127 if(file < 0)
129 rb->splash(HZ*2, "Could not open file!");
130 return -1;
133 if(rb->read(file, &header, sizeof (header)) != sizeof (header))
135 rb->splash(HZ*2, "Could not read file!");
136 rb->close (file);
137 return -1;
140 total_bytes_read += sizeof (header);
141 little_endian_to_native(&header, WAV_HEADER_FORMAT);
143 if (rb->strncmp(header.chunkid, "RIFF", 4) ||
144 rb->strncmp(header.formatchunkid, "fmt ", 4) ||
145 rb->strncmp(header.datachunkid, "data", 4) ||
146 (header.bitspersample != 16) ||
147 header.audioformat != 1)
149 rb->splash(HZ*2, "Incompatible wav file!");
150 rb->close (file);
151 return -1;
154 rb->lcd_clear_display();
155 rb->lcd_puts(0, 0, "Viewing file:");
156 rb->lcd_puts_scroll(0, 1, (unsigned char *)filename);
157 rb->lcd_update();
159 rb->snprintf(tstr,127, "Channels: %s",
160 header.numchannels == 1 ? "mono" : "stereo");
161 rb->lcd_puts(0, 2, tstr);
163 rb->snprintf(tstr,127, "Bits/sample: %d", header.bitspersample);
164 rb->lcd_puts(0, 3, tstr);
166 rb->snprintf(tstr,127, "Samplerate: %d Hz", (int)(header.samplerate));
167 rb->lcd_puts(0, 4, tstr);
169 seconds = header.datachunksize / header.byterate;
170 hours = seconds / 3600;
171 seconds -= hours * 3600;
172 minutes = seconds / 60;
173 seconds -= minutes * 60;
174 rb->snprintf(tstr,127, "Length (hh:mm:ss): %02d:%02d:%02d", hours,
175 minutes,
176 seconds);
177 rb->lcd_puts(0, 5, tstr);
178 rb->lcd_puts(0, 6, "Searching for peaks... ");
179 rb->lcd_update();
181 /* calculate room for peaks */
182 filepeakcount = header.datachunksize /
183 (header.numchannels * (header.bitspersample / 8));
184 mempeakcount = audiobuflen / sizeof(struct peakstruct);
185 fppmp = (filepeakcount / mempeakcount) + 1;
186 peak = (struct peakstruct*)audiobuf;
188 fppmp_count = fppmp;
189 mempeakcount = 0;
190 while(total_bytes_read < (header.datachunksize +
191 sizeof(struct wav_header)))
193 bytes_read = rb->read(file, &samples, sizeof(samples));
194 total_bytes_read += bytes_read;
196 if(0 == bytes_read)
198 rb->splash(HZ*2, "File read error!");
199 rb->close (file);
200 return -1;
202 if(((bytes_read/4)*4) != bytes_read)
204 rb->splash(HZ*2, "bytes_read/*4 err: %ld",(long int)bytes_read);
205 rb->close (file);
206 return -1;
209 sampleshort = (int16_t*)samples;
210 sampleval = letoh16(*sampleshort);
211 peak->lmin = sampleval;
212 peak->lmax = sampleval;
213 sampleval = letoh16(*(sampleshort+1));
214 peak->rmin = sampleval;
215 peak->rmax = sampleval;
217 while(bytes_read)
219 sampleval = letoh16(*sampleshort++);
220 if(sampleval < peak->lmin)
221 peak->lmin = sampleval;
222 else if (sampleval > peak->lmax)
223 peak->lmax = sampleval;
225 sampleval = letoh16(*sampleshort++);
226 if(sampleval < peak->rmin)
227 peak->rmin = sampleval;
228 else if (sampleval > peak->rmax)
229 peak->rmax = sampleval;
231 bytes_read -= 4;
232 peakcount++;
233 fppmp_count--;
234 if(!fppmp_count)
236 peak++;
237 mempeakcount++;
238 fppmp_count = fppmp;
239 sampleval = letoh16(*sampleshort);
240 peak->lmin = sampleval;
241 peak->lmax = sampleval;
242 sampleval = letoh16(*(sampleshort+1));
243 peak->rmin = sampleval;
244 peak->rmax = sampleval;
248 /* update progress */
249 rb->snprintf(tstr,127, "Searching for peaks... %d%%",(int)
250 (total_bytes_read / ((header.datachunksize +
251 sizeof(struct wav_header)) / 100)));
252 rb->lcd_puts(0, 6, tstr);
253 rb->lcd_update();
255 /* allow user to abort */
256 if(ACTION_KBD_ABORT == rb->get_action(CONTEXT_KEYBOARD,TIMEOUT_NOBLOCK))
258 rb->splash(HZ*2, "ABORTED");
259 rb->close (file);
260 return -1;
264 rb->lcd_puts(0, 6, "Searching for peaks... done");
265 rb->lcd_update();
267 rb->close (file);
269 return 0;
272 int displaypeaks(void)
274 register int x = 0;
275 register int lymin = INT_MAX;
276 register int lymax = INT_MIN;
277 register int rymin = INT_MAX;
278 register int rymax = INT_MIN;
279 register unsigned int peakcount = 0;
280 struct peakstruct* peak = (struct peakstruct*)audiobuf + leftmargin;
282 #if LCD_DEPTH > 1
283 unsigned org_forecolor = rb->lcd_get_foreground();
284 rb->lcd_set_foreground(LCD_LIGHTGRAY);
285 #endif
287 if(!zoomlevel) zoomlevel = 1;
288 ppp = (mempeakcount / LCD_WIDTH) / zoomlevel; /* peaks per pixel */
290 rb->lcd_clear_display();
292 rb->lcd_drawline(0, LEFTZERO - (0x8000 / YSCALE), LCD_WIDTH-1,
293 LEFTZERO - (0x8000 / YSCALE));
294 rb->lcd_drawline(0, LEFTZERO, LCD_WIDTH-1, LEFTZERO);
295 rb->lcd_drawline(0, LEFTZERO + (0x8000 / YSCALE), LCD_WIDTH-1,
296 LEFTZERO + (0x8000 / YSCALE));
297 rb->lcd_drawline(0, RIGHTZERO - (0x8000 / YSCALE), LCD_WIDTH-1,
298 RIGHTZERO - (0x8000 / YSCALE));
299 rb->lcd_drawline(0, RIGHTZERO, LCD_WIDTH-1, RIGHTZERO);
300 rb->lcd_drawline(0, RIGHTZERO + (0x8000 / YSCALE), LCD_WIDTH-1,
301 RIGHTZERO + (0x8000 / YSCALE));
303 #if LCD_DEPTH > 1
304 rb->lcd_set_foreground(LCD_BLACK);
305 #endif
307 /* draw zoombar */
308 rb->lcd_drawline(leftmargin / (mempeakcount / LCD_WIDTH), LCD_HEIGHT / 2,
309 (leftmargin / (mempeakcount / LCD_WIDTH)) +
310 (LCD_WIDTH / zoomlevel),
311 LCD_HEIGHT / 2);
313 while((x < LCD_WIDTH) && (peakcount < mempeakcount))
315 if(peak->lmin < lymin)
316 lymin = peak->lmin;
317 if(peak->lmax > lymax)
318 lymax = peak->lmax;
319 if(peak->rmin < rymin)
320 rymin = peak->rmin;
321 if(peak->rmax > rymax)
322 rymax = peak->rmax;
323 peak++;
324 if(0 == (peakcount % ppp))
326 /* drawing time */
327 rb->lcd_drawline(x, LEFTZERO - (lymax / YSCALE), x,
328 LEFTZERO - (lymin / YSCALE));
329 rb->lcd_drawline(x, RIGHTZERO - (rymax / YSCALE), x,
330 RIGHTZERO - (rymin / YSCALE));
331 lymin = INT_MAX;
332 lymax = INT_MIN;
333 rymin = INT_MAX;
334 rymax = INT_MIN;
335 x++;
336 rb->lcd_update();
338 peakcount++;
341 #if LCD_DEPTH > 1
342 rb->lcd_set_foreground(org_forecolor);
343 #endif
345 return 0;
348 void show_help(void)
350 rb->lcd_clear_display();
351 rb->lcd_puts(0, 0, "WAVVIEW USAGE:");
352 rb->lcd_puts(0, 2, "up/down: zoom out/in");
353 rb->lcd_puts(0, 3, "left/right: pan left/right");
354 rb->lcd_puts(0, 4, "select: refresh/continue");
355 rb->lcd_puts(0, 5, "stop/off: quit");
356 rb->lcd_update();
359 enum plugin_status plugin_start(struct plugin_api* api, void *parameter)
361 unsigned int quit = 0;
362 unsigned int action = 0;
363 unsigned int dodisplay = 1;
364 rb = api;
365 int retval;
367 if (!parameter)
368 return PLUGIN_ERROR;
370 audiobuf = rb->plugin_get_audio_buffer((size_t *)&audiobuflen);
372 if (!audiobuf)
374 rb->splash(HZ*2, "unable to get audio buffer!");
375 return PLUGIN_ERROR;
378 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
379 rb->cpu_boost(true);
380 #endif
382 retval = readwavpeaks(parameter); /* read WAV file and create peaks array */
384 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
385 rb->cpu_boost(false);
386 #endif
388 if(retval)
389 return 0;
391 /* press any key to continue */
392 while(1)
394 retval = rb->get_action(CONTEXT_KEYBOARD,TIMEOUT_BLOCK);
395 if(ACTION_KBD_ABORT == retval)
396 return 0;
397 else if(ACTION_KBD_SELECT == retval)
398 break;
401 /* start with the overview */
402 zoomlevel = 1;
403 leftmargin = 0;
405 while(!quit)
407 if(!center)
408 center = mempeakcount / 2;
409 if(zoomlevel <= 1)
411 zoomlevel = 1;
412 leftmargin = 0;
414 else
416 if(center < (mempeakcount / (zoomlevel * 2)))
417 center = mempeakcount / (zoomlevel * 2);
418 if(center > (((zoomlevel * 2) - 1) * (mempeakcount /
419 (zoomlevel * 2))))
420 center = ((zoomlevel * 2) - 1) * (mempeakcount /
421 (zoomlevel * 2));
422 leftmargin = center - (mempeakcount / (zoomlevel * 2));
425 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
426 rb->cpu_boost(true);
427 #endif
428 if(dodisplay)
429 displaypeaks();
430 dodisplay = 1;
432 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
433 rb->cpu_boost(false);
434 #endif
436 action = rb->get_action(CONTEXT_KEYBOARD, TIMEOUT_BLOCK);
437 switch(action)
439 case ACTION_KBD_UP:
440 /* zoom out */
441 if(zoomlevel > 1)
442 zoomlevel /= 2;
443 rb->splash(HZ/2, "ZOOM: %dx",(int)zoomlevel);
444 break;
445 case ACTION_KBD_DOWN:
446 if(zoomlevel < (mempeakcount / LCD_WIDTH / 2))
447 zoomlevel *= 2;
448 rb->splash(HZ/2, "ZOOM: %dx",(int)zoomlevel);
449 break;
450 case ACTION_KBD_LEFT:
451 center -= 10 * (mempeakcount / LCD_WIDTH) / zoomlevel;
452 break;
453 case ACTION_KBD_RIGHT:
454 center += 10 * (mempeakcount / LCD_WIDTH) / zoomlevel;
455 break;
456 case ACTION_KBD_ABORT:
457 quit = 1;
458 break;
459 case ACTION_KBD_SELECT:
460 /* refresh */
461 break;
462 case ACTION_KBD_PAGE_FLIP:
463 /* menu key shows help */
464 show_help();
465 while(1)
467 retval = rb->get_action(CONTEXT_KEYBOARD,TIMEOUT_BLOCK);
468 if((ACTION_KBD_SELECT == retval) ||
469 (ACTION_KBD_ABORT == retval))
470 break;
472 break;
473 default:
474 /* eat it */
475 dodisplay = 0;
476 break;
480 return 0;