def _process_audio_samples(self, seq, samples, rssi): if self._options.quiet is False: sys.stdout.write('\rBlock: %08x, RSSI: %6.1f' % (seq, rssi)) sys.stdout.flush() if self._squelch: is_open = self._squelch.process(seq, rssi) if not is_open: self._start_ts = None self._start_time = None return if self._options.resample > 0: if HAS_RESAMPLER: ## libsamplerate resampling if self._resampler is None: self._resampler = Resampler(converter_type='sinc_best') samples = np.round( self._resampler.process( samples, ratio=self._ratio)).astype(np.int16) else: ## resampling by linear interpolation n = len(samples) xa = np.arange(round(n * self._ratio)) / self._ratio xp = np.arange(n) samples = np.round(np.interp(xa, xp, samples)).astype(np.int16) self._write_samples(samples, {})
def _process_audio_samples(self, seq, samples, rssi): if self._options.quiet is False: sys.stdout.write('\rBlock: %08x, RSSI: %6.1f' % (seq, rssi)) sys.stdout.flush() if self._options.resample > 0: if HAS_RESAMPLER: ## libsamplerate resampling if self._resampler is None: self._resampler = Resampler(converter_type='sinc_best') samples = np.round( self._resampler.process( samples, ratio=self._ratio)).astype(np.int16) else: ## resampling by linear interpolation n = len(samples) xa = np.arange(round(n * self._ratio)) / self._ratio xp = np.arange(n) samples = np.round(np.interp(xa, xp, samples)).astype(np.int16) # Convert the int16 samples [-32768,32,767] to the floating point # samples [-1.0,1.0] SoundCard expects fsamples = samples.astype(np.float32) fsamples /= 32768 self._player.play(fsamples)
def _process_iq_samples(self, seq, samples, rssi, gps): if self._options.quiet is False: sys.stdout.write('\rBlock: %08x, RSSI: %6.1f' % (seq, rssi)) sys.stdout.flush() if self._squelch: is_open = self._squelch.process(seq, rssi) if not is_open: self._start_ts = None self._start_time = None return ##print gps['gpsnsec']-self._last_gps['gpsnsec'] self._last_gps = gps if self._options.resample == 0 or HAS_RESAMPLER: ## convert list of complex numbers into an array s = np.ndarray((len(samples),2), dtype=np.float32) s[:, 0] = np.real(samples).astype(np.float32) / 32768 s[:, 1] = np.imag(samples).astype(np.float32) / 32768 if self._options.resample > 0: if HAS_RESAMPLER: ## libsamplerate resampling if self._resampler is None: self._resampler = Resampler(channels=2, converter_type='sinc_best') s = self._resampler.process(s, ratio=self._ratio) else: ## resampling by linear interpolation n = len(samples) m = int(round(n*self._ratio)) xa = np.arange(m)/self._ratio xp = np.arange(n) s = np.ndarray((m,2), dtype=np.float32) s[:, 0] = np.interp(xa, xp, np.real(samples).astype(np.float32) / 32768) s[:, 1] = np.interp(xa, xp, np.imag(samples).astype(np.float32) / 32768) if self._ifreq is not None and self._output_sample_rate >= 4 * self._ifreq: # view as complex after possible resampling - no copying. cs = s.view(dtype=np.complex64) l = len(cs) # get final phase value stopph = self.startph + 2 * np.pi * l * self._ifreq / self._output_sample_rate # all the steps needed steps = 1j*np.linspace(self.startph, stopph, l, endpoint=False, dtype=np.float32) # shift frequency and get back to a 2D array s = (cs * np.exp(steps)[:, None]).view(np.float32) # save phase for next time, modulo 2π self.startph = stopph % (2*np.pi) self._player.play(s) # no GPS or no recent GPS solution last = gps['last_gps_solution'] if last == 255 or last == 254: self._options.status = 3
def _process_iq_samples_raw(self, seq, data): if self._options.quiet is False: sys.stdout.write('\rBlock: %08x' % seq) sys.stdout.flush() n = len(data) // 4 if self._options.resample == 0 or HAS_RESAMPLER: ## convert bytes into an array s = np.ndarray( (n, 2), dtype='>h', buffer=data).astype(np.float32) / 32768 if self._options.resample > 0: if HAS_RESAMPLER: ## libsamplerate resampling if self._resampler is None: self._resampler = Resampler(channels=2, converter_type='sinc_best') s = self._resampler.process(s, ratio=self._ratio) else: ## resampling by linear interpolation m = int(round(n * self._ratio)) xa = np.arange(m) / self._ratio xp = np.arange(n) s = np.ndarray((m, 2), dtype=np.float32) s[:, 0] = np.interp(xa, xp, data[0::2] / 32768) s[:, 1] = np.interp(xa, xp, data[1::2] / 32768) if self._ifreq is not None and self._output_sample_rate >= 4 * self._ifreq: # view as complex after possible resampling - no copying. cs = s.view(dtype=np.complex64) l = len(cs) # get final phase value stopph = self.startph + 2 * np.pi * l * self._ifreq / self._output_sample_rate # all the steps needed steps = 1j * np.linspace( self.startph, stopph, l, endpoint=False, dtype=np.float32) # shift frequency and get back to a 2D array s = (cs * np.exp(steps)[:, None]).view(np.float32) # save phase for next time, modulo 2π self.startph = stopph % (2 * np.pi) self._player.play(s)
def _process_iq_samples(self, seq, samples, rssi, gps): if self._squelch: is_open = self._squelch.process(seq, rssi) if not is_open: self._start_ts = None self._start_time = None return ##print gps['gpsnsec']-self._last_gps['gpsnsec'] self._last_gps = gps ## convert list of complex numbers into an array s = np.zeros(2 * len(samples), dtype=np.int16) s[0::2] = np.real(samples).astype(np.int16) s[1::2] = np.imag(samples).astype(np.int16) if self._options.resample > 0: if HAS_RESAMPLER: ## libsamplerate resampling if self._resampler is None: self._resampler = Resampler(channels=2, converter_type='sinc_best') s = self._resampler.process(s.reshape(len(samples), 2), ratio=self._ratio) s = np.round(s.flatten()).astype(np.int16) else: ## resampling by linear interpolation n = len(samples) m = int(round(n * self._ratio)) xa = np.arange(m) / self._ratio xp = np.arange(n) s = np.zeros(2 * m, dtype=np.int16) s[0::2] = np.round(np.interp(xa, xp, np.real(samples))).astype( np.int16) s[1::2] = np.round(np.interp(xa, xp, np.imag(samples))).astype( np.int16) self._write_samples(s, gps) # no GPS or no recent GPS solution last = gps['last_gps_solution'] if last == 255 or last == 254: self._options.status = 3
def _process_audio_samples(self, seq, samples, rssi): is_open = self._squelch_status(seq, samples, rssi) if not is_open: return if self._options.resample > 0: if HAS_RESAMPLER: ## libsamplerate resampling if self._resampler is None: self._resampler = Resampler(converter_type='sinc_best') samples = np.round( self._resampler.process( samples, ratio=self._ratio)).astype(np.int16) else: ## resampling by linear interpolation n = len(samples) xa = np.arange(round(n * self._ratio)) / self._ratio xp = np.arange(n) samples = np.round(np.interp(xa, xp, samples)).astype(np.int16) self._write_samples(samples, {})
class KiwiSoundRecorder(KiwiSDRStream): def __init__(self, options): super(KiwiSoundRecorder, self).__init__() self._options = options self._type = 'SND' freq = options.frequency #logging.info("%s:%s freq=%d" % (options.server_host, options.server_port, freq)) self._freq = freq self._start_ts = None self._start_time = None self._squelch = Squelch( self._options) if options.thresh is not None else None self._num_channels = 2 if options.modulation == 'iq' else 1 self._last_gps = dict( zip(['last_gps_solution', 'dummy', 'gpssec', 'gpsnsec'], [0, 0, 0, 0])) self._resampler = None self._gnss_performance = GNSSPerformance() def _setup_rx_params(self): if self._options.no_api: if self._options.user != 'kiwirecorder.py': self.set_name(self._options.user) return self.set_name(self._options.user) mod = self._options.modulation lp_cut = self._options.lp_cut hp_cut = self._options.hp_cut if mod == 'am': # For AM, ignore the low pass filter cutoff lp_cut = -hp_cut if hp_cut is not None else hp_cut self.set_mod(mod, lp_cut, hp_cut, self._freq) if self._options.agc_gain != None: ## fixed gain (no AGC) self.set_agc(on=False, gain=self._options.agc_gain) elif self._options.agc_yaml_file != None: ## custon AGC parameters from YAML file self.set_agc(**self._options.agc_yaml) else: ## default is AGC ON (with default parameters) self.set_agc(on=True) if self._options.compression is False: self._set_snd_comp(False) if self._options.nb is True: gate = self._options.nb_gate if gate < 100 or gate > 5000: gate = 100 thresh = self._options.nb_thresh if thresh < 0 or thresh > 100: thresh = 50 self.set_noise_blanker(gate, thresh) self._output_sample_rate = self._sample_rate if self._squelch: self._squelch.set_sample_rate(self._sample_rate) if self._options.resample > 0: if not HAS_RESAMPLER: self._output_sample_rate = self._options.resample self._ratio = float( self._output_sample_rate) / self._sample_rate logging.info( "libsamplerate not available: linear interpolation is used for low-quality resampling. " "(pip/pip3 install samplerate)") logging.info( 'resampling from %g to %d Hz (ratio=%f)' % (self._sample_rate, self._options.resample, self._ratio)) else: fs = 10 * round(self._sample_rate / 10) ## rounded sample rate ratio = self._options.resample / fs ## work around a bug in python-libsamplerate: ## the following makes sure that ratio * 512 is an integer ## at the expense of resampling frequency precision for some resampling frequencies (it's ok for 375 Hz) n = 512 ## KiwiSDR block length for samples m = round(ratio * n) self._ratio = m / n self._output_sample_rate = self._ratio * self._sample_rate logging.info( 'resampling from %g to %g Hz (ratio=%f)' % (self._sample_rate, self._output_sample_rate, self._ratio)) def _process_audio_samples(self, seq, samples, rssi): if self._options.quiet is False: sys.stdout.write('\rBlock: %08x, RSSI: %6.1f' % (seq, rssi)) sys.stdout.flush() if self._squelch: is_open = self._squelch.process(seq, rssi) if not is_open: self._start_ts = None self._start_time = None return if self._options.resample > 0: if HAS_RESAMPLER: ## libsamplerate resampling if self._resampler is None: self._resampler = Resampler(converter_type='sinc_best') samples = np.round( self._resampler.process( samples, ratio=self._ratio)).astype(np.int16) else: ## resampling by linear interpolation n = len(samples) xa = np.arange(round(n * self._ratio)) / self._ratio xp = np.arange(n) samples = np.round(np.interp(xa, xp, samples)).astype(np.int16) self._write_samples(samples, {}) def _process_iq_samples(self, seq, samples, rssi, gps): if self._squelch: is_open = self._squelch.process(seq, rssi) if not is_open: self._start_ts = None self._start_time = None return ##print gps['gpsnsec']-self._last_gps['gpsnsec'] self._last_gps = gps ## convert list of complex numbers into an array s = np.zeros(2 * len(samples), dtype=np.int16) s[0::2] = np.real(samples).astype(np.int16) s[1::2] = np.imag(samples).astype(np.int16) if self._options.resample > 0: if HAS_RESAMPLER: ## libsamplerate resampling if self._resampler is None: self._resampler = Resampler(channels=2, converter_type='sinc_best') s = self._resampler.process(s.reshape(len(samples), 2), ratio=self._ratio) s = np.round(s.flatten()).astype(np.int16) else: ## resampling by linear interpolation n = len(samples) m = int(round(n * self._ratio)) xa = np.arange(m) / self._ratio xp = np.arange(n) s = np.zeros(2 * m, dtype=np.int16) s[0::2] = np.round(np.interp(xa, xp, np.real(samples))).astype( np.int16) s[1::2] = np.round(np.interp(xa, xp, np.imag(samples))).astype( np.int16) self._write_samples(s, gps) # no GPS or no recent GPS solution last = gps['last_gps_solution'] if last == 255 or last == 254: self._options.status = 3 def _get_output_filename(self): if self._options.test_mode: return os.devnull station = '' if self._options.station is None else '_' + self._options.station # if multiple connections specified but not distinguished via --station then use index if self._options.multiple_connections and self._options.station is None: station = '_%d' % self._options.idx if self._options.filename != '': filename = '%s%s.wav' % (self._options.filename, station) else: ts = time.strftime('%Y%m%dT%H%M%SZ', self._start_ts) filename = '%s_%d%s_%s.wav' % (ts, int( self._freq * 1000), station, self._options.modulation) if self._options.dir is not None: filename = '%s/%s' % (self._options.dir, filename) return filename def _update_wav_header(self): with open(self._get_output_filename(), 'r+b') as fp: fp.seek(0, os.SEEK_END) filesize = fp.tell() fp.seek(0, os.SEEK_SET) # fp.tell() sometimes returns zero. _write_wav_header writes filesize - 8 if filesize >= 8: _write_wav_header(fp, filesize, int(self._output_sample_rate), self._num_channels, self._options.is_kiwi_wav) def _write_samples(self, samples, *args): """Output to a file on the disk.""" now = time.gmtime() sec_of_day = lambda x: 3600 * x.tm_hour + 60 * x.tm_min + x.tm_sec dt_reached = self._options.dt != 0 and self._start_ts is not None and sec_of_day( now) // self._options.dt != sec_of_day( self._start_ts) // self._options.dt if self._start_ts is None or (self._options.filename == '' and dt_reached): self._start_ts = now self._start_time = time.time() # Write a static WAV header with open(self._get_output_filename(), 'wb') as fp: _write_wav_header(fp, 100, int(self._output_sample_rate), self._num_channels, self._options.is_kiwi_wav) if self._options.is_kiwi_tdoa: # NB: MUST be a print (i.e. not a logging.info) print("file=%d %s" % (self._options.idx, self._get_output_filename())) else: logging.info("Started a new file: %s" % self._get_output_filename()) with open(self._get_output_filename(), 'ab') as fp: if self._options.is_kiwi_wav: gps = args[0] self._gnss_performance.analyze(self._get_output_filename(), gps) fp.write( struct.pack('<4sIBBII', b'kiwi', 10, gps['last_gps_solution'], 0, gps['gpssec'], gps['gpsnsec'])) sample_size = samples.itemsize * len(samples) fp.write(struct.pack('<4sI', b'data', sample_size)) # TODO: something better than that samples.tofile(fp) self._update_wav_header() def _on_gnss_position(self, pos): pos_record = False if self._options.dir is not None: pos_dir = self._options.dir pos_record = True else: if os.path.isdir('gnss_pos'): pos_dir = 'gnss_pos' pos_record = True if pos_record: station = 'kiwi_noname' if self._options.station is None else self._options.station pos_filename = pos_dir + '/' + station + '.txt' with open(pos_filename, 'w') as f: station = station.replace('-', '_') # since Octave var name f.write( "d.%s = struct('coord', [%f,%f], 'host', '%s', 'port', %d);\n" % (station, pos[0], pos[1], self._options.server_host, self._options.server_port))
class KiwiSoundRecorder(KiwiSDRStream): def __init__(self, options): super(KiwiSoundRecorder, self).__init__() self._options = options self._type = 'SND' freq = options.frequency options.S_meter = False #logging.info("%s:%s freq=%d" % (options.server_host, options.server_port, freq)) self._freq = freq self._modulation = self._options.modulation self._lowcut = self._options.lp_cut self._highcut = self._options.hp_cut self._start_ts = None self._start_time = None self._squelch = Squelch( self._options) if options.thresh is not None else None self._num_channels = 2 if options.modulation == 'iq' else 1 self._last_gps = dict( zip(['last_gps_solution', 'dummy', 'gpssec', 'gpsnsec'], [0, 0, 0, 0])) self._resampler = None self._output_sample_rate = 0 def _init_player(self): if hasattr(self, 'player'): self._player.__exit__(exc_type=None, exc_value=None, traceback=None) options = self._options speaker = sc.get_speaker(options.sounddevice) rate = self._output_sample_rate if speaker is None: if options.sounddevice is None: print('Using default sound device. Specify --sound-device?') options.sounddevice = 'default' else: print("Could not find %s, using default", options.sounddevice) speaker = sc.default_speaker() # pulseaudio has sporadic failures, retry a few times for i in range(0, 10): try: self._player = speaker.player(samplerate=rate, blocksize=4096) self._player.__enter__() break except Exception as ex: print("speaker.player failed with ", ex) time.sleep(0.1) pass def _setup_rx_params(self): self.set_name(self._options.user) lowcut = self._lowcut if self._modulation == 'am': # For AM, ignore the low pass filter cutoff lowcut = -self._highcut if lowcut is not None else lowcut self.set_mod(self._modulation, lowcut, self._highcut, self._freq) if self._options.agc_gain != None: self.set_agc(on=False, gain=self._options.agc_gain) else: self.set_agc(on=True) if self._options.compression is False: self._set_snd_comp(False) if self._options.nb is True: gate = self._options.nb_gate if gate < 100 or gate > 5000: gate = 100 thresh = self._options.nb_thresh if thresh < 0 or thresh > 100: thresh = 50 self.set_noise_blanker(gate, thresh) self._output_sample_rate = int(self._sample_rate) if self._options.resample > 0: self._output_sample_rate = self._options.resample self._ratio = float(self._output_sample_rate) / self._sample_rate logging.info( 'resampling from %g to %d Hz (ratio=%f)' % (self._sample_rate, self._options.resample, self._ratio)) if not HAS_RESAMPLER: logging.info( "libsamplerate not available: linear interpolation is used for low-quality resampling. " "(pip install samplerate)") self._init_player() def _process_audio_samples(self, seq, samples, rssi): if self._options.quiet is False: sys.stdout.write('\rBlock: %08x, RSSI: %6.1f' % (seq, rssi)) sys.stdout.flush() if self._options.resample > 0: if HAS_RESAMPLER: ## libsamplerate resampling if self._resampler is None: self._resampler = Resampler(converter_type='sinc_best') samples = np.round( self._resampler.process( samples, ratio=self._ratio)).astype(np.int16) else: ## resampling by linear interpolation n = len(samples) xa = np.arange(round(n * self._ratio)) / self._ratio xp = np.arange(n) samples = np.round(np.interp(xa, xp, samples)).astype(np.int16) # Convert the int16 samples [-32768,32,767] to the floating point # samples [-1.0,1.0] SoundCard expects fsamples = samples.astype(np.float32) fsamples /= 32768 self._player.play(fsamples) def _on_sample_rate_change(self): if self._options.resample is 0: # if self._output_sample_rate == int(self._sample_rate): # return # reinitialize player if the playback sample rate changed self._output_sample_rate = int(self._sample_rate) self._init_player()
class KiwiSoundRecorder(KiwiSDRStream): def __init__(self, options): super(KiwiSoundRecorder, self).__init__() self._options = options self._type = 'SND' freq = options.frequency options.S_meter = False options.stats = False #logging.info("%s:%s freq=%d" % (options.server_host, options.server_port, freq)) self._freq = freq self._ifreq = options.ifreq self._modulation = self._options.modulation self._lowcut = self._options.lp_cut self._highcut = self._options.hp_cut self._start_ts = None self._start_time = None self._squelch = Squelch(self._options) if options.thresh is not None else None self._last_gps = dict(zip(['last_gps_solution', 'dummy', 'gpssec', 'gpsnsec'], [0,0,0,0])) self._resampler = None self._output_sample_rate = 0 def _init_player(self): if hasattr(self, 'player'): self._player.__exit__(exc_type=None, exc_value=None, traceback=None) options = self._options speaker = sc.get_speaker(options.sounddevice) rate = self._output_sample_rate if speaker is None: if options.sounddevice is None: print('Using default sound device. Specify --sound-device?') options.sounddevice = 'default' else: print("Could not find %s, using default", options.sounddevice) speaker = sc.default_speaker() # pulseaudio has sporadic failures, retry a few times for i in range(0,10): try: self._player = speaker.player(samplerate=rate) self._player.__enter__() break except Exception as ex: print("speaker.player failed with ", ex) time.sleep(0.1) pass def _setup_rx_params(self): self.set_name(self._options.user) lowcut = self._lowcut if self._modulation == 'am': # For AM, ignore the low pass filter cutoff lowcut = -self._highcut if lowcut is not None else lowcut self.set_mod(self._modulation, lowcut, self._highcut, self._freq) if self._options.agc_gain != None: self.set_agc(on=False, gain=self._options.agc_gain) else: self.set_agc(on=True) if self._options.compression is False: self._set_snd_comp(False) if self._options.nb is True: gate = self._options.nb_gate if gate < 100 or gate > 5000: gate = 100 thresh = self._options.nb_thresh if thresh < 0 or thresh > 100: thresh = 50 self.set_noise_blanker(gate, thresh) if self._options.de_emp is True: self.set_de_emp(1) self._output_sample_rate = int(self._sample_rate) if self._options.resample > 0: self._output_sample_rate = self._options.resample self._ratio = float(self._output_sample_rate)/self._sample_rate logging.info('resampling from %g to %d Hz (ratio=%f)' % (self._sample_rate, self._options.resample, self._ratio)) if not HAS_RESAMPLER: logging.info("libsamplerate not available: linear interpolation is used for low-quality resampling. " "(pip/pip3 install samplerate)") if self._ifreq is not None: if self._modulation != 'iq': logging.warning('Option --if %.1f only valid for IQ modulation, ignored' % self._ifreq) elif self._output_sample_rate < self._ifreq * 4: logging.warning('Sample rate %.1f is not enough for --if %.1f, ignored. Use --resample %.1f' % ( self._output_sample_rate, self._ifreq, self._ifreq * 4)) self._init_player() def _process_audio_samples(self, seq, samples, rssi): if self._options.quiet is False: sys.stdout.write('\rBlock: %08x, RSSI: %6.1f' % (seq, rssi)) sys.stdout.flush() if self._options.resample > 0: if HAS_RESAMPLER: ## libsamplerate resampling if self._resampler is None: self._resampler = Resampler(converter_type='sinc_best') samples = np.round(self._resampler.process(samples, ratio=self._ratio)).astype(np.int16) else: ## resampling by linear interpolation n = len(samples) xa = np.arange(round(n*self._ratio))/self._ratio xp = np.arange(n) samples = np.round(np.interp(xa,xp,samples)).astype(np.int16) # Convert the int16 samples [-32768,32,767] to the floating point # samples [-1.0,1.0] SoundCard expects fsamples = samples.astype(np.float32) fsamples /= 32768 self._player.play(fsamples) def _process_iq_samples_raw(self, seq, data): if self._options.quiet is False: sys.stdout.write('\rBlock: %08x' % seq) sys.stdout.flush() n = len(data)//4 if self._options.resample == 0 or HAS_RESAMPLER: ## convert bytes into an array s = np.ndarray((n,2), dtype='>h', buffer=data).astype(np.float32) / 32768 if self._options.resample > 0: if HAS_RESAMPLER: ## libsamplerate resampling if self._resampler is None: self._resampler = Resampler(channels=2, converter_type='sinc_best') s = self._resampler.process(s, ratio=self._ratio) else: ## resampling by linear interpolation m = int(round(n*self._ratio)) xa = np.arange(m)/self._ratio xp = np.arange(n) s = np.ndarray((m,2), dtype=np.float32) s[:, 0] = np.interp(xa, xp, data[0::2] / 32768) s[:, 1] = np.interp(xa, xp, data[1::2] / 32768) if self._ifreq is not None and self._output_sample_rate >= 4 * self._ifreq: # view as complex after possible resampling - no copying. cs = s.view(dtype=np.complex64) l = len(cs) # get final phase value stopph = self.startph + 2 * np.pi * l * self._ifreq / self._output_sample_rate # all the steps needed steps = 1j*np.linspace(self.startph, stopph, l, endpoint=False, dtype=np.float32) # shift frequency and get back to a 2D array s = (cs * np.exp(steps)[:, None]).view(np.float32) # save phase for next time, modulo 2π self.startph = stopph % (2*np.pi) self._player.play(s) # phase for frequency shift startph = np.float32(0) def _process_iq_samples(self, seq, samples, rssi, gps): if self._options.quiet is False: sys.stdout.write('\rBlock: %08x, RSSI: %6.1f' % (seq, rssi)) sys.stdout.flush() if self._squelch: is_open = self._squelch.process(seq, rssi) if not is_open: self._start_ts = None self._start_time = None return ##print gps['gpsnsec']-self._last_gps['gpsnsec'] self._last_gps = gps if self._options.resample == 0 or HAS_RESAMPLER: ## convert list of complex numbers into an array s = np.ndarray((len(samples),2), dtype=np.float32) s[:, 0] = np.real(samples).astype(np.float32) / 32768 s[:, 1] = np.imag(samples).astype(np.float32) / 32768 if self._options.resample > 0: if HAS_RESAMPLER: ## libsamplerate resampling if self._resampler is None: self._resampler = Resampler(channels=2, converter_type='sinc_best') s = self._resampler.process(s, ratio=self._ratio) else: ## resampling by linear interpolation n = len(samples) m = int(round(n*self._ratio)) xa = np.arange(m)/self._ratio xp = np.arange(n) s = np.ndarray((m,2), dtype=np.float32) s[:, 0] = np.interp(xa, xp, np.real(samples).astype(np.float32) / 32768) s[:, 1] = np.interp(xa, xp, np.imag(samples).astype(np.float32) / 32768) if self._ifreq is not None and self._output_sample_rate >= 4 * self._ifreq: # view as complex after possible resampling - no copying. cs = s.view(dtype=np.complex64) l = len(cs) # get final phase value stopph = self.startph + 2 * np.pi * l * self._ifreq / self._output_sample_rate # all the steps needed steps = 1j*np.linspace(self.startph, stopph, l, endpoint=False, dtype=np.float32) # shift frequency and get back to a 2D array s = (cs * np.exp(steps)[:, None]).view(np.float32) # save phase for next time, modulo 2π self.startph = stopph % (2*np.pi) self._player.play(s) # no GPS or no recent GPS solution last = gps['last_gps_solution'] if last == 255 or last == 254: self._options.status = 3 def _on_sample_rate_change(self): if self._options.resample == 0: # if self._output_sample_rate == int(self._sample_rate): # return # reinitialize player if the playback sample rate changed self._output_sample_rate = int(self._sample_rate) self._init_player()