A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 469 lines 13 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2007 Peter D'Hoye 11 * 12 * This program is free software; you can redistribute it and/or 13 * modify it under the terms of the GNU General Public License 14 * as published by the Free Software Foundation; either version 2 15 * of the License, or (at your option) any later version. 16 * 17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 * KIND, either express or implied. 19 * 20 ****************************************************************************/ 21#include "plugin.h" 22 23 24 25/* temp byte buffer */ 26uint8_t samples[10 * 1024]; /* read 10KB at the time */ 27 28static struct wav_header 29{ 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; 44 45#define WAV_HEADER_FORMAT "4L44LSSLLSS4L" 46 47struct peakstruct 48{ 49 int lmin; 50 int lmax; 51 int rmin; 52 int rmax; 53}; 54 55/* TO DO: limit used LCD height, so the waveform isn't streched vertically? */ 56 57#define LEFTZERO (LCD_HEIGHT / 4) 58#define RIGHTZERO (3 * (LCD_HEIGHT / 4)) 59#define YSCALE ((0x8000 / (LCD_HEIGHT / 4)) + 1) 60 61/* global vars */ 62static char *audiobuf; 63static size_t audiobuflen; 64static uint32_t mempeakcount = 0; 65static uint32_t filepeakcount = 0; 66static uint32_t fppmp = 0; /* file peaks per mem peaks */ 67static uint32_t zoomlevel = 1; 68static uint32_t leftmargin = 0; 69static uint32_t center = 0; 70static uint32_t ppp = 1; 71 72/* helper function copied from libwavpack bits.c */ 73static void little_endian_to_native (void *data, char *format) 74{ 75 unsigned char *cp = (unsigned char *) data; 76 77 while (*format) { 78 switch (*format) { 79 case 'L': 80 *(long *)cp = letoh32(*(long *)cp); 81 cp += 4; 82 break; 83 84 case 'S': 85 *(short *)cp = letoh16(*(short *)cp); 86 cp += 2; 87 break; 88 89 default: 90 if (*format >= '0' && *format <= '9') 91 cp += *format - '0'; 92 93 break; 94 } 95 format++; 96 } 97} 98/* --- */ 99 100/* read WAV file 101 display some info about it 102 store peak info in aufiobuf for display routine */ 103static int readwavpeaks(const char *filename) 104{ 105 register uint32_t bytes_read; 106 register uint32_t fppmp_count; 107 register int16_t sampleval; 108 register uint16_t* sampleshort = NULL; 109 110 int file; 111 uint32_t total_bytes_read = 0; 112 int hours; 113 int minutes; 114 int seconds; 115 uint32_t peakcount = 0; 116 struct peakstruct* peak = NULL; 117 118 if(rb->strcasecmp (filename + rb->strlen (filename) - 3, "wav")) 119 { 120 rb->splash(HZ*2, "Only for wav files!"); 121 return -1; 122 } 123 124 file = rb->open(filename, O_RDONLY); 125 126 if(file < 0) 127 { 128 rb->splash(HZ*2, "Could not open file!"); 129 return -1; 130 } 131 132 if(rb->read(file, &header, sizeof (header)) != sizeof (header)) 133 { 134 rb->splash(HZ*2, "Could not read file!"); 135 rb->close (file); 136 return -1; 137 } 138 139 total_bytes_read += sizeof (header); 140 little_endian_to_native(&header, WAV_HEADER_FORMAT); 141 142 if (rb->strncmp(header.chunkid, "RIFF", 4) || 143 rb->strncmp(header.formatchunkid, "fmt ", 4) || 144 rb->strncmp(header.datachunkid, "data", 4) || 145 (header.bitspersample != 16) || 146 header.audioformat != 1) 147 { 148 rb->splash(HZ*2, "Incompatible wav file!"); 149 rb->close (file); 150 return -1; 151 } 152 153 rb->lcd_clear_display(); 154 rb->lcd_puts(0, 0, "Viewing file:"); 155 rb->lcd_puts_scroll(0, 1, (unsigned char *)filename); 156 rb->lcd_update(); 157 158 rb->lcd_putsf(0, 2, "Channels: %s", 159 header.numchannels == 1 ? "mono" : "stereo"); 160 rb->lcd_putsf(0, 3, "Bits/sample: %d", header.bitspersample); 161 rb->lcd_putsf(0, 4, "Samplerate: %"PRIu32" Hz", header.samplerate); 162 163 seconds = header.datachunksize / header.byterate; 164 hours = seconds / 3600; 165 seconds -= hours * 3600; 166 minutes = seconds / 60; 167 seconds -= minutes * 60; 168 169 rb->lcd_putsf(0, 5, "Length (hh:mm:ss): %02d:%02d:%02d", hours, 170 minutes, 171 seconds); 172 rb->lcd_puts(0, 6, "Searching for peaks... "); 173 rb->lcd_update(); 174 175 /* calculate room for peaks */ 176 filepeakcount = header.datachunksize / 177 (header.numchannels * (header.bitspersample / 8)); 178 mempeakcount = audiobuflen / sizeof(struct peakstruct); 179 fppmp = (filepeakcount / mempeakcount) + 1; 180 peak = (struct peakstruct*)audiobuf; 181 182 fppmp_count = fppmp; 183 mempeakcount = 0; 184 while(total_bytes_read < (header.datachunksize + 185 sizeof(struct wav_header))) 186 { 187 bytes_read = rb->read(file, &samples, sizeof(samples)); 188 total_bytes_read += bytes_read; 189 190 if(0 == bytes_read) 191 { 192 rb->splash(HZ*2, "File read error!"); 193 rb->close (file); 194 return -1; 195 } 196 if(((bytes_read/4)*4) != bytes_read) 197 { 198 rb->splashf(HZ*2, "bytes_read/*4 err: %ld",(long int)bytes_read); 199 rb->close (file); 200 return -1; 201 } 202 203 sampleshort = (int16_t*)samples; 204 sampleval = letoh16(*sampleshort); 205 peak->lmin = sampleval; 206 peak->lmax = sampleval; 207 sampleval = letoh16(*(sampleshort+1)); 208 peak->rmin = sampleval; 209 peak->rmax = sampleval; 210 211 while(bytes_read) 212 { 213 sampleval = letoh16(*sampleshort++); 214 if(sampleval < peak->lmin) 215 peak->lmin = sampleval; 216 else if (sampleval > peak->lmax) 217 peak->lmax = sampleval; 218 219 sampleval = letoh16(*sampleshort++); 220 if(sampleval < peak->rmin) 221 peak->rmin = sampleval; 222 else if (sampleval > peak->rmax) 223 peak->rmax = sampleval; 224 225 bytes_read -= 4; 226 peakcount++; 227 fppmp_count--; 228 if(!fppmp_count) 229 { 230 peak++; 231 mempeakcount++; 232 fppmp_count = fppmp; 233 sampleval = letoh16(*sampleshort); 234 peak->lmin = sampleval; 235 peak->lmax = sampleval; 236 sampleval = letoh16(*(sampleshort+1)); 237 peak->rmin = sampleval; 238 peak->rmax = sampleval; 239 } 240 } 241 242 /* update progress */ 243 rb->lcd_putsf(0, 6, "Searching for peaks... %"PRIu32"%%", 244 (total_bytes_read / ((header.datachunksize + 245 sizeof(struct wav_header)) / 100))); 246 rb->lcd_update(); 247 248 /* allow user to abort */ 249 if(ACTION_KBD_ABORT == rb->get_action(CONTEXT_KEYBOARD,TIMEOUT_NOBLOCK)) 250 { 251 rb->splash(HZ*2, "ABORTED"); 252 rb->close (file); 253 return -1; 254 } 255 } 256 257 rb->lcd_puts(0, 6, "Searching for peaks... done"); 258 rb->lcd_update(); 259 260 rb->close (file); 261 262 return 0; 263} 264 265static int displaypeaks(void) 266{ 267 register int x = 0; 268 register int lymin = INT_MAX; 269 register int lymax = INT_MIN; 270 register int rymin = INT_MAX; 271 register int rymax = INT_MIN; 272 register unsigned int peakcount = 0; 273 struct peakstruct* peak = (struct peakstruct*)audiobuf + leftmargin; 274 275#if LCD_DEPTH > 1 276 unsigned org_forecolor = rb->lcd_get_foreground(); 277 rb->lcd_set_foreground(LCD_LIGHTGRAY); 278#endif 279 280 if(!zoomlevel) zoomlevel = 1; 281 ppp = (mempeakcount / LCD_WIDTH) / zoomlevel; /* peaks per pixel */ 282 283 rb->lcd_clear_display(); 284 285 rb->lcd_hline(0, LCD_WIDTH-1, LEFTZERO - (0x8000 / YSCALE)); 286 rb->lcd_hline(0, LCD_WIDTH-1, LEFTZERO); 287 rb->lcd_hline(0, LCD_WIDTH-1, LEFTZERO + (0x8000 / YSCALE)); 288 rb->lcd_hline(0, LCD_WIDTH-1, RIGHTZERO - (0x8000 / YSCALE)); 289 rb->lcd_hline(0, LCD_WIDTH-1, RIGHTZERO); 290 rb->lcd_hline(0, LCD_WIDTH-1, RIGHTZERO + (0x8000 / YSCALE)); 291 292#if LCD_DEPTH > 1 293 rb->lcd_set_foreground(LCD_BLACK); 294#endif 295 296 /* draw zoombar */ 297 rb->lcd_hline(leftmargin / (mempeakcount / LCD_WIDTH), 298 (leftmargin / (mempeakcount / LCD_WIDTH)) + 299 (LCD_WIDTH / zoomlevel), 300 LCD_HEIGHT / 2); 301 302 while((x < LCD_WIDTH) && (peakcount < mempeakcount)) 303 { 304 if(peak->lmin < lymin) 305 lymin = peak->lmin; 306 if(peak->lmax > lymax) 307 lymax = peak->lmax; 308 if(peak->rmin < rymin) 309 rymin = peak->rmin; 310 if(peak->rmax > rymax) 311 rymax = peak->rmax; 312 peak++; 313 if(0 == (peakcount % ppp)) 314 { 315 /* drawing time */ 316 rb->lcd_vline(x, LEFTZERO - (lymax / YSCALE), 317 LEFTZERO - (lymin / YSCALE)); 318 rb->lcd_vline(x, RIGHTZERO - (rymax / YSCALE), 319 RIGHTZERO - (rymin / YSCALE)); 320 lymin = INT_MAX; 321 lymax = INT_MIN; 322 rymin = INT_MAX; 323 rymax = INT_MIN; 324 x++; 325 rb->lcd_update(); 326 } 327 peakcount++; 328 } 329 330#if LCD_DEPTH > 1 331 rb->lcd_set_foreground(org_forecolor); 332#endif 333 334 return 0; 335} 336 337static void show_help(void) 338{ 339 rb->lcd_clear_display(); 340 rb->lcd_puts(0, 0, "WAVVIEW USAGE:"); 341 rb->lcd_puts(0, 2, "up/down: zoom out/in"); 342 rb->lcd_puts(0, 3, "left/right: pan left/right"); 343 rb->lcd_puts(0, 4, "select: refresh/continue"); 344 rb->lcd_puts(0, 5, "stop/off: quit"); 345 rb->lcd_update(); 346} 347 348enum plugin_status plugin_start(const void *parameter) 349{ 350 unsigned int quit = 0; 351 unsigned int action = 0; 352 unsigned int dodisplay = 1; 353 int retval; 354 355 if (!parameter) 356 return PLUGIN_ERROR; 357 358 audiobuf = rb->plugin_get_audio_buffer(&audiobuflen); 359 360 if (!audiobuf) 361 { 362 rb->splash(HZ*2, "unable to get audio buffer!"); 363 return PLUGIN_ERROR; 364 } 365 366#ifdef HAVE_ADJUSTABLE_CPU_FREQ 367 rb->cpu_boost(true); 368#endif 369 370 retval = readwavpeaks(parameter); /* read WAV file and create peaks array */ 371 372#ifdef HAVE_ADJUSTABLE_CPU_FREQ 373 rb->cpu_boost(false); 374#endif 375 376 if(retval) 377 return 0; 378 379 /* press any key to continue */ 380 while(1) 381 { 382 retval = rb->get_action(CONTEXT_KEYBOARD,TIMEOUT_BLOCK); 383 if(ACTION_KBD_ABORT == retval) 384 return 0; 385 else if(ACTION_KBD_SELECT == retval) 386 break; 387 } 388 389 /* start with the overview */ 390 zoomlevel = 1; 391 leftmargin = 0; 392 393 while(!quit) 394 { 395 if(!center) 396 center = mempeakcount / 2; 397 if(zoomlevel <= 1) 398 { 399 zoomlevel = 1; 400 leftmargin = 0; 401 } 402 else 403 { 404 if(center < (mempeakcount / (zoomlevel * 2))) 405 center = mempeakcount / (zoomlevel * 2); 406 if(center > (((zoomlevel * 2) - 1) * (mempeakcount / 407 (zoomlevel * 2)))) 408 center = ((zoomlevel * 2) - 1) * (mempeakcount / 409 (zoomlevel * 2)); 410 leftmargin = center - (mempeakcount / (zoomlevel * 2)); 411 } 412 413#ifdef HAVE_ADJUSTABLE_CPU_FREQ 414 rb->cpu_boost(true); 415#endif 416 if(dodisplay) 417 displaypeaks(); 418 dodisplay = 1; 419 420#ifdef HAVE_ADJUSTABLE_CPU_FREQ 421 rb->cpu_boost(false); 422#endif 423 424 action = rb->get_action(CONTEXT_KEYBOARD, TIMEOUT_BLOCK); 425 switch(action) 426 { 427 case ACTION_KBD_UP: 428 /* zoom out */ 429 if(zoomlevel > 1) 430 zoomlevel /= 2; 431 rb->splashf(HZ/2, "ZOOM: %dx",(int)zoomlevel); 432 break; 433 case ACTION_KBD_DOWN: 434 if(zoomlevel < (mempeakcount / LCD_WIDTH / 2)) 435 zoomlevel *= 2; 436 rb->splashf(HZ/2, "ZOOM: %dx",(int)zoomlevel); 437 break; 438 case ACTION_KBD_LEFT: 439 center -= 10 * (mempeakcount / LCD_WIDTH) / zoomlevel; 440 break; 441 case ACTION_KBD_RIGHT: 442 center += 10 * (mempeakcount / LCD_WIDTH) / zoomlevel; 443 break; 444 case ACTION_KBD_ABORT: 445 quit = 1; 446 break; 447 case ACTION_KBD_SELECT: 448 /* refresh */ 449 break; 450 case ACTION_KBD_PAGE_FLIP: 451 /* menu key shows help */ 452 show_help(); 453 while(1) 454 { 455 retval = rb->get_action(CONTEXT_KEYBOARD,TIMEOUT_BLOCK); 456 if((ACTION_KBD_SELECT == retval) || 457 (ACTION_KBD_ABORT == retval)) 458 break; 459 } 460 break; 461 default: 462 /* eat it */ 463 dodisplay = 0; 464 break; 465 } 466 } 467 468 return 0; 469}