blob: 5f78901a56badfb2cc94abd6ea56735205f594a8 [file] [log] [blame]
Miika Pekkarinen20b38972005-07-13 12:48:22 +00001/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2005 by Miika Pekkarinen
11 *
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.
14 *
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
17 *
18 ****************************************************************************/
19
20#include <stdbool.h>
21#include <stdio.h>
22#include "config.h"
23#include "debug.h"
24#include "panic.h"
25#include <kernel.h>
26#include "pcmbuf.h"
27#include "pcm_playback.h"
28#include "logf.h"
29#ifndef SIMULATOR
30#include "cpu.h"
31#endif
32#include "system.h"
33#include <string.h>
34#include "buffer.h"
Miika Pekkarinenf090dc32005-07-21 11:44:00 +000035#include "settings.h"
36#include "audio.h"
Miika Pekkarinen159c52d2005-08-20 11:13:19 +000037#include "dsp.h"
Miika Pekkarinen20b38972005-07-13 12:48:22 +000038
Miika Pekkarinenf090dc32005-07-21 11:44:00 +000039#define CHUNK_SIZE PCMBUF_GUARD
Miika Pekkarinen20b38972005-07-13 12:48:22 +000040/* Must be a power of 2 */
Miika Pekkarinenf090dc32005-07-21 11:44:00 +000041#define NUM_PCM_BUFFERS 64
Miika Pekkarinen20b38972005-07-13 12:48:22 +000042#define NUM_PCM_BUFFERS_MASK (NUM_PCM_BUFFERS - 1)
Miika Pekkarinenf090dc32005-07-21 11:44:00 +000043#define PCMBUF_WATERMARK (CHUNK_SIZE * 6)
Miika Pekkarinen20b38972005-07-13 12:48:22 +000044
45/* Audio buffer related settings. */
Miika Pekkarinenf090dc32005-07-21 11:44:00 +000046static long pcmbuf_size = 0; /* Size of the PCM buffer. */
Miika Pekkarinen20b38972005-07-13 12:48:22 +000047static char *audiobuffer;
48static long audiobuffer_pos; /* Current audio buffer write index. */
49long audiobuffer_free; /* Amount of bytes left in the buffer. */
50static long audiobuffer_fillpos; /* Amount audiobuffer_pos will be increased. */
51static char *guardbuf;
52
53static void (*pcmbuf_event_handler)(void);
54
55/* Crossfade related. */
56static int crossfade_mode;
57static bool crossfade_enabled;
58static bool crossfade_active;
59static bool crossfade_init;
60static int crossfade_pos;
61static int crossfade_amount;
62static int crossfade_rem;
63
64
65static bool boost_mode;
66
67/* Crossfade modes. If CFM_CROSSFADE is selected, normal
68 * crossfader will activate. Selecting CFM_FLUSH is a special
69 * operation that only overwrites the pcm buffer without crossfading.
70 */
71enum {
72 CFM_CROSSFADE,
Miika Pekkarinen90161c92005-07-22 16:46:27 +000073 CFM_MIX,
Miika Pekkarinen20b38972005-07-13 12:48:22 +000074 CFM_FLUSH
75};
76
77/* Structure we can use to queue pcm chunks in memory to be played
78 * by the driver code. */
79struct pcmbufdesc
80{
81 void *addr;
82 int size;
83 /* Call this when the buffer has been played */
84 void (*callback)(void);
85} pcmbuffers[NUM_PCM_BUFFERS];
86
87volatile int pcmbuf_read_index;
88volatile int pcmbuf_write_index;
89int pcmbuf_unplayed_bytes;
Miika Pekkarinen159c52d2005-08-20 11:13:19 +000090int pcmbuf_mix_used_bytes;
Miika Pekkarinen20b38972005-07-13 12:48:22 +000091int pcmbuf_watermark;
92void (*pcmbuf_watermark_event)(int bytes_left);
Miika Pekkarinenf090dc32005-07-21 11:44:00 +000093static int last_chunksize;
Miika Pekkarinen159c52d2005-08-20 11:13:19 +000094static long mixpos = 0;
Miika Pekkarinen20b38972005-07-13 12:48:22 +000095
96static void pcmbuf_boost(bool state)
97{
98 static bool boost_state = false;
99
100 if (crossfade_init || crossfade_active || boost_mode)
101 return ;
102
103 if (state != boost_state) {
104#ifdef HAVE_ADJUSTABLE_CPU_FREQ
105 cpu_boost(state);
106#endif
107 boost_state = state;
108 }
109}
110
111int pcmbuf_num_used_buffers(void)
112{
113 return (pcmbuf_write_index - pcmbuf_read_index) & NUM_PCM_BUFFERS_MASK;
114}
115
116static void pcmbuf_callback(unsigned char** start, long* size)
117{
118 struct pcmbufdesc *desc = &pcmbuffers[pcmbuf_read_index];
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000119
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000120 pcmbuf_unplayed_bytes -= last_chunksize;
121 audiobuffer_free += last_chunksize;
122
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000123 if(desc->size == 0)
124 {
125 /* The buffer is finished, call the callback function */
126 if(desc->callback)
127 desc->callback();
128
129 /* Advance to the next buffer */
130 pcmbuf_read_index = (pcmbuf_read_index + 1) & NUM_PCM_BUFFERS_MASK;
131 desc = &pcmbuffers[pcmbuf_read_index];
132 }
133
134 if(pcmbuf_num_used_buffers())
135 {
Miika Pekkarinend83b6592005-07-19 19:57:23 +0000136
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000137 *start = desc->addr;
Miika Pekkarinend83b6592005-07-19 19:57:23 +0000138 *size = desc->size;
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000139
140 /* Update the buffer descriptor */
Miika Pekkarinend83b6592005-07-19 19:57:23 +0000141 desc->addr += desc->size;
142 desc->size = 0;
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000143 }
144 else
145 {
146 /* No more buffers */
147 *size = 0;
148 if (pcmbuf_event_handler)
149 pcmbuf_event_handler();
150 }
151
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000152 last_chunksize = *size;
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000153 if(pcmbuf_unplayed_bytes <= pcmbuf_watermark)
154 {
155 if(pcmbuf_watermark_event)
156 {
157 pcmbuf_watermark_event(pcmbuf_unplayed_bytes);
158 }
159 }
160}
161
162void pcmbuf_set_watermark(int numbytes, void (*callback)(int bytes_left))
163{
164 pcmbuf_watermark = numbytes;
165 pcmbuf_watermark_event = callback;
166}
167
168bool pcmbuf_add_chunk(void *addr, int size, void (*callback)(void))
169{
170 /* We don't use the last buffer, since we can't see the difference
171 between the full and empty condition */
172 if(pcmbuf_num_used_buffers() < (NUM_PCM_BUFFERS - 2))
173 {
174 pcmbuffers[pcmbuf_write_index].addr = addr;
175 pcmbuffers[pcmbuf_write_index].size = size;
176 pcmbuffers[pcmbuf_write_index].callback = callback;
177 pcmbuf_write_index = (pcmbuf_write_index+1) & NUM_PCM_BUFFERS_MASK;
178 pcmbuf_unplayed_bytes += size;
Miika Pekkarinen159c52d2005-08-20 11:13:19 +0000179 pcmbuf_mix_used_bytes = MAX(0, pcmbuf_mix_used_bytes - size);
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000180 return true;
181 }
182 else
183 return false;
184}
185
186void pcmbuf_watermark_callback(int bytes_left)
187{
188 /* Fill audio buffer by boosting cpu */
189 pcmbuf_boost(true);
190 if (bytes_left <= CHUNK_SIZE * 2)
191 crossfade_active = false;
192}
193
194void pcmbuf_set_boost_mode(bool state)
195{
196 if (state)
197 pcmbuf_boost(true);
198 boost_mode = state;
199}
200
201void pcmbuf_add_event(void (*event_handler)(void))
202{
203 pcmbuf_event_handler = event_handler;
204}
205
206unsigned int pcmbuf_get_latency(void)
207{
208 int latency;
209
Miika Pekkarineneab434c2005-07-22 06:32:55 +0000210 latency = (pcmbuf_unplayed_bytes + pcm_get_bytes_waiting())
211 / 4 / (44100/1000);
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000212 if (latency < 0)
213 latency = 0;
214
215 return latency;
216}
217
218bool pcmbuf_is_lowdata(void)
219{
220 if (!pcm_is_playing() || pcm_is_paused() || crossfade_init || crossfade_active)
221 return false;
222
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000223 if (pcmbuf_unplayed_bytes < CHUNK_SIZE * 4)
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000224 return true;
225
226 return false;
227}
228
Miika Pekkarinen90161c92005-07-22 16:46:27 +0000229bool pcmbuf_crossfade_init(int type)
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000230{
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000231 if (pcmbuf_size - audiobuffer_free < CHUNK_SIZE * 8 || !crossfade_enabled
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000232 || crossfade_active || crossfade_init) {
Miika Pekkarinen34a25a62005-07-15 16:42:01 +0000233 pcmbuf_flush_audio();
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000234 return false;
235 }
236 logf("pcmbuf_crossfade_init");
237 pcmbuf_boost(true);
Miika Pekkarinen90161c92005-07-22 16:46:27 +0000238
239 switch (type) {
240 case CROSSFADE_MODE_CROSSFADE:
241 crossfade_mode = CFM_CROSSFADE;
242 break;
243 case CROSSFADE_MODE_MIX:
244 crossfade_mode = CFM_MIX;
245 break;
246 default:
247 return false;
248 }
249
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000250 crossfade_init = true;
251
252 return true;
253
254}
255
256void pcmbuf_play_stop(void)
257{
258 pcm_play_stop();
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000259 last_chunksize = 0;
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000260 pcmbuf_unplayed_bytes = 0;
Miika Pekkarinen159c52d2005-08-20 11:13:19 +0000261 pcmbuf_mix_used_bytes = 0;
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000262 pcmbuf_read_index = 0;
263 pcmbuf_write_index = 0;
264 audiobuffer_pos = 0;
265 audiobuffer_fillpos = 0;
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000266 audiobuffer_free = pcmbuf_size;
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000267 crossfade_init = false;
268 crossfade_active = false;
269
270 pcmbuf_set_boost_mode(false);
271 pcmbuf_boost(false);
272
273}
274
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000275void pcmbuf_init(long bufsize)
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000276{
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000277 pcmbuf_size = bufsize;
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000278 audiobuffer = &audiobuf[(audiobufend - audiobuf) -
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000279 pcmbuf_size - PCMBUF_GUARD];
280 guardbuf = &audiobuffer[pcmbuf_size];
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000281 pcmbuf_event_handler = NULL;
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000282 pcmbuf_play_stop();
283}
284
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000285long pcmbuf_get_bufsize(void)
286{
287 return pcmbuf_size;
288}
289
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000290/** Initialize a track switch so that audio playback will not stop but
291 * the switch to next track would happen as soon as possible.
292 */
293void pcmbuf_flush_audio(void)
294{
295 if (crossfade_init || crossfade_active || !pcm_is_playing()) {
296 pcmbuf_play_stop();
297 return ;
298 }
299
Miika Pekkarinend83b6592005-07-19 19:57:23 +0000300 pcmbuf_boost(true);
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000301 crossfade_mode = CFM_FLUSH;
302 crossfade_init = true;
303}
304
Miika Pekkarinen159c52d2005-08-20 11:13:19 +0000305/* Force playback. */
306void pcmbuf_play_start(void)
307{
308 if (!pcm_is_playing() && pcmbuf_unplayed_bytes)
309 pcm_play_data(pcmbuf_callback);
310}
311
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000312void pcmbuf_flush_fillpos(void)
313{
314 int copy_n;
315
316 copy_n = MIN(audiobuffer_fillpos, CHUNK_SIZE);
317
318 if (copy_n) {
319 while (!pcmbuf_add_chunk(&audiobuffer[audiobuffer_pos],
320 copy_n, pcmbuf_event_handler)) {
321 pcmbuf_boost(false);
322 sleep(1);
323 /* This is a fatal error situation that should never happen. */
324 if (!pcm_is_playing()) {
325 logf("pcm_flush_fillpos error");
Miika Pekkarinend83b6592005-07-19 19:57:23 +0000326 pcm_play_data(pcmbuf_callback);
327 return ;
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000328 }
329 }
330 pcmbuf_event_handler = NULL;
331 audiobuffer_pos += copy_n;
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000332 if (audiobuffer_pos >= pcmbuf_size)
333 audiobuffer_pos -= pcmbuf_size;
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000334 audiobuffer_free -= copy_n;
335 audiobuffer_fillpos -= copy_n;
336 }
337}
338
339static void crossfade_start(void)
340{
341 int bytesleft = pcmbuf_unplayed_bytes;
342
343 crossfade_init = 0;
Miika Pekkarinen21598112005-07-15 07:57:09 +0000344 if (bytesleft < CHUNK_SIZE * 4) {
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000345 logf("crossfade rejected");
346 pcmbuf_play_stop();
347 return ;
348 }
349
350 logf("crossfade_start");
Miika Pekkarinend83b6592005-07-19 19:57:23 +0000351 audiobuffer_fillpos = 0;
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000352 pcmbuf_boost(true);
353 crossfade_active = true;
354 crossfade_pos = audiobuffer_pos;
355
356 switch (crossfade_mode) {
Miika Pekkarinen90161c92005-07-22 16:46:27 +0000357 case CFM_MIX:
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000358 case CFM_CROSSFADE:
359 crossfade_amount = (bytesleft - (CHUNK_SIZE * 2))/2;
360 crossfade_rem = crossfade_amount;
361 break ;
362
363 case CFM_FLUSH:
364 crossfade_amount = (bytesleft - (CHUNK_SIZE * 2))/2;
365 crossfade_rem = crossfade_amount;
366 break ;
367 }
368
369 crossfade_pos -= crossfade_amount*2;
370 if (crossfade_pos < 0)
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000371 crossfade_pos += pcmbuf_size;
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000372}
373
374static __inline
375int crossfade(short *buf, const short *buf2, int length)
376{
377 int size, i;
378 int val1, val2;
379
380 size = MIN(length, crossfade_rem);
381 switch (crossfade_mode) {
Miika Pekkarinen90161c92005-07-22 16:46:27 +0000382 /* Mix two streams. */
383 case CFM_MIX:
384 /* Bias & add & clip. */
385 for (i = 0; i < size; i++) {
386 buf[i] = MIN(MAX(buf[i] + buf2[i], -32768), 32767);
387 }
388 break ;
389
390 /* Fade two streams. */
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000391 case CFM_CROSSFADE:
392 val1 = (crossfade_rem<<10)/crossfade_amount;
393 val2 = ((crossfade_amount-crossfade_rem)<<10)/crossfade_amount;
394
395 for (i = 0; i < size; i++) {
396 buf[i] = ((buf[i] * val1) + (buf2[i] * val2)) >> 10;
397 }
398 break ;
399
Miika Pekkarinen90161c92005-07-22 16:46:27 +0000400 /* Join two streams. */
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000401 case CFM_FLUSH:
402 for (i = 0; i < size; i++) {
403 buf[i] = buf2[i];
404 }
405 //memcpy((char *)buf, (char *)buf2, size*2);
406 break ;
407 }
408
409 crossfade_rem -= size;
410 if (crossfade_rem <= 0)
411 crossfade_active = false;
412
413 return size;
414}
415
416static bool prepare_insert(long length)
417{
418 if (crossfade_init)
419 crossfade_start();
420
421 if (audiobuffer_free < length + audiobuffer_fillpos
422 + CHUNK_SIZE && !crossfade_active) {
423 pcmbuf_boost(false);
424 return false;
425 }
Miika Pekkarinend83b6592005-07-19 19:57:23 +0000426
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000427 if (!pcm_is_playing()) {
428 pcmbuf_boost(true);
429 crossfade_active = false;
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000430 if (audiobuffer_free < pcmbuf_size - CHUNK_SIZE*4) {
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000431 logf("pcm starting");
432 pcm_play_data(pcmbuf_callback);
433 }
434 }
435
436 return true;
437}
438
439void* pcmbuf_request_buffer(long length, long *realsize)
440{
441 void *ptr = NULL;
Miika Pekkarinen21598112005-07-15 07:57:09 +0000442
443 if (crossfade_init)
444 crossfade_start();
445
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000446 while (audiobuffer_free < length + audiobuffer_fillpos
447 + CHUNK_SIZE && !crossfade_active) {
448 pcmbuf_boost(false);
449 sleep(1);
450 }
451
452 if (crossfade_active) {
453 *realsize = MIN(length, PCMBUF_GUARD);
454 ptr = &guardbuf[0];
455 } else {
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000456 *realsize = MIN(length, pcmbuf_size - audiobuffer_pos
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000457 - audiobuffer_fillpos);
458 if (*realsize < length) {
459 *realsize += MIN((long)(length - *realsize), PCMBUF_GUARD);
460 }
461 ptr = &audiobuffer[audiobuffer_pos + audiobuffer_fillpos];
462 }
463
464 return ptr;
465}
466
467bool pcmbuf_is_crossfade_active(void)
468{
469 return crossfade_active || crossfade_init;
470}
471
472void pcmbuf_flush_buffer(long length)
473{
474 int copy_n;
475 char *buf;
476
477 prepare_insert(length);
478
479 if (crossfade_active) {
480 buf = &guardbuf[0];
481 length = MIN(length, PCMBUF_GUARD);
482 while (length > 0 && crossfade_active) {
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000483 copy_n = MIN(length, pcmbuf_size - crossfade_pos);
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000484 copy_n = 2 * crossfade((short *)&audiobuffer[crossfade_pos],
485 (const short *)buf, copy_n/2);
486 buf += copy_n;
487 length -= copy_n;
488 crossfade_pos += copy_n;
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000489 if (crossfade_pos >= pcmbuf_size)
490 crossfade_pos -= pcmbuf_size;
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000491 }
492
493 while (length > 0) {
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000494 copy_n = MIN(length, pcmbuf_size - audiobuffer_pos);
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000495 memcpy(&audiobuffer[audiobuffer_pos], buf, copy_n);
496 audiobuffer_fillpos = copy_n;
497 buf += copy_n;
498 length -= copy_n;
499 if (length > 0)
500 pcmbuf_flush_fillpos();
501 }
502 }
503
504 audiobuffer_fillpos += length;
505
506 try_flush:
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000507 if (audiobuffer_fillpos < CHUNK_SIZE && pcmbuf_size
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000508 - audiobuffer_pos - audiobuffer_fillpos > 0)
509 return ;
510
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000511 copy_n = audiobuffer_fillpos - (pcmbuf_size - audiobuffer_pos);
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000512 if (copy_n > 0) {
513 audiobuffer_fillpos -= copy_n;
514 pcmbuf_flush_fillpos();
515 copy_n = MIN(copy_n, PCMBUF_GUARD);
516 memcpy(&audiobuffer[0], &guardbuf[0], copy_n);
517 audiobuffer_fillpos = copy_n;
518 goto try_flush;
519 }
520 pcmbuf_flush_fillpos();
521}
522
523bool pcmbuf_insert_buffer(char *buf, long length)
524{
525 long copy_n = 0;
526
527 if (!prepare_insert(length))
528 return false;
529
530
531 if (crossfade_active) {
532 while (length > 0 && crossfade_active) {
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000533 copy_n = MIN(length, pcmbuf_size - crossfade_pos);
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000534
535 copy_n = 2 * crossfade((short *)&audiobuffer[crossfade_pos],
536 (const short *)buf, copy_n/2);
537 buf += copy_n;
538 length -= copy_n;
539 crossfade_pos += copy_n;
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000540 if (crossfade_pos >= pcmbuf_size)
541 crossfade_pos -= pcmbuf_size;
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000542 }
543
544 while (length > 0) {
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000545 copy_n = MIN(length, pcmbuf_size - audiobuffer_pos);
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000546 memcpy(&audiobuffer[audiobuffer_pos], buf, copy_n);
547 audiobuffer_fillpos = copy_n;
548 buf += copy_n;
549 length -= copy_n;
550 if (length > 0)
551 pcmbuf_flush_fillpos();
552 }
553 }
554
555 while (length > 0) {
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000556 copy_n = MIN(length, pcmbuf_size - audiobuffer_pos -
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000557 audiobuffer_fillpos);
558 copy_n = MIN(CHUNK_SIZE - audiobuffer_fillpos, copy_n);
559
560 memcpy(&audiobuffer[audiobuffer_pos+audiobuffer_fillpos],
561 buf, copy_n);
562 buf += copy_n;
563 audiobuffer_fillpos += copy_n;
564 length -= copy_n;
565
566 /* Pre-buffer to meet CHUNK_SIZE requirement */
567 if (audiobuffer_fillpos < CHUNK_SIZE && length == 0) {
568 return true;
569 }
570
571 pcmbuf_flush_fillpos();
572 }
573
574 return true;
575}
576
Miika Pekkarinen159c52d2005-08-20 11:13:19 +0000577/* Generates a constant square wave sound with a given frequency
578 in Hertz for a duration in milliseconds. */
579void pcmbuf_beep(int frequency, int duration, int amplitude)
580{
581 int state = 0, count = 0;
582 int interval = NATIVE_FREQUENCY / frequency;
583 int pos;
584 short *buf = (short *)audiobuffer;
585 int bufsize = pcmbuf_size / 2;
586
587 /* FIXME: Should start playback. */
588 //if (pcmbuf_unplayed_bytes * 1000 < 4 * NATIVE_FREQUENCY * duration)
589 // return ;
590
591 pos = (audiobuffer_pos - pcmbuf_unplayed_bytes) / 2;
592 if (pos < 0)
593 pos += bufsize;
594
595 duration = NATIVE_FREQUENCY / 1000 * duration;
596 while (duration-- > 0)
597 {
598 if (state) {
599 buf[pos] = MIN(MAX(buf[pos] + amplitude, -32768), 32767);
600 if (++pos >= bufsize)
601 pos = 0;
602 buf[pos] = MIN(MAX(buf[pos] + amplitude, -32768), 32767);
603 } else {
604 buf[pos] = MIN(MAX(buf[pos] - amplitude, -32768), 32767);
605 if (++pos >= bufsize)
606 pos = 0;
607 buf[pos] = MIN(MAX(buf[pos] - amplitude, -32768), 32767);
608 }
609
610 if (++count >= interval)
611 {
612 count = 0;
613 if (state)
614 state = 0;
615 else
616 state = 1;
617 }
618 pos++;
619 if (pos >= bufsize)
620 pos = 0;
621 }
622}
623
624/* Returns pcm buffer usage in percents (0 to 100). */
625int pcmbuf_usage(void)
626{
627 return pcmbuf_unplayed_bytes * 100 / pcmbuf_size;
628}
629
630int pcmbuf_mix_usage(void)
631{
632 return pcmbuf_mix_used_bytes * 100 / pcmbuf_unplayed_bytes;
633}
634
635void pcmbuf_reset_mixpos(void)
636{
637 int bufsize = pcmbuf_size / 2;
638
639 pcmbuf_mix_used_bytes = 0;
640 mixpos = (audiobuffer_pos - pcmbuf_unplayed_bytes) / 2;
641 if (mixpos < 0)
642 mixpos += bufsize;
643 if (mixpos >= bufsize)
644 mixpos -= bufsize;
645}
646
647void pcmbuf_mix(char *buf, long length)
648{
649 short *ibuf = (short *)buf;
650 short *obuf = (short *)audiobuffer;
651 int bufsize = pcmbuf_size / 2;
652
653 if (pcmbuf_mix_used_bytes == 0)
654 pcmbuf_reset_mixpos();
655
656 pcmbuf_mix_used_bytes += length;
657 length /= 2;
658
659 while (length-- > 0) {
660 obuf[mixpos] = MIN(MAX(obuf[mixpos] + *ibuf*4, -32768), 32767);
661
662 ibuf++;
663 mixpos++;
664 if (mixpos >= bufsize)
665 mixpos = 0;
666 }
667}
668
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000669void pcmbuf_crossfade_enable(bool on_off)
670{
671 crossfade_enabled = on_off;
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000672
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000673 if (crossfade_enabled) {
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000674 pcmbuf_set_watermark(pcmbuf_size - (CHUNK_SIZE*6), pcmbuf_watermark_callback);
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000675 } else {
676 pcmbuf_set_watermark(PCMBUF_WATERMARK, pcmbuf_watermark_callback);
Miika Pekkarinenf090dc32005-07-21 11:44:00 +0000677 }
Miika Pekkarinen20b38972005-07-13 12:48:22 +0000678}
679
680bool pcmbuf_is_crossfade_enabled(void)
681{
682 return crossfade_enabled;
683}
684