#include #include #include #include #include #include #include #include #if defined(__linux__) # include #elif defined(__FreeBSD__) # include #endif #ifdef YSHM_SUPPORT # include "../include/shm.h" #endif #include "ytypes.h" #include "midiiow.h" #include "audiocd.h" #include "ysound.h" #include "options.h" int YSoundInit(Recorder *recorder, Audio *audio); int YSoundShellOut(Recorder *recorder); void YSoundCaliberateCycle(Recorder *recorder); int YSoundPlayBuffer(Recorder *recorder); void YSoundSync(Recorder *recorder, int options); void YSoundShutdown(Recorder *recorder); #define MIN(a,b) ((a) < (b) ? (a) : (b)) #define MAX(a,b) ((a) > (b) ? (a) : (b)) #define CLIP(a,l,h) (MIN(MAX((a),(l)),(h))) #define ABSOLUTE(x) (((x) < 0) ? ((x) * -1) : (x)) /* * Initializes the recorder, the Audio values specified in * audio will be coppied to the recorder's Audio values and * initialized. * * Any errors which occured from opening any devices will be printed * to stderr. */ int YSoundInit(Recorder *recorder, Audio *audio) { int status; Sound *sound_ptr; Audio *audio_ptr; MIDIAudio *midi_audio_ptr; #ifdef YSHM_SUPPORT int shm_id; #endif #ifdef OSS_BUFFRAG unsigned int fragment, num_fragments, fragment_size; int buffer_size; #endif /* OSS_BUFFRAG */ /* struct mixer_info mixer_info_buf; */ struct synth_info synth_info_buf; if((recorder == NULL) || (audio == NULL)) return(-1); /* Get pointers to substructures on recorder. */ sound_ptr = &recorder->sound; audio_ptr = &recorder->audio; midi_audio_ptr = &recorder->audio.midi_audio; /* Reset recorder values. */ memset(sound_ptr, 0x00, sizeof(Sound)); memset(audio_ptr, 0x00, sizeof(Audio)); memset(midi_audio_ptr, 0x00, sizeof(MIDIAudio)); /* Get values from input Audio structure. */ sound_ptr->buffer_length = 0; sound_ptr->buffer_content_length = 0; sound_ptr->device_buffer_length = 0; sound_ptr->device_buffer_content_length = 0; audio_ptr->cycle_set = audio->cycle_set; audio_ptr->cycle.ms = audio->cycle.ms; audio_ptr->cycle.us = audio->cycle.us; audio_ptr->compensated_cycle.ms = audio_ptr->cycle.ms; audio_ptr->compensated_cycle.us = audio_ptr->cycle.us; audio_ptr->write_ahead.ms = audio->write_ahead.ms; audio_ptr->write_ahead.us = audio->write_ahead.us; audio_ptr->cycle_ahead_left.ms = audio->write_ahead.ms; audio_ptr->cycle_ahead_left.us = audio->write_ahead.us; audio_ptr->cumulative_latency.ms = 0; audio_ptr->cumulative_latency.us = 0; audio_ptr->sample_size = audio->sample_size; audio_ptr->channels = audio->channels; audio_ptr->sample_rate = audio->sample_rate; audio_ptr->bytes_per_second = audio->sample_rate * MAX((audio->sample_size / 8), 1) ; #ifdef OSS_BUFFRAG audio_ptr->allow_fragments = audio->allow_fragments; audio_ptr->num_fragments = audio->num_fragments; audio_ptr->fragment_size = audio->fragment_size; #endif /* OSS_BUFFRAG */ audio_ptr->flip_stereo = audio->flip_stereo; audio_ptr->direction = audio->direction; audio_ptr->audio_mode_name = NULL; audio_ptr->fd = -1; audio_ptr->mixer_fd = -1; audio_ptr->sound_name = NULL; audio_ptr->shelled_out = False; /* ******************************************************** */ /* Initialize MIDI IO resources. */ #ifdef ALSA_RUN_CONFORM midi_audio_ptr->midi_device_number = MAX( option.midi_device_number, 0 ); #endif /* ALSA_RUN_CONFORM */ if(MIDIIOWInit(midi_audio_ptr)) { YSoundShutdown(recorder); return(-1); } /* ******************************************************** */ /* Initialize audio CD context. */ audio_ptr->audiocd_context = AudioCDInit(); /* ******************************************************** */ /* Open audio device. */ audio_ptr->fd = open( fname.device, ((audio_ptr->direction == AUDIO_DIRECTION_RECORD) ? O_RDONLY : O_WRONLY ) ); if(audio_ptr->fd < 0) { fprintf( stderr, "%s: Cannot open for %s.\n", fname.device, ((audio_ptr->direction == AUDIO_DIRECTION_RECORD) ? "recording" : "playing" ) ); YSoundShutdown(recorder); return(-1); } /* Open mixer. */ if(fname.mixer[0] != '\0') { audio_ptr->mixer_fd = open( fname.mixer, O_RDWR ); if(audio_ptr->mixer_fd < 0) { /* Warn about failure to open mixer, do not fail * entire procedure. */ fprintf(stderr, "%s: Cannot open.\n", fname.mixer ); } } else { /* No mixer specified. */ audio_ptr->mixer_fd = -1; } #ifdef OSS_BUFFRAG /* * OSS ioctl() call SNDCTL_DSP_SETFRAGMENT: * * Accepts an int parameter which has format 0x00nn00ss where * the nn is max number of buffer fragments (between 0x02 and 0xff) * and the ss gives indirectly the size of a buffer fragment * (fragment_size = (1 << ss)). Valid sizes are between * (ss=0x07 -> 128 bytes and ss=0x11 (17 dec) -> 128k). * * Linux documentation suggestion: 0x00020009 * XBlast Sound Server: 0x0004000a */ if(audio_ptr->allow_fragments) { num_fragments = audio_ptr->num_fragments; fragment_size = audio_ptr->fragment_size; /* Pack fragment argument. */ fragment = (num_fragments << 16) | fragment_size; /* Set new fragment parameters. */ status = ioctl( audio_ptr->fd, SNDCTL_DSP_SETFRAGMENT, &fragment ); if(status == -1) { fprintf(stderr, "%s: Warning: Cannot set buffer fragment configuration to 0x%.8x\n", fname.device, fragment ); } } #endif /* OSS_BUFFRAG */ /* Set sample format (aka sample size or number of bits). */ if(ioctl( audio_ptr->fd, SNDCTL_DSP_SAMPLESIZE, &audio_ptr->sample_size ) == -1) { fprintf(stderr, "%s: Warning: Cannot set sample size to %i bits.\n", fname.device, audio_ptr->sample_size ); } /* Set channels (1 or 2, mono or stereo respectivly). */ if(ioctl( audio_ptr->fd, SNDCTL_DSP_CHANNELS, &audio_ptr->channels ) == -1) { fprintf(stderr, "%s: Warning: Cannot set channels to %i.\n", fname.device, audio_ptr->channels ); } /* Set sample rate. */ if(ioctl( audio_ptr->fd, SNDCTL_DSP_SPEED, /* aka SOUND_PCM_WRITE_RATE. */ &audio_ptr->sample_rate ) == -1) { fprintf(stderr, "%s: Warning: Cannot set sample rate to %i Hz.\n", fname.device, audio_ptr->sample_rate ); } /* Query audio format. */ /* ioctl(audio_ptr->fd, AFMT_QUERY, &status); printf("Format: %i\n", status); */ #ifdef OSS_BUFFRAG /* OSS requires exact audio buffer segment size (because of the * fragmented buffers). * * All write()'s to the device must have a buffer of exactly * this size. SNDCTL_DSP_GETBLKSIZE must be called after * SNDCTL_DSP_SETFRAGMENT. */ status = ioctl( audio_ptr->fd, SNDCTL_DSP_GETBLKSIZE, &buffer_size ); if(status == -1) { fprintf(stderr, "%s: Warning: Cannot get buffer size.\n", fname.device ); } sound_ptr->buffer_length = buffer_size; sound_ptr->device_buffer_length = buffer_size; /* Calculate theoretical and compensated cycle. * * Warning: This value is rarly correct and should be * set manually by being defined in a YMode. */ if(audio_ptr->cycle_set == CYCLE_SET_HARDWARE) { audio_ptr->cycle.ms = (int)sound_ptr->buffer_length * 1000 / (int)audio_ptr->sample_rate / MAX(((int)audio_ptr->sample_size / 8), 1); audio_ptr->cycle.us = (double)sound_ptr->buffer_length / (double)audio_ptr->sample_rate * 1000000 / MAX(((int)audio_ptr->sample_size / 8), 1); audio_ptr->cycle.us = audio_ptr->cycle.us - (audio_ptr->cycle.ms * 1000); /* For now, compensated is the same as theoretical. */ audio_ptr->compensated_cycle.ms = audio_ptr->cycle.ms; audio_ptr->compensated_cycle.us = audio_ptr->cycle.us; } /* Not sure what this is... divides fragment segments? */ /* status = 4; ioctl(audio_ptr->fd, SOUND_PCM_SUBDIVIDE, &status); */ #endif /* OSS_BUFFRAG */ /* ********************************************************** */ /* Allocate "midway" Sound buffer. */ #ifdef YSHM_SUPPORT sound_ptr->buffer = (SoundBuffer *)SHMNew( sound_ptr->buffer_length * sizeof(SoundBuffer), &shm_id ); sound_ptr->buffer_shm_id = shm_id; #else sound_ptr->buffer = (SoundBuffer *)calloc( sound_ptr->buffer_length, sizeof(SoundBuffer) ); #endif /* YSHM_SUPPORT */ if(sound_ptr->buffer == NULL) { YSoundShutdown(recorder); return(-1); } /* Allocate Sound buffer shared with the actual DSP device. */ sound_ptr->device_buffer = (SoundBuffer *)calloc( sound_ptr->device_buffer_length, sizeof(SoundBuffer) ); if(sound_ptr->device_buffer == NULL) { YSoundShutdown(recorder); return(-1); } /* Get sound name. */ /* status = ioctl( audio_ptr->fd, SOUND_MIXER_INFO, &mixer_info_buf ); if(status != -1) { free(audio_ptr->sound_name); audio_ptr->sound_name = strdup(mixer_info_buf.name); } */ synth_info_buf.device = 0; status = ioctl( audio_ptr->fd, SNDCTL_SYNTH_INFO, &synth_info_buf ); if(status != -1) { free(audio_ptr->sound_name); audio_ptr->sound_name = strdup(synth_info_buf.name); } /* Caliberate cycle (only if cycle is to be set by program). */ if(audio_ptr->cycle_set == CYCLE_SET_PROGRAM) YSoundCaliberateCycle(recorder); /* fprintf(stderr, "Cycle: %i.%i (comp %i.%i) Buffer: %i bytes\n", audio_ptr->cycle.ms, audio_ptr->cycle.us, audio_ptr->compensated_cycle.ms, audio_ptr->compensated_cycle.us, sound_ptr->device_buffer_length ); fprintf(stderr, "Channels: %i Bits: %i Sample Rate: %i\n", audio_ptr->channels, audio_ptr->sample_size, audio_ptr->sample_rate ); */ return(0); } /* * Shells out the recorder, by closing all device descriptors. * * This simulates the recorder being active but in reality * any operations to the actual recorder device(s) compoents * will not be performed. This fools the clients into thinking * that the sound objects are still being played. * * To reinitialize the recorder again, you need to call * YSoundShutdown() and then YSoundInit(). */ int YSoundShellOut(Recorder *recorder) { Audio *audio_ptr; if(recorder == NULL) return(-1); audio_ptr = &recorder->audio; /* Close DSP device. */ if(audio_ptr->fd > -1) { close(audio_ptr->fd); audio_ptr->fd = -1; } audio_ptr->shelled_out = True; /* Leave mixer device up. */ /* Not sure what to do about the MIDI device. */ return(0); } /* * Attempts to caliberate the right audio.cycle and * audio.compensated_cycle for the sound device. * * Consider this (program calculating cycle). */ void YSoundCaliberateCycle(Recorder *recorder) { #define TOTAL_SYNC_PASSES 4 int i, fd; time_t prev_delta_u, delta_u; Sound *sound_ptr; Audio *audio_ptr; YTime start_time, end_time; SoundBuffer *buf; YDataLength buf_len; if(recorder == NULL) return; /* Get pointers to substructures. */ sound_ptr = &recorder->sound; audio_ptr = &recorder->audio; /* Not allow program to set cycle? */ if(audio_ptr->cycle_set != CYCLE_SET_PROGRAM) return; if(sound_ptr->device_buffer == NULL) return; if(recorder->audio.fd < 0) return; fd = recorder->audio.fd; buf = sound_ptr->device_buffer; buf_len = sound_ptr->device_buffer_length; /* Reset actual buffer for sound device. */ memset(buf, (SoundBuffer)0x00, buf_len); /* ****************************************************** */ /* Begin syncing. */ /* Sync in case audio is still playing. Otherwise * the average cycle calculated is garbage. */ ioctl(recorder->audio.fd, SNDCTL_DSP_SYNC, 0); /* Write and sync a couple of times, get average cycle * value. */ GetCurrentTime(&start_time); write(fd, buf, buf_len); ioctl(recorder->audio.fd, SNDCTL_DSP_SYNC, 0); GetCurrentTime(&end_time); delta_u = MAX(((end_time.ms * 1000) + end_time.us) - ((start_time.ms * 1000) + start_time.us), 0 ); prev_delta_u = delta_u; for(i = 0; i < TOTAL_SYNC_PASSES; i++) { GetCurrentTime(&start_time); write(fd, buf, buf_len); ioctl(recorder->audio.fd, SNDCTL_DSP_SYNC, 0); GetCurrentTime(&end_time); delta_u = MAX(((end_time.ms * 1000) + end_time.us) - ((start_time.ms * 1000) + start_time.us), 0 ); /* Average it. */ delta_u = (prev_delta_u + delta_u) / 2; prev_delta_u = delta_u; } audio_ptr->cycle.ms = delta_u / 1000; audio_ptr->cycle.us = delta_u % 1000; audio_ptr->compensated_cycle.ms = audio_ptr->cycle.ms; audio_ptr->compensated_cycle.us = audio_ptr->cycle.us; } /* * Copys the midway buffer to the sound buffer and attempts * to have the sound device play it. Afterwards the midway * buffer's contents are reset. */ int YSoundPlayBuffer(Recorder *recorder) { int bytes_written; Sound *sound_ptr; Audio *audio_ptr; if(recorder == NULL) return(-1); /* Get pointers to substructures. */ sound_ptr = &recorder->sound; audio_ptr = &recorder->audio; /* Check if DSP device is opened. If it is not, then still * return 0 because it may need to indicate it is shelled * out. */ if(audio_ptr->fd < 0) return(0); /* Make sure buffers are allocated. */ if((sound_ptr->buffer == NULL) || (sound_ptr->device_buffer == NULL) ) return(-1); /* **************************************************** */ /* Copy buffer to device_buffer. */ memcpy( sound_ptr->device_buffer, /* target. */ sound_ptr->buffer, /* source. */ MIN(sound_ptr->buffer_length, sound_ptr->device_buffer_length ) ); /* Write to audio device. */ bytes_written = write( audio_ptr->fd, sound_ptr->device_buffer, sound_ptr->device_buffer_length ); /* Clear midway buffer. */ memset( sound_ptr->buffer, (SoundBuffer)0x00, sound_ptr->buffer_length ); return(0); } /* * Syncronizes recorder with process, then resets cycle ahead * left value in recorder's Audio. */ void YSoundSync(Recorder *recorder, int options) { Audio *audio_ptr; if(recorder == NULL) return; audio_ptr = &recorder->audio; /* Tell DSP device to sync if it's opened. */ if(audio_ptr->fd > -1) { /* Sync and end any current playback or recording for * the DSP device. */ ioctl(audio_ptr->fd, SNDCTL_DSP_SYNC, 0); } /* Reset cycle ahead time, so writes to the DSP * device are performed more frequently for a while. */ audio_ptr->cycle_ahead_left.ms = audio_ptr->write_ahead.ms; audio_ptr->cycle_ahead_left.us = audio_ptr->write_ahead.us; /* fprintf(stderr, "Sync: Cyclc ahead reset to %ld ms\n", (long)((audio_ptr->cycle_ahead_left.ms * 1000) + audio_ptr->cycle_ahead_left.us) ); */ } /* * Shuts down the recorder, deallocating any of its * allocated resources. */ void YSoundShutdown(Recorder *recorder) { Sound *sound_ptr; Audio *audio_ptr; MIDIAudio *midi_audio_ptr; if(recorder == NULL) return; /* Get pointers to substructures. */ sound_ptr = &recorder->sound; audio_ptr = &recorder->audio; midi_audio_ptr = &recorder->audio.midi_audio; /* Shutdown MIDI IO resources. */ MIDIIOWShutdown(midi_audio_ptr); /* Shutdown audio CD context. */ AudioCDShutdown(audio_ptr->audiocd_context); audio_ptr->audiocd_context = NULL; /* Close recorder's audio device as needed. */ if(audio_ptr->fd > -1) { /* Wait for sound to finish playing. */ ioctl(audio_ptr->fd, SNDCTL_DSP_SYNC, 0); close(audio_ptr->fd); audio_ptr->fd = -1; } /* Close recorder's mixer as needed. */ if(audio_ptr->mixer_fd > -1) { close(audio_ptr->mixer_fd); audio_ptr->mixer_fd = -1; } /* Free allocated substructures. */ #ifdef YSHM_SUPPORT SHMUnref(sound_ptr->buffer); #else free(sound_ptr->buffer); #endif sound_ptr->buffer = NULL; sound_ptr->buffer_length = 0; sound_ptr->buffer_content_length = 0; free(sound_ptr->device_buffer); sound_ptr->device_buffer = NULL; sound_ptr->device_buffer_length = 0; sound_ptr->device_buffer_content_length = 0; memset(&audio_ptr->cycle, 0, sizeof(YDeltaTime)); memset(&audio_ptr->compensated_cycle, 0, sizeof(YDeltaTime)); memset(&audio_ptr->write_ahead, 0, sizeof(YDeltaTime)); memset(&audio_ptr->cycle_ahead_left, 0, sizeof(YDeltaTime)); memset(&audio_ptr->cumulative_latency, 0, sizeof(YDeltaTime)); audio_ptr->sample_size = 0; audio_ptr->channels = 0; audio_ptr->sample_rate = 0; audio_ptr->bytes_per_second = 0; #ifdef OSS_BUFFRAG audio_ptr->allow_fragments = False; audio_ptr->num_fragments = 0; audio_ptr->fragment_size = 0; #endif /* OSS_BUFFRAG */ audio_ptr->flip_stereo = False; free(audio_ptr->audio_mode_name); audio_ptr->audio_mode_name = NULL; audio_ptr->fd = -1; audio_ptr->mixer_fd = -1; free(audio_ptr->sound_name); audio_ptr->sound_name = NULL; audio_ptr->shelled_out = False; }