/*---------------------------------------------------------------------------- ChucK Concurrent, On-the-fly Audio Programming Language Compiler and Virtual Machine Copyright (c) 2004 Ge Wang and Perry R. Cook. All rights reserved. http://chuck.stanford.edu/ http://chuck.cs.princeton.edu/ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 U.S.A. -----------------------------------------------------------------------------*/ #include "chuck_audio.h" #include "chuck_errmsg.h" #include "util_thread.h" #include #include #include #define AL_ALEXT_PROTOTYPES #include // sample #if defined(__CHUCK_USE_64_BIT_SAMPLE__) #define AUDIO_FORMAT AL_FORMAT_STEREO_DOUBLE_EXT #else #define AUDIO_FORMAT AL_FORMAT_STEREO_FLOAT32 #endif // real-time watch dog t_CKBOOL g_do_watchdog = FALSE; // countermeasure #ifdef __MACOSX_CORE__ t_CKUINT g_watchdog_countermeasure_priority = 10; #elif defined(__PLATFORM_WIN32__) && !defined(__WINDOWS_PTHREAD__) t_CKUINT g_watchdog_countermeasure_priority = THREAD_PRIORITY_LOWEST; #else t_CKUINT g_watchdog_countermeasure_priority = 0; #endif // watchdog timeout t_CKFLOAT g_watchdog_timeout = 0.5; // static initialization t_CKBOOL ChuckAudio::m_init = FALSE; t_CKBOOL ChuckAudio::m_start = FALSE; t_CKBOOL ChuckAudio::m_go = FALSE; t_CKBOOL ChuckAudio::m_silent = FALSE; t_CKBOOL ChuckAudio::m_expand_in_mono2stereo= FALSE; t_CKUINT ChuckAudio::m_num_channels_out = NUM_CHANNELS_DEFAULT; t_CKUINT ChuckAudio::m_num_channels_in = NUM_CHANNELS_DEFAULT; t_CKUINT ChuckAudio::m_num_channels_max = NUM_CHANNELS_DEFAULT; t_CKUINT ChuckAudio::m_sample_rate = SAMPLE_RATE_DEFAULT; t_CKUINT ChuckAudio::m_buffer_size = BUFFER_SIZE_DEFAULT; t_CKUINT ChuckAudio::m_num_buffers = NUM_BUFFERS_DEFAULT; SAMPLE * ChuckAudio::m_buffer_out = NULL; SAMPLE * ChuckAudio::m_buffer_in = NULL; SAMPLE * ChuckAudio::m_extern_in = NULL; SAMPLE * ChuckAudio::m_extern_out = NULL; f_audio_cb ChuckAudio::m_audio_cb = NULL; void * ChuckAudio::m_cb_user_data = NULL; t_CKINT ChuckAudio::m_adc_n = 0; t_CKINT ChuckAudio::m_dac_n = 0; std::string ChuckAudio::m_adc_name = ""; std::string ChuckAudio::m_dac_name = ""; std::string ChuckAudio::m_driver_name = ""; static ALCdevice *aldev = NULL; static ALCcontext *alc = NULL; static ALuint albuf; static ALuint alsrc; static pthread_t althread; void ChuckAudio::probe( const char * driver ) { } t_CKINT ChuckAudio::device_named( const char * driver, const std::string & name, t_CKBOOL needs_dac, t_CKBOOL needs_adc ) { if(needs_adc || !needs_dac) return -1; const char *dev = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER); if(dev == NULL) return -1; const char *s = dev; for(int i = 1; *s; i++){ if(strcmp(s, name.c_str()) == 0) return i; s += strlen(s) + 1; } for(int i = 1; *s; i++){ if(strncmp(s, name.c_str(), name.length()) == 0) return i; s += strlen(s) + 1; } return -1; } static t_CKFLOAT get_current_time( t_CKBOOL fresh = TRUE ) { #ifdef __PLATFORM_WIN32__ struct _timeb t; _ftime(&t); return t.time + t.millitm/1000.0; #else static struct timeval t; if( fresh ) gettimeofday(&t,NULL); return t.tv_sec + (t_CKFLOAT)t.tv_usec/1000000; #endif } // watch dog globals static CHUCK_THREAD g_tid_synthesis = 0; static XThread * g_watchdog_thread = NULL; static t_CKBOOL g_watchdog_state = FALSE; static t_CKFLOAT g_watchdog_time = 0; #if !defined(__PLATFORM_WIN32__) || defined(__WINDOWS_PTHREAD__) static void * watch_dog( void * ) #else static unsigned int __stdcall watch_dog( void * ) #endif { // time t_CKFLOAT time = 0; // boost priority? t_CKUINT priority = XThreadUtil::our_priority; // log EM_log( CK_LOG_SEVERE, "starting real-time watch dog process..." ); // push log EM_pushlog(); EM_log( CK_LOG_INFO, "watchdog timeout: %f::second", g_watchdog_timeout ); EM_log( CK_LOG_INFO, "watchdog thread priority: %d", priority ); EM_log( CK_LOG_INFO, "watchdog countermeasure priority: %d", g_watchdog_countermeasure_priority ); // pop log EM_poplog(); // boost watchdog by same priority if( XThreadUtil::our_priority != 0x7fffffff ) XThreadUtil::set_priority( priority ); // while going while( g_do_watchdog ) { // get time = get_current_time( TRUE ); // CK_FPRINTF_STDERR( "last: %f now: %f\n", g_watchdog_time, time ); // resting if( g_watchdog_state == FALSE ) { // check time vs timeout if( time - g_watchdog_time > g_watchdog_timeout ) { // log EM_log( CK_LOG_SEVERE, "real-time watchdog counter-measure activating..." ); // lowering priority if( g_tid_synthesis && XThreadUtil::our_priority != 0x7fffffff ) XThreadUtil::set_priority( g_tid_synthesis, g_watchdog_countermeasure_priority ); // set state g_watchdog_state = TRUE; } } else { // check time vs timeout if( time - g_watchdog_time < g_watchdog_timeout ) { // log EM_log( CK_LOG_SEVERE, "real-time watchdog resting..." ); // raise priority if( g_tid_synthesis && XThreadUtil::our_priority != 0x7fffffff ) XThreadUtil::set_priority( g_tid_synthesis, XThreadUtil::our_priority ); // set state g_watchdog_state = FALSE; } } // advance time usleep( 40000 ); } // log EM_log( CK_LOG_SEVERE, "stopping real-time watch dog process..." ); return 0; } t_CKBOOL ChuckAudio::watchdog_start() { // already if( g_watchdog_thread ) return FALSE; // flag g_do_watchdog = TRUE; // allocate it g_watchdog_thread = new XThread; // start it g_watchdog_thread->start( watch_dog, NULL ); return TRUE; } t_CKBOOL ChuckAudio::watchdog_stop() { // already if( !g_watchdog_thread ) return FALSE; // stop things g_do_watchdog = FALSE; // wait a bit | TODO: is this needed? usleep( 100000 ); // delete watchdog thread | TODO: is this safe? SAFE_DELETE( g_watchdog_thread ); return TRUE; } ALsizei ChuckAudio::alcb(ALvoid *userptr, ALvoid *output_buffer, ALsizei len) { // priority boost if( !m_go && XThreadUtil::our_priority != 0x7fffffff ) { #if !defined(__PLATFORM_WIN32__) || defined(__WINDOWS_PTHREAD__) g_tid_synthesis = pthread_self(); #else // must duplicate for handle to be usable by other threads g_tid_synthesis = NULL; DuplicateHandle( GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &g_tid_synthesis, 0, FALSE, DUPLICATE_SAME_ACCESS ); // TODO: close the duplicate handle? #endif XThreadUtil::set_priority( XThreadUtil::our_priority ); memset( output_buffer, 0, len ); m_go = TRUE; // start watchdog if( g_do_watchdog ) { // timestamp g_watchdog_time = get_current_time( TRUE ); // start watchdog watchdog_start(); } // let it go the first time return len; } // timestamp if( g_do_watchdog ) g_watchdog_time = get_current_time( TRUE ); // call the audio cb m_audio_cb( m_buffer_in, m_buffer_out, len/(sizeof(SAMPLE) * m_num_channels_out), m_num_channels_in, m_num_channels_out, m_cb_user_data ); // copy local buffer to be rendered // REFACTOR-2017: TODO: m_end was the signal to end, what should it be now? if( m_start ) // REFACTOR-2017: now using m_start to gate this { memcpy( output_buffer, m_buffer_out, len ); } // set all elements of local buffer to silence else { memset( output_buffer, 0, len ); } // copy to extern if( m_extern_out ) memcpy( m_extern_out, output_buffer, len ); return len; } const char * ChuckAudio::defaultDriverName() { return ""; } t_CKBOOL ChuckAudio::initialize( t_CKUINT dac_device, t_CKUINT adc_device, t_CKUINT num_dac_channels, t_CKUINT num_adc_channels, t_CKUINT sample_rate, t_CKUINT buffer_size, t_CKUINT num_buffers, f_audio_cb callback, void * data, t_CKBOOL force_srate, // force_srate | 1.3.1.2 (added) const char * driver // NULL means default for build | 1.5.0.0 (added) ) { if( m_init ) return FALSE; m_num_channels_out = num_dac_channels; m_num_channels_in = num_adc_channels; m_sample_rate = sample_rate; m_buffer_size = buffer_size; m_num_buffers = num_buffers; m_start = 0; m_go = 0; m_silent = 0; // remember callback m_audio_cb = callback; // remember user data to pass to callback m_cb_user_data = data; // variable to pass by reference into OpenAL unsigned int bufsize = (unsigned int)m_buffer_size; // log EM_log( CK_LOG_FINE, "initializing OpenAL..." ); // push indent EM_pushlog(); const char *dev = NULL; if(m_dac_n > 0){ const char *s = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER); for(int i = 1; *s; i++){ if(i == m_dac_n){ dev = s; break; } s += strlen(s) + 1; } } const ALCint attrlist[] = {ALC_FREQUENCY, (ALCint)sample_rate, 0, 0}; althread = pthread_self(); if((aldev = alcOpenDevice(dev)) == NULL) goto error; if((alc = alcCreateContext(aldev, attrlist)) == NULL) goto error; alcMakeContextCurrent(alc); alGenSources(1, &alsrc); alGenBuffers(1, &albuf); alBufferCallbackSOFT(albuf, AUDIO_FORMAT, sample_rate, alcb, NULL); alSourcei(alsrc, AL_SOURCE_SPATIALIZE_SOFT, AL_FALSE); alSourcei(alsrc, AL_BUFFER, albuf); // pop indent EM_poplog(); // allocate buffers m_buffer_in = new SAMPLE[m_buffer_size * m_num_channels_max]; m_buffer_out = new SAMPLE[m_buffer_size * m_num_channels_max]; memset( m_buffer_in, 0, m_buffer_size * sizeof(SAMPLE) * m_num_channels_max ); memset( m_buffer_out, 0, m_buffer_size * sizeof(SAMPLE) * m_num_channels_max ); // set flag and return return m_init = TRUE; // handle common error case (added 1.4.1.0) error: if(alc != NULL){ alcDestroyContext(alc); alc = NULL; } if(aldev != NULL){ alcCloseDevice(aldev); aldev = NULL; } // pop log indent (added 1.4.1.0) EM_poplog(); // set flag and return return m_init = FALSE; } t_CKBOOL ChuckAudio::start( ) { alSourcePlay(alsrc); if( !m_start ) alcDeviceResumeSOFT(aldev); m_start = TRUE; return m_start; } t_CKBOOL ChuckAudio::stop( ) { alSourceStop(alsrc); if( m_start ) alcDevicePauseSOFT(aldev); m_start = FALSE; // REFACTOR-2017: set end flag m_silent = TRUE; return !m_start; } void ChuckAudio::shutdown() { // check flag if( !m_init ) return; // stop watchdog | REFACTOR-2017: moved this earlier to give watchdog // a head start in stopping, to avoid unneeded countermeasure deployment watchdog_stop(); if(althread == pthread_self() && alc != NULL){ alSourceStop(alsrc); alDeleteSources(1, &alsrc); alDeleteBuffers(1, &albuf); alcDestroyContext(alc); alc = NULL; alcCloseDevice(aldev); aldev = NULL; } // unflag m_init = FALSE; m_start = FALSE; }