/* * Sound modules that do not depend on alsa or portaudio */ #define PY_SSIZE_T_CLEAN #include #include #include #include #include #include #include #ifdef MS_WINDOWS #include #include #else #include #include #include #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) #include #if defined(BSD) #include #endif #endif #endif #include "quisk.h" #include "filter.h" #define DEBUG 0 // Thanks to Franco Spinelli for this fix: // The H101 hardware using the PCM2904 chip has a one-sample delay between // channels that must be fixed in software. If you have this problem, // set channel_delay in your config file. The FIX_H101 #define is obsolete // but still works. It is equivalent to channel_delay = channel_q. // The structure sound_dev represents a sound device to open. If portaudio_index // is -1, it is an ALSA sound device; otherwise it is a portaudio device with that // index. Portaudio devices have names that start with "portaudio". A device name // can be the null string, meaning the device should not be opened. The sound_dev // "handle" is either an alsa handle or a portaudio stream if the stream is open; // otherwise it is NULL for a closed device. // Set DEBUG_MIC (in quisk.h) to send the microphone samples to the FFT instead of the radio samples. // The sample rate and mic sample rate must be 48000. Use -c n2adr/conf4.py. // 0: Normal operation. // 1: Send filtered mic output to the FFT. // 2: Send mic playback to the FFT and to the radio sound playback device "Playback". // 3: Send unfiltered mic output to the FFT. #if DEBUG_IO static int debug_timer = 1; // count up number of samples #endif struct sound_dev quisk_Playback; struct sound_dev quisk_MicPlayback; static struct sound_dev Capture, MicCapture, DigitalInput, DigitalOutput, RawSamplePlayback; static struct sound_dev dr1, dr2, dr3, dr4, dr5, dr6, dr7, dr8, dr9; // sound output for digital sub-receivers // These are arrays of all capture and playback devices, and MUST end with NULL: static struct sound_dev * CaptureDevices[] = {&Capture, &MicCapture, &DigitalInput, NULL}; struct sound_dev * quiskPlaybackDevices[QUISK_INDEX_SUB_RX1 + QUISK_MAX_SUB_RECEIVERS + 1] = { &quisk_Playback, &quisk_MicPlayback, &DigitalOutput, &RawSamplePlayback, &dr1, &dr2, &dr3, &dr4, &dr5, &dr6, &dr7, &dr8, &dr9, NULL}; static SOCKET radio_sound_socket = INVALID_SOCKET; // send radio sound samples to a socket static SOCKET radio_sound_mic_socket = INVALID_SOCKET; // receive mic samples from a socket static int radio_sound_nshorts; // number of shorts (two bytes) to send static int radio_sound_mic_nshorts; // number of shorts (two bytes) to receive int quisk_active_sidetone; // quisk_active_sidetone == 0 No sidetone; == 1 sidetone from wasapi; == 2 sidetone from old logic int quisk_midi_cwkey; // the CW key from MIDI NoteOn/NoteOff messages int remote_control_head; int remote_control_slave; struct sound_conf quisk_sound_state; // Current sound status struct sound_conf * pt_quisk_sound_state = &quisk_sound_state; // Keep this array of names up to date with quisk.h char * sound_format_names[4] = {"Int32", "Int16", "Float32", "Int24"} ; static struct wav_file file_rec_audio, file_rec_samples, file_rec_mic; static int close_file_rec; double digital_output_level = 0.7; static double file_play_level = 1.0; static int dc_remove_bw=100; // bandwidth of DC removal filter static int multus_cw_samples = 0; // always send CW Tx samples to the Multus radio, even in Rx static ty_sample_start pt_sample_start; static ty_sample_stop pt_sample_stop; static ty_sample_read pt_sample_read; ty_sample_write quisk_pt_sample_write; static complex double cSamples[SAMP_BUFFER_SIZE]; // Complex buffer for samples #if 0 void quisk_sample_level(const char * msg, complex double * cSamples, int nSamples, double scale) { static double time0 = 0; static double level = 0; static int count = 0; double d; int i; count += nSamples; for (i = 0; i < nSamples; i++) { d = cabs(cSamples[i]); if (level < d) level = d; } if (QuiskTimeSec() - time0 > 0.1) { printf ("peak sample level %s: %14.6lf count %8d\n", msg, level / scale, count); level = 0; count = 0; time0 = QuiskTimeSec(); } } #endif void ptimer(int counts) // used for debugging { // print the number of counts per second static unsigned int calls=0, total=0; static time_t time0=0; time_t dt; if (time0 == 0) { time0 = (int)(QuiskTimeSec() * 1.e6); return; } total += counts; calls++; if (calls % 1000 == 0) { dt = (int)(QuiskTimeSec() * 1.e6) - time0; printf("ptimer: %d counts in %d microseconds %.3f counts/sec\n", total, (unsigned)dt, (double)total * 1E6 / dt); } } static void delay_sample (struct sound_dev * dev, double * dSamp, int nSamples) { // Delay the I or Q data stream by one sample. // cSamples is double D[nSamples][2] double d; double * first, * last; if (nSamples < 1) return; if (dev->channel_Delay == dev->channel_I) { first = dSamp; last = dSamp + nSamples * 2 - 2; } else if (dev->channel_Delay == dev->channel_Q) { first = dSamp + 1; last = dSamp + nSamples * 2 - 1; } else { return; } d = dev->save_sample; dev->save_sample = *last; while (--nSamples) { *last = *(last - 2); last -= 2; } *first = d; } static void correct_sample (struct sound_dev * dev, complex double * cSamples, int nSamples) { // Correct the amplitude and phase int i; double re, im; if (dev->doAmplPhase) { // amplitude and phase corrections for (i = 0; i < nSamples; i++) { re = creal(cSamples[i]); im = cimag(cSamples[i]); cSamples[i] = re * dev->AmPhAAAA + I * (re * dev->AmPhCCCC + im * dev->AmPhDDDD); //re = re * dev->AmPhAAAA; //im = re * dev->AmPhCCCC + im * dev->AmPhDDDD; //cSamples[i] = re + I * im; } } } static void DCremove(complex double * cSamples, int nSamples, int sample_rate, int key_state) { int i; double omega, Qsin, Qcos, H0, x; complex double c; static int old_sample_rate = 0; static int old_bandwidth = 0; static double alpha = 0.95; static complex double dc_remove = 0; static complex double dc_average = 0; // Average DC component in samples static complex double dc_sum = 0; static int dc_count = 0; static int dc_key_delay = 0; if (sample_rate != old_sample_rate || dc_remove_bw != old_bandwidth) { old_sample_rate = sample_rate; // calculate a new alpha old_bandwidth = dc_remove_bw; if (old_bandwidth > 0) { omega = M_PI * old_bandwidth / (old_sample_rate / 2.0); Qsin = sin(omega); Qcos = cos(omega); H0 = 1.0 / sqrt(2.0); x = ((Qcos - 1) * (Qcos - 1) + Qsin * Qsin) / (H0 * H0) - Qsin * Qsin; x = sqrt(x); alpha = Qcos - x; //printf ("DC remove: alpha %.3f rate %i bw %i\n", alpha, old_sample_rate, old_bandwidth); } else { //printf("DC remove: disable\n"); } } if (quisk_is_vna || old_bandwidth == 0) { } else if (old_bandwidth == 1) { if (key_state) { dc_key_delay = 0; dc_sum = 0; dc_count = 0; } else if (dc_key_delay < old_sample_rate) { dc_key_delay += nSamples; } else { dc_count += nSamples; for (i = 0; i < nSamples; i++) // Correction for DC offset in samples dc_sum += cSamples[i]; if (dc_count > old_sample_rate * 2) { dc_average = dc_sum / dc_count; //printf("dc average %lf %lf %d\n", creal(dc_average), cimag(dc_average), dc_count); //printf("dc polar %.0lf %d\n", cabs(dc_average), // (int)(360.0 / 2 / M_PI * atan2(cimag(dc_average), creal(dc_average)))); dc_sum = 0; dc_count = 0; } } for (i = 0; i < nSamples; i++) // Correction for DC offset in samples cSamples[i] -= dc_average; } else if (old_bandwidth > 1) { for (i = 0; i < nSamples; i++) { // DC removal; R.G. Lyons page 553; 3rd Ed. p 762 c = cSamples[i] + dc_remove * alpha; cSamples[i] = c - dc_remove; dc_remove = c; } } } void quisk_record_audio(struct wav_file * wavfile, complex double * cSamples, int nSamples) { // Record the speaker audio to a WAV file, PCM, 16 bits, one channel // TODO: correct for big-endian byte order FILE * fp; int j; short samp; // must be 2 bytes unsigned int u; // must be 4 bytes unsigned short s; // must be 2 bytes switch (nSamples) { case -1: // Open the file if (wavfile->fp) fclose(wavfile->fp); wavfile->fp = fp = fopen(wavfile->file_name, "wb"); if ( ! fp) { return; } if (fwrite("RIFF", 1, 4, fp) != 4) { fclose(fp); wavfile->fp = NULL; return; } // pcm data, 16-bit samples, one channel u = 36; fwrite(&u, 4, 1, fp); fwrite("WAVE", 1, 4, fp); fwrite("fmt ", 1, 4, fp); u = 16; fwrite(&u, 4, 1, fp); s = 1; // wave_format_pcm fwrite(&s, 2, 1, fp); s = 1; // number of channels fwrite(&s, 2, 1, fp); u = quisk_Playback.sample_rate; // sample rate fwrite(&u, 4, 1, fp); u *= 2; fwrite(&u, 4, 1, fp); s = 2; fwrite(&s, 2, 1, fp); s = 16; fwrite(&s, 2, 1, fp); fwrite("data", 1, 4, fp); u = 0; fwrite(&u, 4, 1, fp); wavfile->samples = 0; break; case -2: // close the file if (wavfile->fp) fclose(wavfile->fp); wavfile->fp = NULL; break; default: // write the sound data to the file fp = wavfile->fp; if ( ! fp) return; u = (unsigned int)nSamples; if (wavfile->samples >= 2147483629 - u) { // limit size to 2**32 - 1 wavfile->samples = ~0; u = ~0; fseek(fp, 40, SEEK_SET); // seek from the beginning fwrite(&u, 4, 1, fp); fseek(fp, 4, SEEK_SET); fwrite(&u, 4, 1, fp); } else { wavfile->samples += u; fseek(fp, 40, SEEK_SET); u = 2 * wavfile->samples; fwrite(&u, 4, 1, fp); fseek(fp, 4, SEEK_SET); u += 36; fwrite(&u, 4, 1, fp); } fseek(fp, 0, SEEK_END); // seek to the end for (j = 0; j < nSamples; j++) { samp = (short)(creal(cSamples[j]) / 65536.0); fwrite(&samp, 2, 1, fp); } break; } } static int record_samples(struct wav_file * wavfile, complex double * cSamples, int nSamples) { // Record the samples to a WAV file, two float samples I/Q FILE * fp; // TODO: correct for big-endian byte order int j; float samp; // must be 4 bytes unsigned int u; // must be 4 bytes unsigned short s; // must be 2 bytes switch (nSamples) { case -1: // Open the file if (wavfile->fp) fclose(wavfile->fp); wavfile->fp = fp = fopen(wavfile->file_name, "wb"); if ( ! fp) { return 0; } if (fwrite("RIFF", 1, 4, fp) != 4) { fclose(fp); wavfile->fp = NULL; return 0; } // IEEE float data, two channels u = 36; fwrite(&u, 4, 1, fp); fwrite("WAVE", 1, 4, fp); fwrite("fmt ", 1, 4, fp); u = 16; fwrite(&u, 4, 1, fp); s = 3; // wave_format_ieee_float fwrite(&s, 2, 1, fp); s = 2; // number of channels fwrite(&s, 2, 1, fp); u = quisk_sound_state.sample_rate; // sample rate fwrite(&u, 4, 1, fp); u *= 8; fwrite(&u, 4, 1, fp); s = 8; fwrite(&s, 2, 1, fp); s = 32; fwrite(&s, 2, 1, fp); // Add a LIST chunk of type INFO for further metadata fwrite("data", 1, 4, fp); u = 0; fwrite(&u, 4, 1, fp); wavfile->samples = 0; break; case -2: // close the file if (wavfile->fp) fclose(wavfile->fp); wavfile->fp = NULL; break; default: // write the sound data to the file fp = wavfile->fp; if ( ! fp) return 0; u = (unsigned int)nSamples; if (wavfile->samples >= 536870907 - u) { // limit size to 2**32 - 1 wavfile->samples = ~0; u = ~0; fseek(fp, 40, SEEK_SET); // seek from the beginning fwrite(&u, 4, 1, fp); fseek(fp, 4, SEEK_SET); // seek from the beginning fwrite(&u, 4, 1, fp); } else { wavfile->samples += u; fseek(fp, 40, SEEK_SET); // seek from the beginning u = 8 * wavfile->samples; fwrite(&u, 4, 1, fp); fseek(fp, 4, SEEK_SET); // seek from the beginning u += 36 ; fwrite(&u, 4, 1, fp); } fseek(fp, 0, SEEK_END); // seek to the end for (j = 0; j < nSamples; j++) { samp = creal(cSamples[j]) / CLIP32; fwrite(&samp, 4, 1, fp); samp = cimag(cSamples[j]) / CLIP32; fwrite(&samp, 4, 1, fp); } break; } return 1; } void quisk_sample_source(ty_sample_start start, ty_sample_stop stop, ty_sample_read read) { pt_sample_start = start; pt_sample_stop = stop; pt_sample_read = read; } void quisk_sample_source4(ty_sample_start start, ty_sample_stop stop, ty_sample_read read, ty_sample_write write) { pt_sample_start = start; pt_sample_stop = stop; pt_sample_read = read; quisk_pt_sample_write = write; } /*! * \brief Driver interface for reading samples from a device * * \param dev Input. Device to read from * \param cSamples Output. Read samples. * \returns number of samples read */ int read_sound_interface( struct sound_dev* dev, complex double * cSamples ) { int i, nSamples; double avg, samp, re, im, frac, diff; // Read using correct driver. switch( dev->driver ) { case DEV_DRIVER_DIRECTX: nSamples = quisk_read_directx(dev, cSamples); break; case DEV_DRIVER_WASAPI: nSamples = quisk_read_wasapi(dev, cSamples); break; case DEV_DRIVER_PORTAUDIO: nSamples = quisk_read_portaudio(dev, cSamples); break; case DEV_DRIVER_ALSA: nSamples = quisk_read_alsa(dev, cSamples); break; case DEV_DRIVER_PULSEAUDIO: nSamples = quisk_read_pulseaudio(dev, cSamples); break; case DEV_DRIVER_NONE: default: return 0; } if ( ! cSamples || nSamples <= 0 || dev->sample_rate <= 0) // cSamples can be NULL return nSamples; // Calculate average squared level avg = dev->average_square; frac = 1.0 / (0.2 * dev->sample_rate); for (i = 0; i < nSamples; i++) { re = creal(cSamples[i]); im = cimag(cSamples[i]); samp = re * re + im * im; diff = samp - avg; if (diff >= 0) avg = samp; // set to peak value else avg = avg + frac * diff; } dev->average_square = avg; return nSamples; } /*! * \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 */ #define AVG_SEC 10.0 void play_sound_interface(struct sound_dev* dev, int nSamples, complex double * cSamples, int report_latency, double volume) { int i; double avg, samp, re, im, frac, diff, tm; if (cSamples && nSamples > 0 && dev->sample_rate > 0) { // Calculate average squared level avg = dev->average_square; frac = 1.0 / (0.2 * dev->sample_rate); for (i = 0; i < nSamples; i++) { re = creal(cSamples[i]); im = cimag(cSamples[i]); samp = re * re + im * im; diff = samp - avg; if (diff >= 0) avg = samp; // set to peak value else avg = avg + frac * diff; } dev->average_square = avg; } #if 0 static int disturb_time = 0; // Damage the sample rate for debug disturb_time += nSamples; if (disturb_time > 300) { nSamples++; disturb_time = 0; } #endif // Correct sample rate by reference to the buffer_fill <> 0.5 if (dev->cr_correction != 0) { dev->cr_sample_time += nSamples; if (dev->cr_sample_time >= dev->cr_correct_time && nSamples >= 2) { dev->cr_sample_time = 0; if (dev->cr_correction > 0) { // add a sample //printf("Add a sample\n"); cSamples[nSamples] = cSamples[nSamples - 1]; cSamples[nSamples - 1] = (cSamples[nSamples - 2] + cSamples[nSamples]) / 2.0; nSamples++; } else { // remove a sample //printf("Remove a sample\n"); nSamples--; } } } // Play using correct driver. switch( dev->driver ) { case DEV_DRIVER_DIRECTX: quisk_play_directx(dev, nSamples, cSamples, report_latency, volume); break; case DEV_DRIVER_WASAPI: quisk_play_wasapi(dev, nSamples, cSamples, volume); break; case DEV_DRIVER_WASAPI2: quisk_write_wasapi(dev, nSamples, cSamples, volume); break; 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; } // Calculate a new sample rate correction tm = QuiskTimeSec(); if (tm - dev->TimerTime0 > AVG_SEC) { dev->TimerTime0 = tm; if (dev->cr_average_count <= 0) { dev->cr_correction = 0; } else if (dev->dev_index == t_MicPlayback && (rxMode == CWL || rxMode == CWU)) { dev->cr_correction = 0; dev->cr_average_fill /= dev->cr_average_count; if (quisk_sound_state.verbose_sound > 1) QuiskPrintf("%s: Buffer average %5.2lf\n", dev->stream_description, dev->cr_average_fill * 100); } else if (dev->cr_delay > 0) { dev->cr_delay--; dev->cr_correction = 0; dev->cr_average_fill /= dev->cr_average_count; if (quisk_sound_state.verbose_sound > 1) QuiskPrintf("%s: Buffer average %5.2lf\n", dev->stream_description, dev->cr_average_fill * 100); } else if ( ! (dev->dev_index == t_Playback || dev->dev_index == t_MicPlayback)) { dev->cr_correction = 0; dev->cr_average_fill /= dev->cr_average_count; if (quisk_sound_state.verbose_sound > 1) QuiskPrintf("%s: Buffer average %5.2lf\n", dev->stream_description, dev->cr_average_fill * 100); } else { dev->cr_average_fill /= dev->cr_average_count; if (dev->cr_average_fill > 0.55) dev->cr_correction = -0.05 * dev->play_buf_size; else if (dev->cr_average_fill < 0.45) dev->cr_correction = 0.05 * dev->play_buf_size; else dev->cr_correction = (0.5 - dev->cr_average_fill) * dev->play_buf_size; if (dev->cr_correction != 0) dev->cr_correct_time = abs(dev->sample_rate * AVG_SEC / dev->cr_correction); if (quisk_sound_state.verbose_sound > 1) QuiskPrintf("%s: Buffer average %5.2lf cr_correction %5d\n", dev->stream_description, dev->cr_average_fill * 100, dev->cr_correction); } dev->cr_average_fill = 0; dev->cr_average_count = 0; dev->cr_sample_time = 0; } } static int read_radio_sound_socket(complex double * cSamples) { int i, bytes, nSamples; short s; double d; struct timeval tm_wait; char buf[1500]; fd_set fds; static int started = 0; nSamples = 0; while (1) { // read all available blocks if (nSamples > SAMP_BUFFER_SIZE / 2) break; tm_wait.tv_sec = 0; tm_wait.tv_usec = 0; FD_ZERO (&fds); FD_SET (radio_sound_mic_socket, &fds); if (select (radio_sound_mic_socket + 1, &fds, NULL, NULL, &tm_wait) != 1) break; bytes = recv(radio_sound_mic_socket, buf, 1500, 0); if (bytes == radio_sound_mic_nshorts * 2) { // required block size started = 1; for (i = 2; i < bytes; i += 2) { memcpy(&s, buf + i, 2); d = (double)s / CLIP16 * CLIP32; // convert 16-bit samples to 32 bits cSamples[nSamples++] = d + I * d; } } } if ( ! started && nSamples == 0) { i = send(radio_sound_mic_socket, "rr", 2, 0); if (i != 2) printf("read_radio_sound_mic_socket returned %d\n", i); } return nSamples; } static void send_radio_sound_socket(complex double * cSamples, int count, double volume) { // Send count samples. Each sample is sent as two shorts (4 bytes) of I/Q data. // Send an initial two bytes of zero for each block. // Transmission is delayed until a whole block of data is available. int i, sent; static short udp_iq[750] = {0}; // Documented maximum radio sound samples is 367 static int udp_size = 1; for (i = 0; i < count; i++) { udp_iq[udp_size++] = (short)(creal(cSamples[i]) * volume * (double)CLIP16 / CLIP32); udp_iq[udp_size++] = (short)(cimag(cSamples[i]) * volume * (double)CLIP16 / CLIP32); if (udp_size >= radio_sound_nshorts) { // check count sent = send(radio_sound_socket, (char *)udp_iq, udp_size * 2, 0); if (sent != udp_size * 2) printf("Send audio socket returned %d\n", sent); udp_size = 1; } } } void * quisk_make_txIQ(struct sound_dev * dev, int rewind) { // make complex samples to transmit CW on SoundCard static float f32[2]; static int16_t i16[2]; static int32_t i32[2]; static int8_t i24[6]; static float envelopeVol=0; int32_t n; float rise_time; float d, dSamp0, dSamp1; static complex double tuneVector = 1.0; complex double sample, tx_mic_phase; rise_time = 0.707 / (dev->sample_rate * 4.0 / 1000.0); // milliseconds rise time if (QUISK_CWKEY_DOWN) { if (envelopeVol < 0.707) { envelopeVol += rise_time; if (envelopeVol > 0.707) envelopeVol = 0.707; } } else { if (envelopeVol > 0.0) { envelopeVol -= rise_time; if (envelopeVol < 0.0) envelopeVol = 0.0; } } if (rewind) { // rewind the phase tx_mic_phase = cexp(( -I * 2.0 * M_PI * quisk_tx_tune_freq) / dev->sample_rate); tuneVector /= cpow(tx_mic_phase, rewind); return NULL; } if (envelopeVol > 0) { tx_mic_phase = cexp(( -I * 2.0 * M_PI * quisk_tx_tune_freq) / dev->sample_rate); sample = envelopeVol * tuneVector * quisk_sound_state.mic_out_volume; dSamp0 = creal(sample); dSamp1 = cimag(sample); tuneVector *= tx_mic_phase; } else { i32[0] = i32[1] = 0; return i32; } // maybe delay the I or Q channel by one sample if (dev->channel_Delay == dev->channel_I) { d = dev->save_sample; dev->save_sample = dSamp0; dSamp0 = d; } else if (dev->channel_Delay == dev->channel_Q) { d = dev->save_sample; dev->save_sample = dSamp1; dSamp1 = d; } // amplitude and phase corrections if (dev->doAmplPhase) { //dSamp0 = dSamp0 * dev->AmPhAAAA; //dSamp1 = dSamp0 * dev->AmPhCCCC + dSamp1 * dev->AmPhDDDD; d = dSamp0; dSamp0 = d * dev->AmPhAAAA; dSamp1 = d * dev->AmPhCCCC + dSamp1 * dev->AmPhDDDD; } switch (dev->sound_format) { case Int16: i16[0] = (int16_t)(dSamp0 * CLIP16); i16[1] = (int16_t)(dSamp1 * CLIP16); return i16; case Int24: // only works for little-endian n = (int32_t)(dSamp0 * CLIP32); memcpy(i24, (int8_t *)&n + 1, 3); n = (int32_t)(dSamp1 * CLIP32); memcpy(i24 + 3, (int8_t *)&n + 1, 3); return i24; case Int32: default: i32[0] = (int32_t)(dSamp0 * CLIP32); i32[1] = (int32_t)(dSamp1 * CLIP32); return i32; case Float32: f32[0] = dSamp0; f32[1] = dSamp1; return f32; } } void * quisk_make_sidetone(struct sound_dev * dev, int rewind) { // make sidetone samples static float f32; static int16_t i16; static int32_t i32; static float envelopeVol=0; static float phase = 0.0; float rise_time; if (rewind) { // rewind the phase phase -= fmodf(2.0 * M_PI * quisk_sidetoneFreq / dev->sample_rate * rewind, 2.0 * M_PI); return NULL; } rise_time = 0.707 / (dev->sample_rate * 4.0 / 1000.0); // milliseconds rise time if (QUISK_CWKEY_DOWN) { if (envelopeVol < 0.707) { envelopeVol += rise_time; if (envelopeVol > 0.707) envelopeVol = 0.707; } } else { if (envelopeVol > 0.0) { envelopeVol -= rise_time; if (envelopeVol < 0.0) envelopeVol = 0.0; } } if (phase < 0) phase += 2.0 * M_PI; else if (phase > 2.0 * M_PI) phase -= 2.0 * M_PI; if (envelopeVol > 0) { phase += 2.0 * M_PI * quisk_sidetoneFreq / dev->sample_rate; f32 = sinf(phase) * envelopeVol * quisk_sidetoneVolume; } else { i32 = 0; return &i32; } switch (dev->sound_format) { case Int16: i16 = (int16_t)(f32 * CLIP16); return &i16; case Int24: // only works for little-endian i32 = (int32_t)(f32 * CLIP32); return (unsigned char *)&i32 + 1; case Int32: default: i32 = (int32_t)(f32 * CLIP32); return &i32; case Float32: return &f32; } } int quisk_play_sidetone(struct sound_dev * dev) { // return 1 if we played the ALSA or PULSEAUDIO sidetone static int last_play_state = 0; if (quisk_play_state <= RECEIVE && last_play_state <= RECEIVE) return 0; last_play_state = quisk_play_state; if (quisk_isFDX) return 0; #ifdef QUISK_HAVE_ALSA if (quisk_active_sidetone == 3 && dev->driver == DEV_DRIVER_ALSA) { quisk_alsa_sidetone(dev); return 1; } #endif #ifdef QUISK_HAVE_PULSEAUDIO if (quisk_active_sidetone == 4 && dev->driver == DEV_DRIVER_PULSEAUDIO) { quisk_pulseaudio_sidetone(dev); return 1; } #endif return 0; } #if 0 static void testing(double complex * cSamples, int nSamples) { int i; static double AvgR=0, AvgQ=0, AvgRQ=0; double d, p, Gain, Phase; time_t tim; static time_t time0; for (i = 0; i < nSamples; i++) { d = creal(cSamples[i]); AvgR += d * d; p = cimag(cSamples[i]); AvgQ += p * p; AvgRQ += d * p; } tim = time(NULL); if (tim - time0 >= 2) { time0 = tim; Gain = sqrt(AvgR / AvgQ); Phase = AvgRQ / AvgQ / Gain; Phase = asin(Phase); printf("Time domain Gain %10.7lf, Phase %10.7lf, %10.7lf degrees\n", Gain, Phase, Phase * 57.29578); AvgR = AvgQ = AvgRQ = 0; } } #endif 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; double mic_play_volume; complex double tx_mic_phase; static double cwEnvelope=0; static double cwCount=0; static complex double tuneVector = (double)CLIP32 / CLIP16; // Convert 16-bit to 32-bit samples static struct quisk_cFilter filtInterp={NULL}; int key_state, is_DGT; #if DEBUG_IO > 1 char str80[80]; // Extra debug output by Ben Cahill, AC2YD #endif #if DEBUG_MIC == 1 complex double tmpSamples[SAMP_BUFFER_SIZE]; #endif if (close_file_rec) { close_file_rec = 0; quisk_record_audio(&file_rec_audio, NULL, -2); quisk_record_audio(&file_rec_mic, NULL, -2); record_samples(&file_rec_samples, NULL, -2); } //QuiskPrintTime("quisk_read_sound start", 0); quisk_sound_state.interrupts++; if (quisk_use_serial_port) { quisk_poll_hardware_key(); } quisk_set_play_state(); key_state = quisk_is_key_down(); //reading this once is important for predicable bevavior on cork/flush #if DEBUG_IO > 1 QuiskPrintTime("Start quisk_read_sound", 0); #endif #ifdef QUISK_HAVE_PULSEAUDIO if (quisk_sound_state.IQ_server[0] && ! (rxMode == CWL || rxMode == CWU)) { if (Capture.handle && Capture.driver == DEV_DRIVER_PULSEAUDIO) { if (key_state == 1 && !Capture.cork_status) quisk_cork_pulseaudio(&Capture, 1); else if (key_state == 0 && Capture.cork_status) { quisk_cork_pulseaudio(&Capture, 0); quisk_flush_pulseaudio(&Capture); } } if (quisk_MicPlayback.handle && quisk_MicPlayback.driver == DEV_DRIVER_PULSEAUDIO) { if (key_state == 0 && !quisk_MicPlayback.cork_status) quisk_cork_pulseaudio(&quisk_MicPlayback, 1); else if (key_state == 1 && quisk_MicPlayback.cork_status) { quisk_cork_pulseaudio(&quisk_MicPlayback, 0); quisk_flush_pulseaudio(&quisk_MicPlayback); } } } else if (quisk_sound_state.IQ_server[0]) { if (Capture.handle && Capture.driver == DEV_DRIVER_PULSEAUDIO) { if (Capture.cork_status) quisk_cork_pulseaudio(&Capture, 0); } if (quisk_MicPlayback.handle && quisk_MicPlayback.driver == DEV_DRIVER_PULSEAUDIO) { if (quisk_MicPlayback.cork_status) quisk_cork_pulseaudio(&quisk_MicPlayback, 0); } } #endif if (pt_sample_read) { // read samples from SDR-IQ or UDP or SoapySDR nSamples = (*pt_sample_read)(cSamples); DCremove(cSamples, nSamples, quisk_sound_state.sample_rate, key_state); if (nSamples <= 0) QuiskSleepMicrosec(2000); } else if (Capture.handle) { // blocking read from soundcard //QuiskPrintTime("quisk_read_sound start", 0); nSamples = read_sound_interface(&Capture, cSamples); //QuiskPrintTime("quisk_read_sound end", 0); if (Capture.channel_Delay >= 0) // delay the I or Q channel by one sample delay_sample(&Capture, (double *)cSamples, nSamples); DCremove(cSamples, nSamples, quisk_sound_state.sample_rate, key_state); //testing(cSamples, nSamples); if (Capture.doAmplPhase) // amplitude and phase corrections correct_sample(&Capture, cSamples, nSamples); if (nSamples <= 0) QuiskSleepMicrosec(2000); #if DEBUG_IO > 1 snprintf(str80, 80, " rx i/q read %d samples", nSamples); QuiskPrintTime(str80, 0); #endif } else { QuiskSleepMicrosec(5000); nSamples = (int)(0.5 + QuiskDeltaSec(1) * quisk_sound_state.sample_rate); if (nSamples > SAMP_BUFFER_SIZE / 2) nSamples = SAMP_BUFFER_SIZE / 2; for (i = 0; i < nSamples; i++) cSamples[i] = 0; } //QuiskPrintTime("quisk_read_sound end", 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 // Perhaps record the Rx samples to a file if ( ! key_state && file_rec_samples.fp) record_samples(&file_rec_samples, cSamples, nSamples); // Perhaps write samples to a loopback device for use by another program if (RawSamplePlayback.handle) play_sound_interface(&RawSamplePlayback, nSamples, cSamples, 0, 1.0); // Perhaps replace the samples with samples from a file if (quisk_record_state == FILE_PLAY_SAMPLES) quisk_play_samples(cSamples, nSamples); // Send radio IQ samples to TCI tci_send_iq(cSamples, nSamples); #if ! DEBUG_MIC nSamples = quisk_process_samples(cSamples, nSamples); #endif #if DEBUG_IO > 1 snprintf(str80, 80, " rx i/q process %d samples", nSamples); QuiskPrintTime(str80, 0); #endif // For control_head role in remote control operation, receive radio sound via UDP if (remote_control_head) nSamples = read_remote_radio_sound_socket(cSamples); // For remote_radio role in remote control operation, send radio sound via UDP if (remote_control_slave) send_remote_radio_sound_socket(cSamples, nSamples); is_DGT = (rxMode == DGT_U || rxMode == DGT_L || rxMode == DGT_IQ || rxMode == DGT_FM || rxMode == FDV_U || rxMode == FDV_L); if (quisk_record_state == TMP_PLAY_SPKR_MIC) quisk_tmp_playback(cSamples, nSamples, 1.0); // replace radio sound else if (quisk_record_state == FILE_PLAY_SPKR_MIC) quisk_file_playback(cSamples, nSamples, file_play_level); // replace radio sound // Play the demodulated audio #if DEBUG_MIC != 2 if ( ! quisk_play_sidetone(&quisk_Playback)) { // play sidetone if (rxMode == FDV_U || rxMode == FDV_L) play_sound_interface(&quisk_Playback, nSamples, cSamples, 1, 0); // play silence else play_sound_interface(&quisk_Playback, nSamples, cSamples, 1, quisk_audioVolume); // play radio sound } #endif if (radio_sound_socket != INVALID_SOCKET) send_radio_sound_socket(cSamples, nSamples, quisk_audioVolume); // Play digital if required //if (is_DGT) play_sound_interface(&DigitalOutput, nSamples, cSamples, 1, digital_output_level); // Send radio sound to TCI tci_send_audio(cSamples, nSamples); // Perhaps record the speaker audio to a file if ( ! key_state && file_rec_audio.fp) quisk_record_audio(&file_rec_audio, cSamples, nSamples); // Record Rx samples #if DEBUG_IO > 1 snprintf(str80, 80, " play %d radio sound samples", nSamples); QuiskPrintTime(str80, 0); #endif // Read and process the microphone mic_sample_rate = quisk_sound_state.mic_sample_rate; if (MicCapture.handle) { mic_count = read_sound_interface(&MicCapture, cSamples); #if DEBUG_IO > 1 snprintf(str80, 80, " mic read %d samples", mic_count); QuiskPrintTime(str80, 0); #endif } else if (radio_sound_mic_socket != INVALID_SOCKET) mic_count = read_radio_sound_socket(cSamples); else { // No mic source; use zero samples mic_count = (int)(0.5 + QuiskDeltaSec(0) * mic_sample_rate); if (mic_count > SAMP_BUFFER_SIZE / 2) mic_count = SAMP_BUFFER_SIZE / 2; for (i = 0; i < mic_count; i++) cSamples[i] = 0; } //QuiskPrintTime("quisk_read_sound end", 0); if (quisk_record_state == TMP_PLAY_SPKR_MIC) // Discard previous samples and replace with saved sound quisk_tmp_microphone(cSamples, mic_count); else if (quisk_record_state == FILE_PLAY_SPKR_MIC) // Discard previous samples and replace with saved sound quisk_file_microphone(cSamples, mic_count); if (DigitalInput.handle) { if (is_DGT) { // Discard previous mic samples and use digital samples mic_sample_rate = DigitalInput.sample_rate; mic_count = read_sound_interface(&DigitalInput, cSamples); } else { // Read and discard any digital samples read_sound_interface(&DigitalInput, NULL); } } else if (is_DGT) { // Use zero-valued samples for (i = 0; i < mic_count; i++) cSamples[i] = 0; } // Receive microphone sound from TCI //quisk_sample_level("read mic or DGT", cSamples, mic_count, CLIP32); mic_count = tci_get_mic(cSamples, mic_count); //quisk_sample_level("quisk_tci", cSamples, mic_count, CLIP32); // Perhaps record the microphone audio to the speaker audio file if (key_state && file_rec_audio.fp) quisk_record_audio(&file_rec_audio, cSamples, mic_count); // Perhaps record the microphone audio to the microphone audio file if (file_rec_mic.fp) quisk_record_audio(&file_rec_mic, cSamples, mic_count); // For remote_radio role in remote control operation, read mic sound via UDP; replace any mic sound from above. if (remote_control_slave) mic_count = read_remote_mic_sound_socket(cSamples); // For control_head role in remote control operation, send mic sound via UDP if (remote_control_head) send_remote_mic_sound_socket(cSamples, mic_count); if (mic_count > 0) { #if DEBUG_MIC == 3 quisk_process_samples(cSamples, mic_count); #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 for (i = 0; i < mic_count; i++) tmpSamples[i] = cSamples[i] * (double)CLIP32 / CLIP16; // convert 16-bit samples to 32 bits quisk_process_samples(tmpSamples, mic_count); #endif #if DEBUG_IO > 1 snprintf(str80, 80, " mic process %d samples", mic_count); QuiskPrintTime(str80, 0); #endif } //quisk_sample_level("quisk_process_microphone", cSamples, mic_count, CLIP16); // Mic playback without a mic is needed for CW if (quisk_MicPlayback.handle) { // Mic playback: send mic I/Q samples to a sound card //quisk_sample_level("quisk_MicPlayback.handle", cSamples, mic_count, CLIP16); mic_play_volume = 1.0; if (rxMode == CWL || rxMode == CWU) { // 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) / quisk_MicPlayback.sample_rate); if (is_cw) { // Transmit CW; use capture device for timing, not microphone cwCount += (double)retval * quisk_MicPlayback.sample_rate / quisk_sound_state.sample_rate; mic_count = 0; if (QUISK_CWKEY_DOWN || quiskSpotLevel >= 0 || multus_cw_samples) { while (cwCount >= 1.0) { if (cwEnvelope < 1.0) { cwEnvelope += 1. / (quisk_MicPlayback.sample_rate * 5e-3); // 5 milliseconds if (cwEnvelope > 1.0) cwEnvelope = 1.0; } if (quiskSpotLevel >= 0) cSamples[mic_count++] = (CLIP16 - 1) * cwEnvelope * quiskSpotLevel / 1000.0 * tuneVector * quisk_sound_state.mic_out_volume; else 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 / (quisk_MicPlayback.sample_rate * 5e-3); // 5 milliseconds if (cwEnvelope < 0.0) cwEnvelope = 0.0; } if (quiskSpotLevel >= 0) cSamples[mic_count++] = (CLIP16 - 1) * cwEnvelope * quiskSpotLevel / 1000.0 * tuneVector * quisk_sound_state.mic_out_volume; else cSamples[mic_count++] = (CLIP16 - 1) * cwEnvelope * tuneVector * quisk_sound_state.mic_out_volume; tuneVector *= tx_mic_phase; cwCount -= 1; } } } else if( ! DEBUG_MIC && ! quisk_is_key_down()) { // Not CW and key up: zero samples mic_play_volume = 0.0; for (i = 0; i < mic_count; i++) cSamples[i] = 0.0; } // Perhaps interpolate the mic samples back to the mic play rate mic_interp = quisk_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 and convert 16-bit samples to 32-bits (using tuneVector) 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; } } // Correct order of delay and phase by Chuck Ritola. Thanks Chuck. // amplitude and phase corrections if (quisk_MicPlayback.doAmplPhase) correct_sample (&quisk_MicPlayback, cSamples, mic_count); // delay the I or Q channel by one sample if (quisk_MicPlayback.channel_Delay >= 0) delay_sample(&quisk_MicPlayback, (double *)cSamples, mic_count); // play mic samples //quisk_sample_level("play quisk_MicPlayback", cSamples, mic_count, CLIP32); play_sound_interface(&quisk_MicPlayback, mic_count, cSamples, 1, mic_play_volume); #if DEBUG_MIC == 2 play_sound_interface(&Playback, mic_count, cSamples, 1, quisk_audioVolume); quisk_process_samples(cSamples, mic_count); #endif } #if DEBUG_IO > 1 snprintf(str80, 80, " play %d tx i/q samples; finished", mic_count); QuiskPrintTime(str80, 0); #endif // Return negative number for error return retval; } int quisk_get_overrange(void) // Called from GUI thread { // Return the overrange (ADC clip) counter, then zero it int i; i = quisk_sound_state.overrange + Capture.overrange; quisk_sound_state.overrange = 0; Capture.overrange = 0; return i; } void quisk_close_sound(void) // Called from sound thread { quisk_active_sidetone = 0; // No sidetone #ifdef MS_WINDOWS int cleanup = radio_sound_socket != INVALID_SOCKET || radio_sound_mic_socket != INVALID_SOCKET; #endif quisk_close_sound_directx(CaptureDevices, quiskPlaybackDevices); quisk_close_sound_wasapi(CaptureDevices, quiskPlaybackDevices); quisk_close_sound_portaudio(); quisk_close_sound_alsa(CaptureDevices, quiskPlaybackDevices); quisk_close_sound_pulseaudio(); if (pt_sample_stop) (*pt_sample_stop)(); strMcpy (quisk_sound_state.err_msg, CLOSED_TEXT, QUISK_SC_SIZE); if (radio_sound_socket != INVALID_SOCKET) { close(radio_sound_socket); radio_sound_socket = INVALID_SOCKET; } if (radio_sound_mic_socket != INVALID_SOCKET) { shutdown(radio_sound_mic_socket, QUISK_SHUT_RD); send(radio_sound_mic_socket, "ss", 2, 0); send(radio_sound_mic_socket, "ss", 2, 0); QuiskSleepMicrosec(1000000); close(radio_sound_mic_socket); radio_sound_mic_socket = INVALID_SOCKET; } quisk_play_state = SHUTDOWN; #ifdef MS_WINDOWS if (cleanup) WSACleanup(); #endif } static void set_num_channels(struct sound_dev * dev) { // Set num_channels to the maximum channel index plus one dev->num_channels = dev->channel_I; if (dev->num_channels < dev->channel_Q) dev->num_channels = dev->channel_Q; dev->num_channels++; } static void open_radio_sound_socket(void) { struct sockaddr_in Addr; int samples, port, sndsize = 48000; char radio_sound_ip[QUISK_SC_SIZE]; char radio_sound_mic_ip[QUISK_SC_SIZE]; #ifdef MS_WINDOWS WORD wVersionRequested; WSADATA wsaData; #endif dc_remove_bw = QuiskGetConfigInt ("dc_remove_bw", 100); strMcpy(radio_sound_ip, QuiskGetConfigString ("radio_sound_ip", ""), QUISK_SC_SIZE); strMcpy(radio_sound_mic_ip, QuiskGetConfigString ("radio_sound_mic_ip", ""), QUISK_SC_SIZE); if (radio_sound_ip[0] == 0 && radio_sound_mic_ip[0] == 0) return; #ifdef MS_WINDOWS wVersionRequested = MAKEWORD(2, 2); if (WSAStartup(wVersionRequested, &wsaData) != 0) { printf("open_radio_sound_socket: Failure to start WinSock\n"); return; // failure to start winsock } #endif if (radio_sound_ip[0]) { port = QuiskGetConfigInt ("radio_sound_port", 0); samples = QuiskGetConfigInt ("radio_sound_nsamples", 360); if (samples > 367) samples = 367; radio_sound_nshorts = samples * 2 + 1; radio_sound_socket = socket(PF_INET, SOCK_DGRAM, 0); if (radio_sound_socket != INVALID_SOCKET) { setsockopt(radio_sound_socket, SOL_SOCKET, SO_SNDBUF, (char *)&sndsize, sizeof(sndsize)); Addr.sin_family = AF_INET; Addr.sin_port = htons(port); #ifdef MS_WINDOWS Addr.sin_addr.S_un.S_addr = inet_addr(radio_sound_ip); #else inet_aton(radio_sound_ip, &Addr.sin_addr); #endif if (connect(radio_sound_socket, (const struct sockaddr *)&Addr, sizeof(Addr)) != 0) { close(radio_sound_socket); radio_sound_socket = INVALID_SOCKET; } } if (radio_sound_socket == INVALID_SOCKET) { printf("open_radio_sound_socket: Failure to open socket\n"); } else { #if DEBUG_IO printf("open_radio_sound_socket: opened socket %s\n", radio_sound_ip); #endif } } if (radio_sound_mic_ip[0]) { port = QuiskGetConfigInt ("radio_sound_mic_port", 0); samples = QuiskGetConfigInt ("radio_sound_mic_nsamples", 720); if (samples > 734) samples = 734; radio_sound_mic_nshorts = samples + 1; radio_sound_mic_socket = socket(PF_INET, SOCK_DGRAM, 0); if (radio_sound_mic_socket != INVALID_SOCKET) { setsockopt(radio_sound_mic_socket, SOL_SOCKET, SO_SNDBUF, (char *)&sndsize, sizeof(sndsize)); Addr.sin_family = AF_INET; Addr.sin_port = htons(port); #ifdef MS_WINDOWS Addr.sin_addr.S_un.S_addr = inet_addr(radio_sound_mic_ip); #else inet_aton(radio_sound_mic_ip, &Addr.sin_addr); #endif if (connect(radio_sound_mic_socket, (const struct sockaddr *)&Addr, sizeof(Addr)) != 0) { close(radio_sound_mic_socket); radio_sound_mic_socket = INVALID_SOCKET; } } if (radio_sound_mic_socket == INVALID_SOCKET) { printf("open_radio_sound_mic_socket: Failure to open socket\n"); } else { #if DEBUG_IO printf("open_radio_sound_mic_socket: opened socket %s\n", radio_sound_mic_ip); #endif } } } PyObject * quisk_set_sound_name(PyObject * self, PyObject * args) // Called from GUI thread { char * description; // The description from quisk.py char * device_name; // The device name from quisk.py const char * utf8 = "utf-8"; int index, play, driver; Py_ssize_t l1, l2; description = malloc(QUISK_SC_SIZE); device_name = malloc(QUISK_SC_SIZE); l1 = l2 = QUISK_SC_SIZE; if (!PyArg_ParseTuple (args, "iiies#es#", &index, &play, &driver, utf8, &description, &l1, utf8, &device_name, &l2)) return NULL; if (play) { strMcpy(quiskPlaybackDevices[index]->name, description, QUISK_SC_SIZE); strMcpy(quiskPlaybackDevices[index]->device_name, device_name, QUISK_SC_SIZE); quiskPlaybackDevices[index]->driver = driver; } else { strMcpy(CaptureDevices[index]->name, description, QUISK_SC_SIZE); strMcpy(CaptureDevices[index]->device_name, device_name, QUISK_SC_SIZE); CaptureDevices[index]->driver = driver; } free(description); free(device_name); Py_INCREF (Py_None); return Py_None; } static void set_digital_rx() // Set playback devices for digital modes from sub-receivers { int i; char buf80[80]; struct sound_dev * rx_dev; for (i = QUISK_INDEX_SUB_RX1; i < QUISK_INDEX_SUB_RX1 + QUISK_MAX_SUB_RECEIVERS; i++) { rx_dev = quiskPlaybackDevices[i]; rx_dev->dev_errmsg[0] = 0; rx_dev->dev_index = t_DigitalRx1Output; snprintf(buf80, 80, "Digital Rx%d Output", i - QUISK_INDEX_SUB_RX1 + 1); strMcpy(rx_dev->stream_description, buf80, QUISK_SC_SIZE); rx_dev->sample_rate = 48000; rx_dev->channel_I = 0; rx_dev->channel_Q = 1; set_num_channels (rx_dev); rx_dev->average_square = 0; rx_dev->stream_dir_record = 0; rx_dev->read_frames = 0; rx_dev->latency_frames = rx_dev->sample_rate * quisk_sound_state.latency_millisecs / 1000; } } void quisk_open_sound(void) // Called from GUI thread { int i; struct sound_dev * pPlay; quisk_play_state = SHUTDOWN; quisk_sound_state.read_error = 0; quisk_sound_state.write_error = 0; quisk_sound_state.underrun_error = 0; quisk_sound_state.mic_read_error = 0; quisk_sound_state.interrupts = 0; quisk_sound_state.rate_min = quisk_sound_state.rate_max = -99; quisk_sound_state.chan_min = quisk_sound_state.chan_max = -99; quisk_sound_state.msg1[0] = 0; quisk_sound_state.err_msg[0] = 0; Capture.dev_errmsg[0] = 0; quisk_Playback.dev_errmsg[0] = 0; MicCapture.dev_errmsg[0] = 0; quisk_MicPlayback.dev_errmsg[0] = 0; DigitalInput.dev_errmsg[0] = 0; DigitalOutput.dev_errmsg[0] = 0; RawSamplePlayback.dev_errmsg[0] = 0; Capture.dev_index = t_Capture; quisk_Playback.dev_index = t_Playback; MicCapture.dev_index = t_MicCapture; quisk_MicPlayback.dev_index = t_MicPlayback; DigitalInput.dev_index = t_DigitalInput; DigitalOutput.dev_index = t_DigitalOutput; RawSamplePlayback.dev_index = t_RawSamplePlayback; set_digital_rx(); // Set stream descriptions. This is important for "deviceless" drivers like // PulseAudio to be able to distinguish the streams from each other. strMcpy(Capture.stream_description, "I/Q Rx Sample Input", QUISK_SC_SIZE); Capture.stream_description[QUISK_SC_SIZE-1] = '\0'; strMcpy(quisk_Playback.stream_description, "Radio Sound Output", QUISK_SC_SIZE); quisk_Playback.stream_description[QUISK_SC_SIZE-1] = '\0'; strMcpy(MicCapture.stream_description, "Microphone Input", QUISK_SC_SIZE); MicCapture.stream_description[QUISK_SC_SIZE-1] = '\0'; strMcpy(quisk_MicPlayback.stream_description, "I/Q Tx Sample Output", QUISK_SC_SIZE); quisk_MicPlayback.stream_description[QUISK_SC_SIZE-1] = '\0'; strMcpy(DigitalInput.stream_description, "Digital Tx0 Input", QUISK_SC_SIZE); strMcpy(DigitalOutput.stream_description, "Digital Rx0 Output", QUISK_SC_SIZE); strMcpy(RawSamplePlayback.stream_description, "Raw Digital Output", QUISK_SC_SIZE); quisk_Playback.sample_rate = quisk_sound_state.playback_rate; // Radio sound play rate quisk_MicPlayback.sample_rate = quisk_sound_state.mic_playback_rate; MicCapture.sample_rate = quisk_sound_state.mic_sample_rate; MicCapture.channel_I = quisk_sound_state.mic_channel_I; // Mic audio is here MicCapture.channel_Q = quisk_sound_state.mic_channel_Q; // Capture device for digital modes DigitalInput.sample_rate = 48000; DigitalInput.channel_I = 0; DigitalInput.channel_Q = 1; // Playback device for digital modes digital_output_level = QuiskGetConfigDouble("digital_output_level", 0.7); DigitalOutput.sample_rate = quisk_sound_state.playback_rate; // Radio sound play rate DigitalOutput.channel_I = 0; DigitalOutput.channel_Q = 1; // Playback device for raw samples RawSamplePlayback.sample_rate = quisk_sound_state.sample_rate; RawSamplePlayback.channel_I = 0; RawSamplePlayback.channel_Q = 1; file_play_level = QuiskGetConfigDouble("file_play_level", 1.0); set_num_channels (&Capture); set_num_channels (&quisk_Playback); set_num_channels (&MicCapture); set_num_channels (&quisk_MicPlayback); set_num_channels (&DigitalInput); set_num_channels (&DigitalOutput); set_num_channels (&RawSamplePlayback); for (i = 0; (pPlay = quiskPlaybackDevices[i]); i++) { pPlay->started = 0; pPlay->cr_correction = 0; pPlay->cr_delay = 3; pPlay->cr_average_fill = 0; pPlay->cr_average_count = 0; pPlay->cr_sample_time = 0; pPlay->cr_correct_time = 0; } Capture.average_square = 0; quisk_Playback.average_square = 0; MicCapture.average_square = 0; quisk_MicPlayback.average_square = 0; DigitalInput.average_square = 0; DigitalOutput.average_square = 0; RawSamplePlayback.average_square = 0; //Needed for pulse audio context connection (KM4DSJ) Capture.stream_dir_record = 1; quisk_Playback.stream_dir_record = 0; MicCapture.stream_dir_record = 1; quisk_MicPlayback.stream_dir_record= 0; DigitalInput.stream_dir_record = 1; DigitalOutput.stream_dir_record = 0; RawSamplePlayback.stream_dir_record = 0; //For remote IQ server over pulseaudio (KM4DSJ) if (quisk_sound_state.IQ_server[0]) { strMcpy(Capture.server, quisk_sound_state.IQ_server, IP_SIZE); strMcpy(quisk_MicPlayback.server, quisk_sound_state.IQ_server, IP_SIZE); } #ifdef FIX_H101 Capture.channel_Delay = Capture.channel_Q; // Obsolete; do not use. #else Capture.channel_Delay = QuiskGetConfigInt ("channel_delay", -1); #endif quisk_MicPlayback.channel_Delay = QuiskGetConfigInt ("tx_channel_delay", -1); if (pt_sample_read) // capture from SDR-IQ by Rf-Space or UDP Capture.name[0] = 0; // zero the capture soundcard name else // sound card capture Capture.sample_rate = quisk_sound_state.sample_rate; // set read size for sound card capture i = (int)(quisk_sound_state.data_poll_usec * 1e-6 * Capture.sample_rate + 0.5); if (i > SAMP_BUFFER_SIZE / Capture.num_channels) // limit to buffer size i = SAMP_BUFFER_SIZE / Capture.num_channels; Capture.read_frames = i; MicCapture.read_frames = 0; // Use non-blocking read for microphone quisk_Playback.read_frames = 0; quisk_MicPlayback.read_frames = 0; // set sound card play latency quisk_Playback.latency_frames = quisk_Playback.sample_rate * quisk_sound_state.latency_millisecs / 1000; quisk_MicPlayback.latency_frames = quisk_MicPlayback.sample_rate * quisk_sound_state.latency_millisecs / 1000; Capture.latency_frames = 0; MicCapture.latency_frames = 0; // set capture and playback for digital modes DigitalInput.read_frames = 0; // Use non-blocking read DigitalInput.latency_frames = 0; DigitalOutput.read_frames = 0; DigitalOutput.latency_frames = DigitalOutput.sample_rate * quisk_sound_state.latency_millisecs / 1000; // set capture and playback for raw samples RawSamplePlayback.read_frames = 0; RawSamplePlayback.latency_frames = RawSamplePlayback.sample_rate * quisk_sound_state.latency_millisecs / 1000; open_radio_sound_socket(); #if DEBUG_IO printf("Sample buffer size %d, latency msec %d\n", SAMP_BUFFER_SIZE, quisk_sound_state.latency_millisecs); #endif } void quisk_start_sound(void) // Called from sound thread { if (pt_sample_start) (*pt_sample_start)(); // Let the drivers see the devices and start them up if appropriate quisk_start_sound_directx(CaptureDevices, quiskPlaybackDevices); quisk_start_sound_wasapi(CaptureDevices, quiskPlaybackDevices); quisk_start_sound_portaudio(CaptureDevices, quiskPlaybackDevices); quisk_start_sound_pulseaudio(CaptureDevices, quiskPlaybackDevices); quisk_start_sound_alsa(CaptureDevices, quiskPlaybackDevices); if (pt_sample_read) { // Capture from SDR-IQ or UDP quisk_sound_state.rate_min = quisk_Playback.rate_min; quisk_sound_state.rate_max = quisk_Playback.rate_max; quisk_sound_state.chan_min = quisk_Playback.chan_min; quisk_sound_state.chan_max = quisk_Playback.chan_max; } else { // Capture from sound card quisk_sound_state.rate_min = Capture.rate_min; quisk_sound_state.rate_max = Capture.rate_max; quisk_sound_state.chan_min = Capture.chan_min; quisk_sound_state.chan_max = Capture.chan_max; } QuiskDeltaSec(0); // Set timer to zero QuiskDeltaSec(1); quisk_set_play_state(); quisk_play_state = STARTING; } PyObject * quisk_set_ampl_phase(PyObject * self, PyObject * args) // Called from GUI thread { /* Set the sound card amplitude and phase corrections. See S.W. Ellingson, Correcting I-Q Imbalance in Direct Conversion Receivers, February 10, 2003 */ struct sound_dev * dev; double ampl, phase; int is_tx; // Is this for Tx? Otherwise Rx. if (!PyArg_ParseTuple (args, "ddi", &l, &phase, &is_tx)) return NULL; if (is_tx) dev = &quisk_MicPlayback; else dev = &Capture; if (ampl == 0.0 && phase == 0.0) { dev->doAmplPhase = 0; } else { dev->doAmplPhase = 1; ampl = ampl + 1.0; // Change factor 0.01 to 1.01 phase = (phase / 360.0) * 2.0 * M_PI; // convert to radians dev->AmPhAAAA = 1.0 / ampl; dev->AmPhCCCC = - dev->AmPhAAAA * tan(phase); dev->AmPhDDDD = 1.0 / cos(phase); } Py_INCREF (Py_None); return Py_None; } PyObject * quisk_capt_channels(PyObject * self, PyObject * args) // Called from GUI thread { if (!PyArg_ParseTuple (args, "ii", &Capture.channel_I, &Capture.channel_Q)) return NULL; Py_INCREF (Py_None); return Py_None; } PyObject * quisk_play_channels(PyObject * self, PyObject * args) // Called from GUI thread { if (!PyArg_ParseTuple (args, "ii", &quisk_Playback.channel_I, &quisk_Playback.channel_Q)) return NULL; Py_INCREF (Py_None); return Py_None; } PyObject * quisk_micplay_channels(PyObject * self, PyObject * args) // Called from GUI thread { if (!PyArg_ParseTuple (args, "ii", &quisk_MicPlayback.channel_I, &quisk_MicPlayback.channel_Q)) return NULL; Py_INCREF (Py_None); return Py_None; } PyObject * quisk_set_sparams(PyObject * self, PyObject * args, PyObject * keywds) { /* Call with keyword arguments ONLY; change local parameters */ static char * kwlist[] = {"dc_remove_bw", "digital_output_level", "remote_control_slave", "remote_control_head", "file_play_level", "multus_cw_samples", NULL} ; if (!PyArg_ParseTupleAndKeywords (args, keywds, "|idiidi", kwlist, &dc_remove_bw, &digital_output_level, &remote_control_slave, &remote_control_head, &file_play_level, &multus_cw_samples)) return NULL; Py_INCREF (Py_None); return Py_None; } PyObject * quisk_dummy_sound_devices(PyObject * self, PyObject * args) { // Return an empty list of sound devices PyObject * pylist, * pycapt, * pyplay; if (!PyArg_ParseTuple (args, "")) return NULL; pylist = PyList_New(0); // list [pycapt, pyplay] pycapt = PyList_New(0); // list of capture devices pyplay = PyList_New(0); // list of play devices PyList_Append(pylist, pycapt); PyList_Append(pylist, pyplay); return pylist; } void quisk_udp_mic_error(char * msg) { MicCapture.dev_error++; #if DEBUG_IO printf("%s\n", msg); #endif } static void AddCard(struct sound_dev * dev, PyObject * pylist) { PyObject * v; if (dev->name[0]) { v = Py_BuildValue("(NNiiidN)", PyUnicode_DecodeUTF8(dev->stream_description, strlen(dev->stream_description), "replace"), PyUnicode_DecodeUTF8(dev->name, strlen(dev->name), "replace"), dev->sample_rate, dev->dev_latency, dev->dev_error + dev->dev_underrun, dev->average_square, PyUnicode_DecodeUTF8(dev->dev_errmsg, strlen(dev->dev_errmsg), "replace")); PyList_Append(pylist, v); } } PyObject * quisk_sound_errors(PyObject * self, PyObject * args) { // return a list of strings with card names and error counts PyObject * pylist; struct sound_dev * rx_dev; if (!PyArg_ParseTuple (args, "")) return NULL; pylist = PyList_New(0); AddCard(&Capture, pylist); AddCard(&MicCapture, pylist); AddCard(&quisk_Playback, pylist); AddCard(&quisk_MicPlayback, pylist); AddCard(&RawSamplePlayback, pylist); AddCard(&DigitalInput, pylist); AddCard(&DigitalOutput, pylist); rx_dev = quiskPlaybackDevices[QUISK_INDEX_SUB_RX1]; AddCard(rx_dev, pylist); return pylist; } PyObject * quisk_set_file_name(PyObject * self, PyObject * args, PyObject * keywds) // called from GUI { int which = -1; const char * name = NULL; int enable = -1; int play_button = -1; int record_button = -1; static char * kwlist[] = {"which", "name", "enable", "play_button", "record_button", NULL} ; if (!PyArg_ParseTupleAndKeywords (args, keywds, "|isiii", kwlist, &which, &name, &enable, &play_button, &record_button)) return NULL; if (record_button == 0) { // Close all recording files close_file_rec = 1; } if (play_button == 0) { // Close all play files quisk_close_file_play = 1; } if (record_button == 1) switch (which) { case 0: // record audio file if (name) strMcpy(file_rec_audio.file_name, name, QUISK_PATH_SIZE); quisk_record_audio(&file_rec_audio, NULL, -1); break; case 1: // record sample file if (name) strMcpy(file_rec_samples.file_name, name, QUISK_PATH_SIZE); record_samples(&file_rec_samples, NULL, -1); break; case 2: // record mic file if (name) strMcpy(file_rec_mic.file_name, name, QUISK_PATH_SIZE); quisk_record_audio(&file_rec_mic, NULL, -1); break; case 10: // play audio file break; case 11: // play samples file break; case 12: // play CQ message file break; } Py_INCREF (Py_None); return Py_None; }