/*! * \brief Driver interface for playing samples to a device * * \param dev Input. Device to play to * \param nSamples Input. Number of samples to play * \param cSamples Input. Samples to play * \param report_latency Input. 1 to report latency, 0 otherwise. * \param volume Input. [0,1] volume ratio * \returns number of samples read */ void play_sound_interface( struct sound_dev* dev, int nSamples, complex double * cSamples, int report_latency, double volume ) { // Play using correct driver. switch( dev->driver ) { case DEV_DRIVER_PORTAUDIO: quisk_play_portaudio(dev, nSamples, cSamples, report_latency, volume); break; case DEV_DRIVER_ALSA: quisk_play_alsa(dev, nSamples, cSamples, report_latency, volume); break; case DEV_DRIVER_PULSEAUDIO: quisk_play_pulseaudio(dev, nSamples, cSamples, report_latency, volume); break; case DEV_DRIVER_NONE: default: break; } }
int quisk_read_sound(void) // Called from sound thread { // called in an infinite loop by the main program int i, nSamples, mic_count, mic_interp, retval, is_cw, mic_sample_rate; complex tx_mic_phase; static double cwEnvelope=0; static double cwCount=0; static complex tuneVector = (double)CLIP32 / CLIP16; // Convert 16-bit to 32-bit samples static struct quisk_cFilter filtInterp={NULL}; quisk_sound_state.interupts++; #if DEBUG_IO > 1 QuiskPrintTime("Start read_sound", 0); #endif if (pt_sample_read) { // read samples from SDR-IQ nSamples = (*pt_sample_read)(cSamples); } else if (quisk_using_udp) { // read samples from UDP port nSamples = quisk_read_rx_udp(cSamples); } else if (Capture.handle) { // blocking read from soundcard if (Capture.portaudio_index < 0) nSamples = quisk_read_alsa(&Capture, cSamples); else nSamples = quisk_read_portaudio(&Capture, cSamples); if (Capture.channel_Delay >= 0) // delay the I or Q channel by one sample delay_sample(&Capture, (double *)cSamples, nSamples); if (Capture.doAmplPhase) // amplitude and phase corrections correct_sample(&Capture, cSamples, nSamples); } else { nSamples = 0; } retval = nSamples; // retval remains the number of samples read #if DEBUG_IO debug_timer += nSamples; if (debug_timer >= quisk_sound_state.sample_rate) // one second debug_timer = 0; #endif #if DEBUG_IO > 2 ptimer (nSamples); #endif quisk_sound_state.latencyCapt = nSamples; // samples available #if DEBUG_IO > 1 QuiskPrintTime(" read samples", 0); #endif // Perhaps record the samples to a file if (want_record) { if (is_recording_samples) { record_samples(cSamples, nSamples); // Record samples } else if (file_name_samples[0]) { if (record_samples(NULL, -1)) // Open file is_recording_samples = 1; } } else if (is_recording_samples) { record_samples(NULL, -2); // Close file is_recording_samples = 0; } #if ! DEBUG_MIC nSamples = quisk_process_samples(cSamples, nSamples); #endif #if DEBUG_IO > 1 QuiskPrintTime(" process samples", 0); #endif if (Playback.portaudio_index < 0) quisk_play_alsa(&Playback, nSamples, cSamples, 1); else quisk_play_portaudio(&Playback, nSamples, cSamples, 1); if (rxMode == 7) { if (DigitalOutput.portaudio_index < 0) quisk_play_alsa(&DigitalOutput, nSamples, cSamples, 0); else quisk_play_portaudio(&DigitalOutput, nSamples, cSamples, 0); } // Perhaps record the speaker audio to a file if (want_record) { if (is_recording_audio) { record_audio(cSamples, nSamples); // Record samples } else if (file_name_audio[0]) { if (record_audio(NULL, -1)) // Open file is_recording_audio = 1; } } else if (is_recording_audio) { record_audio(NULL, -2); // Close file is_recording_audio = 0; } #if DEBUG_IO > 1 QuiskPrintTime(" play samples", 0); #endif // Read and process the microphone mic_count = 0; mic_sample_rate = quisk_sound_state.mic_sample_rate; if (MicCapture.handle) { if (MicCapture.portaudio_index < 0) mic_count = quisk_read_alsa(&MicCapture, cSamples); else mic_count = quisk_read_portaudio(&MicCapture, cSamples); } if (quisk_record_state == PLAYBACK) { // Discard previous samples and replace with saved sound quisk_tmp_microphone(cSamples, mic_count); } if (rxMode == 7) { // Discard previous samples and use digital samples if (DigitalInput.handle) { mic_sample_rate = DigitalInput.sample_rate; if (DigitalInput.portaudio_index < 0) mic_count = quisk_read_alsa(&DigitalInput, cSamples); else mic_count = quisk_read_portaudio(&DigitalInput, cSamples); } else { mic_count = 0; } } if (mic_count > 0) { #if DEBUG_IO > 1 QuiskPrintTime(" mic-read", 0); #endif // quisk_process_microphone returns samples at the sample rate MIC_OUT_RATE mic_count = quisk_process_microphone(mic_sample_rate, cSamples, mic_count); #if DEBUG_MIC == 1 if ( ! quisk_is_key_down()) for (i = 0; i < mic_count; i++) cSamples[i] = 0; for (i = 0; i < mic_count; i++) cSamples[i] *= (double)CLIP32 / CLIP16; // convert 16-bit samples to 32 bits quisk_process_samples(cSamples, mic_count); #endif #if DEBUG_IO > 1 QuiskPrintTime(" mic-proc", 0); #endif } // Mic playback without a mic is needed for CW if (MicPlayback.handle) { // Mic playback: send mic I/Q samples to a sound card if (rxMode == 0 || rxMode == 1) { // Transmit CW is_cw = 1; } else { is_cw = 0; cwCount = 0; cwEnvelope = 0.0; } tx_mic_phase = cexp(( -I * 2.0 * M_PI * quisk_tx_tune_freq) / MicPlayback.sample_rate); if (is_cw) { // Transmit CW; use capture device for timing, not microphone cwCount += (double)retval * MicPlayback.sample_rate / quisk_sound_state.sample_rate; mic_count = 0; if (quisk_is_key_down()) { while (cwCount >= 1.0) { if (cwEnvelope < 1.0) { cwEnvelope += 1. / (MicPlayback.sample_rate * 5e-3); // 5 milliseconds if (cwEnvelope > 1.0) cwEnvelope = 1.0; } cSamples[mic_count++] = (CLIP16 - 1) * cwEnvelope * tuneVector * quisk_sound_state.mic_out_volume; tuneVector *= tx_mic_phase; cwCount -= 1; } } else { // key is up while (cwCount >= 1.0) { if (cwEnvelope > 0.0) { cwEnvelope -= 1.0 / (MicPlayback.sample_rate * 5e-3); // 5 milliseconds if (cwEnvelope < 0.0) cwEnvelope = 0.0; } cSamples[mic_count++] = (CLIP16 - 1) * cwEnvelope * tuneVector * quisk_sound_state.mic_out_volume; tuneVector *= tx_mic_phase; cwCount -= 1; } } } else { // Transmit SSB if ( ! quisk_is_key_down()) { for (i = 0; i < mic_count; i++) cSamples[i] = 0.0; } } // Perhaps interpolate the mic samples back to the mic play rate mic_interp = MicPlayback.sample_rate / MIC_OUT_RATE; if ( ! is_cw && mic_interp > 1) { if (! filtInterp.dCoefs) quisk_filt_cInit(&filtInterp, quiskFilt12_19Coefs, sizeof(quiskFilt12_19Coefs)/sizeof(double)); mic_count = quisk_cInterpolate(cSamples, mic_count, &filtInterp, mic_interp); } // Tune the samples to frequency if ( ! is_cw) { for (i = 0; i < mic_count; i++) { cSamples[i] = conj(cSamples[i]) * tuneVector * quisk_sound_state.mic_out_volume; tuneVector *= tx_mic_phase; } } // delay the I or Q channel by one sample if (MicPlayback.channel_Delay >= 0) delay_sample(&MicPlayback, (double *)cSamples, mic_count); // amplitude and phase corrections if (MicPlayback.doAmplPhase) correct_sample (&MicPlayback, cSamples, mic_count); // play mic samples if (MicPlayback.portaudio_index < 0) quisk_play_alsa(&MicPlayback, mic_count, cSamples, 0); else quisk_play_portaudio(&MicPlayback, mic_count, cSamples, 0); #if DEBUG_MIC == 2 quisk_process_samples(cSamples, mic_count); #endif } #if DEBUG_IO > 1 QuiskPrintTime(" finished", 0); #endif // Return negative number for error return retval; }