/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2005 by Nick Lanham * * All files in this archive are subject to the GNU General Public License. * See the file COPYING in the source tree root for full license agreement. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "autoconf.h" #include #include #include #include "debug.h" #include "kernel.h" #include "sound.h" #include "SDL.h" static bool pcm_playing; static bool pcm_paused; static Uint8* pcm_data; static size_t pcm_data_size; static SDL_AudioSpec obtained; static SDL_AudioCVT cvt; extern bool debug_audio; static void sdl_dma_start(const void *addr, size_t size) { pcm_playing = true; SDL_LockAudio(); pcm_data = (Uint8 *) addr; pcm_data_size = size; SDL_UnlockAudio(); SDL_PauseAudio(0); } static void sdl_dma_stop(void) { pcm_playing = false; SDL_PauseAudio(1); pcm_paused = false; } static void (*callback_for_more)(unsigned char**, size_t*) = NULL; void pcm_play_data(void (*get_more)(unsigned char** start, size_t* size), unsigned char* start, size_t size) { callback_for_more = get_more; if (!(start && size)) { if (get_more) get_more(&start, &size); else return; } if (start && size) { sdl_dma_start(start, size); } } size_t pcm_get_bytes_waiting(void) { return pcm_data_size; } void pcm_mute(bool mute) { (void) mute; } void pcm_play_stop(void) { if (pcm_playing) { sdl_dma_stop(); } } void pcm_play_pause(bool play) { size_t next_size; Uint8 *next_start; if (!pcm_playing) { return; } if(pcm_paused && play) { if (pcm_get_bytes_waiting()) { printf("unpause\n"); SDL_PauseAudio(0); } else { printf("unpause, no data waiting\n"); void (*get_more)(unsigned char**, size_t*) = callback_for_more; if (get_more) { get_more(&next_start, &next_size); } if (next_start && next_size) { sdl_dma_start(next_start, next_size); } else { sdl_dma_stop(); printf("unpause attempted, no data\n"); } } } else if(!pcm_paused && !play) { printf("pause\n"); SDL_PauseAudio(1); } pcm_paused = !play; } bool pcm_is_paused(void) { return pcm_paused; } bool pcm_is_playing(void) { return pcm_playing; } void pcm_set_frequency(unsigned int frequency) { // FIXME: Check return values SDL_BuildAudioCVT(&cvt, AUDIO_S16SYS, 2, frequency, obtained.format, obtained.channels, obtained.freq); } /* * This function goes directly into the DMA buffer to calculate the left and * right peak values. To avoid missing peaks it tries to look forward two full * peek periods (2/HZ sec, 100% overlap), although it's always possible that * the entire period will not be visible. To reduce CPU load it only looks at * every third sample, and this can be reduced even further if needed (even * every tenth sample would still be pretty accurate). */ #define PEAK_SAMPLES (44100*2/HZ) /* 44100 samples * 2 / 100 Hz tick */ #define PEAK_STRIDE 3 /* every 3rd sample is plenty... */ void pcm_calculate_peaks(int *left, int *right) { long samples = (long) pcm_data_size / 4; short *addr = (short *) pcm_data; if (samples > PEAK_SAMPLES) samples = PEAK_SAMPLES; samples /= PEAK_STRIDE; if (left && right) { int left_peak = 0, right_peak = 0, value; while (samples--) { if ((value = addr [0]) > left_peak) left_peak = value; else if (-value > left_peak) left_peak = -value; if ((value = addr [PEAK_STRIDE | 1]) > right_peak) right_peak = value; else if (-value > right_peak) right_peak = -value; addr += PEAK_STRIDE * 2; } *left = left_peak; *right = right_peak; } else if (left || right) { int peak_value = 0, value; if (right) addr += (PEAK_STRIDE | 1); while (samples--) { if ((value = addr [0]) > peak_value) peak_value = value; else if (-value > peak_value) peak_value = -value; addr += PEAK_STRIDE * 2; } if (left) *left = peak_value; else *right = peak_value; } } static long write_to_soundcard(Uint8 *stream, int len, FILE *debug) { Uint32 written = (((Uint32) len) > pcm_data_size) ? pcm_data_size : (Uint32) len; if (cvt.needed) { cvt.buf = (Uint8 *) malloc(written * cvt.len_mult); cvt.len = written; memcpy(cvt.buf, pcm_data, written); SDL_ConvertAudio(&cvt); memcpy(stream, cvt.buf, cvt.len_cvt); if (debug != NULL) { fwrite(cvt.buf, sizeof(Uint8), cvt.len_cvt, debug); } free(cvt.buf); } else { memcpy(stream, pcm_data, written); if (debug != NULL) { fwrite(pcm_data, sizeof(Uint8), written, debug); } } return written; } void sdl_audio_callback(void *udata, Uint8 *stream, int len) { Uint32 have_now; FILE *debug = (FILE *) udata; /* At all times we need to write a full 'len' bytes to stream. */ /* Write what we have in the PCM buffer */ if (pcm_data_size > 0) { have_now = write_to_soundcard(stream, len, debug); stream += have_now; len -= have_now; pcm_data += have_now; pcm_data_size -= have_now; } /* Audio card wants more? Get some more then. */ while (len > 0) { if (callback_for_more) { callback_for_more(&pcm_data, &pcm_data_size); } else { pcm_data = NULL; pcm_data_size = 0; } if (pcm_data_size > 0) { have_now = write_to_soundcard(stream, len, debug); stream += have_now; len -= have_now; pcm_data += have_now; pcm_data_size -= have_now; } else { DEBUGF("sdl_audio_callback: No Data.\n"); sdl_dma_stop(); break; } } } int pcm_init(void) { SDL_AudioSpec wanted_spec; FILE *debug = NULL; if (debug_audio) { debug = fopen("audiodebug.raw", "wb"); } /* Set 16-bit stereo audio at 44Khz */ wanted_spec.freq = 44100; wanted_spec.format = AUDIO_S16SYS; wanted_spec.channels = 2; wanted_spec.samples = 2048; wanted_spec.callback = sdl_audio_callback; wanted_spec.userdata = debug; /* Open the audio device and start playing sound! */ if(SDL_OpenAudio(&wanted_spec, &obtained) < 0) { fprintf(stderr, "Unable to open audio: %s\n", SDL_GetError()); return -1; } sdl_dma_stop(); return 0; } void pcm_postinit(void) { }