A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
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}