1 /**
2 * This file is a part of the Cairo-Dock project
3 *
4 * Copyright : (C) see the 'copyright' file.
5 * E-mail    : see the 'copyright' file.
6 *
7 * based on 'aplay' from Jaroslav Kysela <perex@perex.cz>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 3
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 */
21 
22 #include <stdlib.h>
23 #if !defined(__FreeBSD__) && !defined(__DragonFly__)
24 #include <endian.h>
25 #include <byteswap.h>
26 #else
27 #include <sys/endian.h>
28 #endif
29 
30 #include "applet-struct.h"
31 #include "applet-sound.h"
32 
33 
34 #if __BYTE_ORDER == __LITTLE_ENDIAN
35 #define COMPOSE_ID(a,b,c,d)   ((a) | ((b)<<8) | ((c)<<16) | ((d)<<24))
36 #define LE_SHORT(v)           (v)
37 #define LE_INT(v)       (v)
38 #define BE_SHORT(v)           bswap_16(v)
39 #define BE_INT(v)       bswap_32(v)
40 #elif __BYTE_ORDER == __BIG_ENDIAN
41 #define COMPOSE_ID(a,b,c,d)   ((d) | ((c)<<8) | ((b)<<16) | ((a)<<24))
42 #define LE_SHORT(v)           bswap_16(v)
43 #define LE_INT(v)       bswap_32(v)
44 #define BE_SHORT(v)           (v)
45 #define BE_INT(v)       (v)
46 #else
47 #error "Wrong endian"
48 #endif
49 
50 #define WAV_RIFF        COMPOSE_ID('R','I','F','F')
51 #define WAV_WAVE        COMPOSE_ID('W','A','V','E')
52 #define WAV_FMT               COMPOSE_ID('f','m','t',' ')
53 #define WAV_DATA        COMPOSE_ID('d','a','t','a')
54 #define WAV_PCM_CODE          1
55 
56 typedef struct {
57 	guint magic;            /* 'RIFF' */
58 	guint length;           /* filelen */
59 	guint type;       /* 'WAVE' */
60 } WaveHeader;
61 
62 typedef struct {
63 	gushort format;         /* should be 1 for PCM-code */
64 	gushort modus;          /* 1 Mono, 2 Stereo */
65 	guint sample_fq;  /* frequence of sample */
66 	guint byte_p_sec;  // number of bytes/sec (Frequence * BytePerFrame)
67 	gushort byte_p_spl;     // bytes per frame (NbChannels * BitsPerSample/8)
68 	gushort bit_p_spl;      // bits per sample (8, 16, 24, 32)
69 } WaveFmtBody;
70 
71 typedef struct {
72 	guint type;       /* 'data' */
73 	guint length;           /* samplecount */
74 } WaveChunkHeader;
75 
76 #define DEFAULT_FORMAT        SND_PCM_FORMAT_U8
77 #define DEFAULT_SPEED         8000
78 
79 
_parse_header(CDSoundFile * pSoundFile)80 static gboolean _parse_header (CDSoundFile *pSoundFile)
81 {
82 	gchar *end = pSoundFile->buffer + pSoundFile->length;
83 	// check the header
84 	if (pSoundFile->length < sizeof(WaveHeader) + sizeof (WaveChunkHeader) + sizeof (WaveFmtBody))  // minimum size.
85 		return FALSE;
86 	gchar *ptr = pSoundFile->buffer;
87 	WaveHeader *h = (WaveHeader *)ptr;
88 	if (h->magic != WAV_RIFF || h->type != WAV_WAVE)
89 		return FALSE;
90 	ptr += sizeof(WaveHeader);
91 
92      // check the audio format
93 	guint type, len;
94 	WaveChunkHeader *c;
95 	do
96 	{
97 		c = (WaveChunkHeader*) ptr;
98 		type = c->type;
99 		len = LE_INT(c->length);
100 		len += len % 2;
101 		if (type == WAV_FMT)
102 			break;
103 		ptr += sizeof (WaveChunkHeader) + len;
104 	} while (ptr < end);
105 	g_return_val_if_fail  (ptr < end, FALSE);
106 
107 	ptr += sizeof (WaveChunkHeader);
108 	WaveFmtBody *f = (WaveFmtBody*) ptr;
109 
110 	if (len < sizeof(WaveFmtBody))
111 	{
112 		cd_warning ("unknown length of 'fmt ' chunk (read %u, should be %u at least)",
113 		len, (guint)sizeof(WaveFmtBody));
114 		return FALSE;
115 	}
116 
117 	gint format = LE_SHORT(f->format);
118 	gint channels = LE_SHORT(f->modus);
119 	gint rate = LE_INT(f->sample_fq);  // frames/s
120 	gint bytePerSec = LE_INT(f->byte_p_sec);
121 	gint bytePerBloc = LE_SHORT(f->byte_p_spl);
122 	gint bitsPerSample = LE_SHORT(f->bit_p_spl);
123 
124 	if (format != WAV_PCM_CODE)
125 	{
126 		cd_warning ("can't play not PCM-coded WAVE-files");
127 		return FALSE;
128 	}
129 	if (LE_SHORT(f->modus) < 1)
130 	{
131 		cd_warning ("can't play WAVE-files with %d tracks", LE_SHORT(f->modus));
132 		return FALSE;
133 	}
134 	pSoundFile->channels = LE_SHORT(f->modus);
135 
136       switch (LE_SHORT(f->bit_p_spl)) {
137       case 8:
138             if (pSoundFile->format != DEFAULT_FORMAT &&
139                 pSoundFile->format != SND_PCM_FORMAT_U8)
140                   cd_warning ("Warning: format is changed to U8\n");
141             pSoundFile->format = SND_PCM_FORMAT_U8;
142             break;
143       case 16:
144             if (pSoundFile->format != DEFAULT_FORMAT &&
145                 pSoundFile->format != SND_PCM_FORMAT_S16_LE)
146                   cd_warning ("Warning: format is changed to S16_LE\n");
147             pSoundFile->format = SND_PCM_FORMAT_S16_LE;
148             break;
149       case 24:
150             switch (LE_SHORT(f->byte_p_spl) / pSoundFile->channels) {
151             case 3:
152                   if (pSoundFile->format != DEFAULT_FORMAT &&
153                       pSoundFile->format != SND_PCM_FORMAT_S24_3LE)
154                         cd_warning ("Warning: format is changed to S24_3LE\n");
155                   pSoundFile->format = SND_PCM_FORMAT_S24_3LE;
156                   break;
157             case 4:
158                   if (pSoundFile->format != DEFAULT_FORMAT &&
159                       pSoundFile->format != SND_PCM_FORMAT_S24_LE)
160                         cd_warning ("Warning: format is changed to S24_LE\n");
161                   pSoundFile->format = SND_PCM_FORMAT_S24_LE;
162                   break;
163             default:
164                   cd_warning (" can't play WAVE-files with sample %d bits in %d bytes wide (%d channels)",
165                         LE_SHORT(f->bit_p_spl), LE_SHORT(f->byte_p_spl), pSoundFile->channels);
166                   return FALSE;
167             }
168             break;
169       case 32:
170             pSoundFile->format = SND_PCM_FORMAT_S32_LE;
171             break;
172       default:
173             cd_warning (" can't play WAVE-files with sample %d bits wide",
174                   LE_SHORT(f->bit_p_spl));
175             return FALSE;
176       }
177 
178 	cd_debug ("rate: %d; channels: %d; BytePerSec: %d; BytePerBloc: %d; BitsPerSample: %d", rate, channels, bytePerSec, bytePerBloc, bitsPerSample);
179 	pSoundFile->rate = LE_INT(rate);
180 
181 	ptr += len;
182 	// check the audio data
183 	while (ptr < end)
184 	{
185 		c = (WaveChunkHeader*) ptr;
186 		type = c->type;
187 		len = LE_INT(c->length);
188 		len += len % 2;
189 		if (type == WAV_DATA)
190 			break;
191 		ptr += sizeof (WaveChunkHeader) + len;
192 	};
193 	g_return_val_if_fail  (ptr < end, FALSE);
194 
195 	cd_debug ("len = %d; file size = %d", len, pSoundFile->length);
196 	pSoundFile->size = len;
197 	pSoundFile->iNbFrames = pSoundFile->size / bytePerBloc;
198 	pSoundFile->iBitsPerSample = bitsPerSample;
199 	pSoundFile->fTimelength = (double)pSoundFile->size / bytePerSec;
200 	ptr += sizeof (WaveChunkHeader);
201 	pSoundFile->data = ptr;
202 
203 	return TRUE;
204 }
205 
206 typedef struct {
207 	char a;
208 	char b;
209 	char c;
210 } int24;
211 
cd_sound_load_sound_file(const gchar * cFilePath)212 CDSoundFile *cd_sound_load_sound_file (const gchar *cFilePath)
213 {
214 	// read the file at once
215 	gchar *buffer = NULL;
216 	gsize length = 0;
217 	g_file_get_contents (cFilePath,
218 		&buffer,
219 		&length,
220 		NULL);
221 	g_return_val_if_fail (buffer != NULL, NULL);
222 
223 	CDSoundFile *pSoundFile = g_new0 (CDSoundFile, 1);
224 	pSoundFile->buffer = buffer;
225 	pSoundFile->length = length;
226 	pSoundFile->format = DEFAULT_FORMAT;
227 	pSoundFile->rate = DEFAULT_SPEED;
228 	pSoundFile->channels = 1;
229 
230 	// parse the header to get the info
231 	_parse_header (pSoundFile);
232 
233 	// apply volume
234 	if (myConfig.fVolume < .99)
235 	{
236 		int iNbSamples = pSoundFile->size * 8 / pSoundFile->iBitsPerSample;
237 		int i;
238 		/**gchar *data = pSoundFile->data;
239 		cd_debug ("%d samples", iNbSamples);
240 		gint32 sample;
241 		gint spl;
242 		gint32 *psample;
243 		for (i = 0; i < iNbSamples-3; i++)
244 		{
245 			psample = (gint32 *)&data[i*pSoundFile->iBitsPerSample/8];
246 			sample = *psample;
247 			spl = sample & (0xFFFFFFFF >> (32 - pSoundFile->iBitsPerSample));
248 			spl *= myConfig.fVolume;
249 			*psample = (sample & (0xFFFFFFFF << pSoundFile->iBitsPerSample)) + spl;
250 		}*/
251 
252 		// .1 -> /2
253 		// (1 - vol) * 10 + 1
254 		double v = (1 - myConfig.fVolume) * 10 + 1;
255 		switch (pSoundFile->iBitsPerSample)
256 		{
257 			case 8:
258 			{
259 				char *data = (char*)pSoundFile->data;
260 				char *psample;
261 				for (i = 0; i < iNbSamples; i++)
262 				{
263 					psample = &data[i];
264 					*psample /= v;
265 				}
266 			}
267 			break;
268 			case 16:
269 			{
270 				gint16 *data = (gint16*)pSoundFile->data;
271 				gint16 *psample;
272 				for (i = 0; i < iNbSamples; i++)
273 				{
274 					psample = &data[i];
275 					*psample /= v;
276 				}
277 			}
278 			break;
279 			case 24:
280 			{
281 				int24 *data = (int24*)pSoundFile->data;
282 				int24 sample24, *psample;
283 				gint32 sample;
284 				for (i = 0; i < iNbSamples; i++)
285 				{
286 					psample = &data[i];
287 					sample24 = *psample;
288 					sample = (sample24.a<<16) + (sample24.b<<8) + sample24.c;
289 					sample /= v;
290 					sample24.a = (sample & 0x00FF0000) >> 16;
291 					sample24.b = (sample & 0x0000FF00) >> 8;
292 					sample24.c = sample & 0x000000FF;
293 					*psample = sample24;
294 				}
295 			}
296 			break;
297 			case 32:
298 			{
299 				gint32 *data = (gint32*)pSoundFile->data;
300 				gint32 *psample;
301 				for (i = 0; i < iNbSamples; i++)
302 				{
303 					psample = &data[i];
304 					*psample /= v;
305 				}
306 			}
307 			break;
308 			default:
309 			break;
310 		}
311 	}
312 
313 	return pSoundFile;
314 }
315 
316 
317 
set_params(snd_pcm_t * handle,CDSoundFile * pSoundFile)318 static gboolean set_params (snd_pcm_t *handle, CDSoundFile *pSoundFile)
319 {
320 	snd_pcm_sw_params_t *swparams;
321 	snd_pcm_uframes_t buffer_size;
322 	int err;
323 	size_t n;
324 	unsigned int rate;
325 	snd_pcm_uframes_t start_threshold, stop_threshold;
326 	static unsigned period_time = 0;
327 	static unsigned buffer_time = 0;
328 	static snd_pcm_uframes_t chunk_size = 0;
329 	static size_t bits_per_sample, bits_per_frame;
330 
331 	// initiate hardware params
332 	snd_pcm_hw_params_t *params;
333 	snd_pcm_hw_params_alloca (&params);
334 	err = snd_pcm_hw_params_any(handle, params);
335 	if (err < 0)
336 	{
337 		cd_warning ("Broken configuration for this PCM: no configurations available");
338 		return FALSE;
339 	}
340 	err = snd_pcm_hw_params_set_access (handle, params,
341 		SND_PCM_ACCESS_RW_INTERLEAVED);
342 	if (err < 0)
343 	{
344 		cd_warning ("Access type not available");
345 		return FALSE;
346 	}
347 
348 	// set format
349 	err = snd_pcm_hw_params_set_format(handle, params, pSoundFile->format);
350 	if (err < 0)
351 	{
352 		cd_warning ("Sample format non available");
353 		return FALSE;
354 	}
355 	// set channels number
356 	err = snd_pcm_hw_params_set_channels(handle, params, pSoundFile->channels);
357 	if (err < 0)
358 	{
359 		cd_warning ("Channels count non available");
360 		return FALSE;
361 	}
362 
363 	// set rate
364 	rate = pSoundFile->rate;
365 	err = snd_pcm_hw_params_set_rate_near(handle, params, &rate, 0);
366 	assert(err >= 0);
367 	if ((float)rate * 1.05 < pSoundFile->rate || (float)rate * 0.95 > pSoundFile->rate)
368 	{
369 		cd_warning ("rate is not accurate (requested = %iHz, got = %iHz)\n", pSoundFile->rate, rate);
370 	}
371 	pSoundFile->rate = rate;
372 
373 	// set buffer and period time
374 	err = snd_pcm_hw_params_get_buffer_time_max(params,
375 		&buffer_time, 0);
376 	assert(err >= 0);
377 	if (buffer_time > 500000)
378 		buffer_time = 500000;
379 
380 	period_time = buffer_time / 4;
381 
382 	err = snd_pcm_hw_params_set_period_time_near(handle, params,
383 	&period_time, 0);
384 	assert(err >= 0);
385 	err = snd_pcm_hw_params_set_buffer_time_near(handle, params,
386 	&buffer_time, 0);
387 	assert(err >= 0);
388 
389 	// now set paramas to the handle
390 	err = snd_pcm_hw_params(handle, params);
391 	if (err < 0)
392 	{
393 		cd_warning ("Unable to install hw params:");
394 		return FALSE;
395 	}
396 
397 	snd_pcm_hw_params_get_period_size(params, &chunk_size, 0);
398 	snd_pcm_hw_params_get_buffer_size(params, &buffer_size);
399 	if (chunk_size == buffer_size) {
400 		cd_warning ("Can't use period equal to buffer size (%lu == %lu)",
401 		chunk_size, buffer_size);
402 		return FALSE;
403 	}
404 
405 	// software params
406 	snd_pcm_sw_params_alloca(&swparams);
407 	snd_pcm_sw_params_current(handle, swparams);
408 	n = chunk_size;
409 	err = snd_pcm_sw_params_set_avail_min(handle, swparams, n);
410 
411 	/* round up to closest transfer boundary */
412 	n = buffer_size;
413 	start_threshold = n;
414 	err = snd_pcm_sw_params_set_start_threshold(handle, swparams, start_threshold);
415 	assert(err >= 0);
416 	stop_threshold = buffer_size;
417 	err = snd_pcm_sw_params_set_stop_threshold(handle, swparams, stop_threshold);
418 	assert(err >= 0);
419 
420 	if (snd_pcm_sw_params(handle, swparams) < 0)
421 	{
422 		cd_warning ("unable to install sw params:");
423 		return FALSE;
424 	}
425 
426 	bits_per_sample = snd_pcm_format_physical_width(pSoundFile->format);
427 	bits_per_frame = bits_per_sample * pSoundFile->channels;
428 
429 	cd_debug ("bits_per_frame: %d; rate: %d", bits_per_frame, pSoundFile->rate);
430 	cd_debug ("chunk_size: %d; buffer_size: %d", chunk_size, buffer_size);
431 	pSoundFile->iNbFrames = (pSoundFile->size * 8) / bits_per_frame;
432 	return TRUE;
433 }
434 
435 
_free_shared_memory(CDSharedMemory * pSharedMemory)436 static void _free_shared_memory (CDSharedMemory *pSharedMemory)
437 {
438 	// close the handle
439 	if (pSharedMemory->handle)
440 		snd_pcm_close (pSharedMemory->handle);
441 
442 	// remove the task from the list of tasks
443 	myData.pTasks = g_list_remove (myData.pTasks, pSharedMemory->pTask);
444 	g_free (pSharedMemory);
445 }
446 
_finish_play_sound(CDSharedMemory * pSharedMemory)447 static gboolean _finish_play_sound (CDSharedMemory *pSharedMemory)
448 {
449 	gldi_task_discard (pSharedMemory->pTask);
450 	return FALSE;
451 }
452 
_play_sound_async(CDSharedMemory * pSharedMemory)453 static void _play_sound_async (CDSharedMemory *pSharedMemory)
454 {
455 	CDSoundFile *pSoundFile = pSharedMemory->pSoundFile;
456 	g_return_if_fail (pSoundFile != NULL);
457 	// open the device
458 	snd_pcm_t *handle = NULL;
459 	int err = snd_pcm_open (&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);  // open-mode = 0
460 	if (err < 0)
461 	{
462 		cd_warning ("audio open error: %s", snd_strerror(err));
463 		return;
464 	}
465 
466 	// set params
467 	if ( !set_params (handle, pSoundFile))
468 		return;
469 
470 	// play data
471 	ssize_t r;
472 	int n = pSoundFile->iNbFrames;
473 	gchar *buffer = pSoundFile->data;
474 	while (n > 0)
475 	{
476 		r = snd_pcm_writei (handle, buffer, n);
477 		if (r == -EAGAIN || (r >= 0 && r < n))
478 		{
479 			snd_pcm_wait (handle, 100);
480 		}
481 		else if (r == -EPIPE)  // an underrun occurred
482 		{
483 			cd_debug ("underrun");
484 			snd_pcm_status_t *status;
485 			int res;
486 			snd_pcm_status_alloca(&status);
487 			if ((res = snd_pcm_status(handle, status))<0)
488 			{
489 				cd_warning ("status error: %s", snd_strerror(res));
490 				return;
491 			}
492 			snd_pcm_state_t state = snd_pcm_status_get_state(status);
493 
494 			if (state == SND_PCM_STATE_XRUN)
495 			{
496 				if ((res = snd_pcm_prepare (handle)) < 0)
497 				{
498 					cd_warning ("prepare error: %s", snd_strerror(res));
499 					return;
500 				}
501 				continue;  // ok, data should be accepted again
502 			}
503 			else if (state != SND_PCM_STATE_DRAINING)
504 			{
505 				cd_warning ("read/write error, state = %s", snd_pcm_state_name(state));
506 				return;
507 			}
508 		}
509 		else if (r == -ESTRPIPE)  // a suspend event occurred (stream is suspended and waiting for an application recovery)
510 		{
511 			cd_debug ("suspend");
512 			int res;
513 			while ((res = snd_pcm_resume (handle)) == -EAGAIN)
514 				sleep(1);	// wait until suspend flag is released
515 			if (res < 0)  // failed, restart stream
516 			{
517 				if ((res = snd_pcm_prepare(handle)) < 0)
518 				{
519 					cd_warning ("suspend: prepare error: %s", snd_strerror(res));
520 					return ;
521 				}
522 			}
523 		}
524 		else if (r < 0)
525 		{
526 			cd_warning ("write error: %s", snd_strerror(r));
527 			return ;
528 		}
529 		if (r > 0)
530 		{
531 			n -= r;
532 			buffer += r;
533 		}
534 	}
535 
536 	pSharedMemory->handle = handle;
537 	snd_pcm_drain (handle);  // flush data
538 }
539 
cd_sound_play_sound(CDSoundFile * pSoundFile)540 void cd_sound_play_sound (CDSoundFile *pSoundFile)
541 {
542 	CDSharedMemory *pSharedMemory = g_new0 (CDSharedMemory, 1);
543 	pSharedMemory->pSoundFile = pSoundFile;  // the sound file is loaded before, and will stay alive until the task is stopped.
544 	GldiTask *pTask = gldi_task_new_full (0,  // 1 shot task.
545 		(GldiGetDataAsyncFunc) _play_sound_async,
546 		(GldiUpdateSyncFunc) _finish_play_sound,
547 		(GFreeFunc) _free_shared_memory,
548 		pSharedMemory);
549 	pSharedMemory->pTask = pTask;
550 	myData.pTasks = g_list_prepend (myData.pTasks, pTask);
551 
552 	gldi_task_launch (pTask);
553 }
554 
555 
cd_sound_free_sound_file(CDSoundFile * pSoundFile)556 void cd_sound_free_sound_file (CDSoundFile *pSoundFile)
557 {
558 	if (!pSoundFile)
559 		return;
560 	g_free (pSoundFile->buffer);  // 'data' points inside 'buffer'.
561 	g_free (pSoundFile);
562 
563 }
564 
cd_sound_free_current_tasks(void)565 void cd_sound_free_current_tasks (void)
566 {
567 	GldiTask *pTask;
568 	GList *t = myData.pTasks, *next_t;
569 	while (t != NULL)
570 	{
571 		next_t = t->next;
572 		pTask = t->data;
573 		gldi_task_free (pTask);  // will remove it from the list.
574 		t = next_t;
575 	}  // at this point the list is empty and myData.pTasks is NULL.
576 }
577