def __init__(self, angle=0.0): gr.hier_block2.__init__( self, 'SimulatedDevice VOR modulator', gr.io_signature(1, 1, gr.sizeof_float * 1), gr.io_signature(1, 1, gr.sizeof_gr_complex * 1), ) self.__angle = 0.0 # dummy statically visible value will be overwritten # TODO: My signal level parameters are probably wrong because this signal doesn't look like a real VOR signal vor_30 = analog.sig_source_f(self.__audio_rate, analog.GR_COS_WAVE, self.__vor_sig_freq, 1, 0) vor_add = blocks.add_cc(1) vor_audio = blocks.add_ff(1) # Audio/AM signal self.connect( vor_30, blocks.multiply_const_ff(0.3), # M_n (vor_audio, 0)) self.connect( self, blocks.multiply_const_ff(audio_modulation_index), # M_i (vor_audio, 1)) # Carrier component self.connect(analog.sig_source_c(0, analog.GR_CONST_WAVE, 0, 0, 1), (vor_add, 0)) # AM component self.__delay = blocks.delay(gr.sizeof_gr_complex, 0) # configured by set_angle self.connect( vor_audio, make_resampler(self.__audio_rate, self.__rf_rate ), # TODO make a complex version and do this last blocks.float_to_complex(1), self.__delay, (vor_add, 1)) # FM component vor_fm_mult = blocks.multiply_cc(1) self.connect( # carrier generation analog.sig_source_f(self.__rf_rate, analog.GR_COS_WAVE, fm_subcarrier, 1, 0), blocks.float_to_complex(1), (vor_fm_mult, 1)) self.connect( # modulation vor_30, make_resampler(self.__audio_rate, self.__rf_rate), analog.frequency_modulator_fc(2 * math.pi * fm_deviation / self.__rf_rate), blocks.multiply_const_cc(0.3), # M_d vor_fm_mult, (vor_add, 2)) self.connect(vor_add, self) # calculate and initialize delay self.set_angle(angle)
def __init__(self, angle=0.0): gr.hier_block2.__init__( self, 'SimulatedDevice VOR modulator', gr.io_signature(1, 1, gr.sizeof_float * 1), gr.io_signature(1, 1, gr.sizeof_gr_complex * 1), ) self.__angle = 0.0 # dummy statically visible value will be overwritten # TODO: My signal level parameters are probably wrong because this signal doesn't look like a real VOR signal vor_30 = analog.sig_source_f(self.__audio_rate, analog.GR_COS_WAVE, self.__vor_sig_freq, 1, 0) vor_add = blocks.add_cc(1) vor_audio = blocks.add_ff(1) # Audio/AM signal self.connect( vor_30, blocks.multiply_const_ff(0.3), # M_n (vor_audio, 0)) self.connect( self, blocks.multiply_const_ff(audio_modulation_index), # M_i (vor_audio, 1)) # Carrier component self.connect( analog.sig_source_c(0, analog.GR_CONST_WAVE, 0, 0, 1), (vor_add, 0)) # AM component self.__delay = blocks.delay(gr.sizeof_gr_complex, 0) # configured by set_angle self.connect( vor_audio, make_resampler(self.__audio_rate, self.__rf_rate), # TODO make a complex version and do this last blocks.float_to_complex(1), self.__delay, (vor_add, 1)) # FM component vor_fm_mult = blocks.multiply_cc(1) self.connect( # carrier generation analog.sig_source_f(self.__rf_rate, analog.GR_COS_WAVE, fm_subcarrier, 1, 0), blocks.float_to_complex(1), (vor_fm_mult, 1)) self.connect( # modulation vor_30, make_resampler(self.__audio_rate, self.__rf_rate), analog.frequency_modulator_fc(2 * math.pi * fm_deviation / self.__rf_rate), blocks.multiply_const_cc(0.3), # M_d vor_fm_mult, (vor_add, 2)) self.connect( vor_add, self) # calculate and initialize delay self.set_angle(angle)
def __init__(self, **kwargs): demod_rate = 48000 SimpleAudioDemodulator.__init__(self, demod_rate=demod_rate, band_filter=5000, band_filter_transition=5000, **kwargs) input_rate = self.input_rate audio_rate = self.audio_rate inherent_gain = 0.5 # fudge factor so that our output is similar level to narrow FM self.agc_block = analog.feedforward_agc_cc(int(.02 * demod_rate), inherent_gain) self.demod_block = blocks.complex_to_mag(1) self.resampler_block = make_resampler(demod_rate, audio_rate) # assuming below 40Hz is not of interest dc_blocker = grfilter.dc_blocker_ff(audio_rate // 40, False) self.connect( self, self.band_filter_block, self.rf_squelch_block, self.agc_block, self.demod_block, dc_blocker, self.resampler_block) self.connect(self.band_filter_block, self.rf_probe_block) self.connect_audio_output(self.resampler_block, self.resampler_block)
def __init__(self, mode, input_rate=0, aprs_information=None, context=None): assert input_rate > 0 gr.hier_block2.__init__( self, str(mode) + ' (FM + Multimon-NG) demodulator', gr.io_signature(1, 1, gr.sizeof_gr_complex * 1), gr.io_signature(1, 1, gr.sizeof_float * 1), ) self.mode = mode self.input_rate = input_rate # FM demod # TODO: Retry telling the NFMDemodulator to have its output rate be pipe_rate instead of using a resampler. Something went wrong when trying that before. self.fm_demod = NFMDemodulator( mode='NFM', input_rate=input_rate, no_audio_filter=True, # don't remove CTCSS tone tau=None) # no deemphasis assert self.fm_demod.get_output_type().get_kind() == 'MONO' fm_audio_rate = self.fm_demod.get_output_type().get_sample_rate() # Subprocess self.mm_demod = APRSDemodulator(aprs_information=aprs_information) mm_audio_rate = self.mm_demod.get_input_type().get_sample_rate() # Output self.connect(self, self.fm_demod, make_resampler(fm_audio_rate, mm_audio_rate), self.mm_demod, self)
def __init__(self, mode, input_rate=0, audio_rate=0, context=None): assert input_rate > 0 gr.hier_block2.__init__( self, str(mode) + " (Multimon-NG) demodulator", gr.io_signature(1, 1, gr.sizeof_gr_complex * 1), # TODO: Add generic support for demodulators with no audio output gr.io_signature(2, 2, gr.sizeof_float * 1), ) self.mode = mode self.input_rate = input_rate # FM demod self.fm_demod = NFMDemodulator( mode="NFM", input_rate=input_rate, audio_rate=pipe_rate, tau=None ) # no deemphasis # Subprocess self.process = SubprocessSink( args=["multimon-ng", "-t", "raw", "-a", "AFSK1200", "-A", "-v", "10", "-"], # args=['python', '../play16bit.py'], itemsize=gr.sizeof_short, ) # Output converter = blocks.float_to_short(vlen=1, scale=int_scale) self.connect(self, self.fm_demod, converter, self.process) # Dummy sink for useless stereo output of demod self.connect((self.fm_demod, 1), blocks.null_sink(gr.sizeof_float)) # Audio copy output resampler = make_resampler(pipe_rate, audio_rate) self.connect(converter, blocks.short_to_float(vlen=1, scale=int_scale), resampler) # self.connect(self.fm_demod, resampler) self.connect(resampler, (self, 0)) self.connect(resampler, (self, 1))
def __init__(self, modulator, audio_rate, rf_rate, freq): modulator = IModulator(modulator) gr.hier_block2.__init__( self, 'SimulatedChannel', gr.io_signature(1, 1, gr.sizeof_float * 1), gr.io_signature(1, 1, gr.sizeof_gr_complex * 1), ) self.__freq = freq self.__rf_rate = rf_rate self.modulator = modulator # exported modulator_input_type = modulator.get_input_type() if modulator_input_type.get_kind() == 'MONO': audio_resampler = make_resampler(audio_rate, modulator_input_type.get_sample_rate()) self.connect(self, audio_resampler, modulator) elif modulator_input_type.get_kind() == 'NONE': self.connect(self, blocks.null_sink(gr.sizeof_float)) else: raise Exception('don\'t know how to supply input of type %s' % modulator_input_type) rf_resampler = rational_resampler.rational_resampler_ccf( interpolation=int(rf_rate), decimation=int(modulator.get_output_type().get_sample_rate())) self.__rotator = blocks.rotator_cc(rotator_inc(rate=rf_rate, shift=freq)) self.__mult = blocks.multiply_const_cc(dB(-10)) self.connect(modulator, rf_resampler, self.__rotator, self.__mult, self)
def __init__(self, mode, input_rate=0, aprs_information=None, context=None): assert input_rate > 0 gr.hier_block2.__init__( self, str(mode) + ' (FM + Multimon-NG) demodulator', gr.io_signature(1, 1, gr.sizeof_gr_complex * 1), gr.io_signature(1, 1, gr.sizeof_float * 1), ) self.mode = mode self.input_rate = input_rate # FM demod # TODO: Retry telling the NFMDemodulator to have its output rate be pipe_rate instead of using a resampler. Something went wrong when trying that before. self.fm_demod = NFMDemodulator( mode='NFM', input_rate=input_rate, no_audio_filter=True, # don't remove CTCSS tone tau=None) # no deemphasis assert self.fm_demod.get_output_type().get_kind() == 'MONO' fm_audio_rate = self.fm_demod.get_output_type().get_sample_rate() # Subprocess self.mm_demod = APRSDemodulator( aprs_information=aprs_information) mm_audio_rate = self.mm_demod.get_input_type().get_sample_rate() # Output self.connect( self, self.fm_demod, make_resampler(fm_audio_rate, mm_audio_rate), self.mm_demod, self)
def _make_resampler(self, input_port, input_rate): taps = design_lofi_audio_filter(input_rate, self.__no_audio_filter) if self.audio_rate == input_rate: filt = grfilter.fir_filter_fff(1, taps) self.connect(input_port, filt) return filt elif input_rate % self.audio_rate == 0: filt = grfilter.fir_filter_fff(input_rate // self.audio_rate, taps) self.connect(input_port, filt) return filt else: # TODO: use combined filter and resampler (need to move filter design) filt = grfilter.fir_filter_fff(1, taps) resampler = make_resampler(input_rate, self.audio_rate) self.connect(input_port, filt, resampler) return resampler
def __init__(self, modulator, audio_rate, rf_rate, freq): modulator = IModulator(modulator) gr.hier_block2.__init__( self, 'SimulatedChannel', gr.io_signature(1, 1, gr.sizeof_float * 1), gr.io_signature(1, 1, gr.sizeof_gr_complex * 1), ) self.__freq = freq self.__rf_rate = rf_rate self.modulator = modulator # exported audio_resampler = make_resampler(audio_rate, modulator.get_input_type().get_sample_rate()) rf_resampler = rational_resampler.rational_resampler_ccf( interpolation=int(rf_rate), decimation=int(modulator.get_output_type().get_sample_rate())) self.__rotator = blocks.rotator_cc(rotator_inc(rate=rf_rate, shift=freq)) self.__mult = blocks.multiply_const_cc(10.0 ** -1) self.connect(self, audio_resampler, modulator, rf_resampler, self.__rotator, self.__mult, self)
def _make_resampler(self): return make_resampler(self.post_demod_rate, self.audio_rate)
def _do_connect(self): """Do all reconfiguration operations in the proper order.""" rate_changed = False if self.source is not self._sources[self.source_name]: print 'Switching source' self.__needs_reconnect = True def tune_hook(): reactor.callLater(self.source.get_tune_delay(), tune_hook_actual) def tune_hook_actual(): if self.source is not this_source: return freq = this_source.get_freq() self.input_freq = freq for key, receiver in self._receivers.iteritems(): receiver.set_input_center_freq(freq) self._update_receiver_validity(key) this_source = self._sources[self.source_name] this_source.set_tune_hook(tune_hook) self.source = this_source this_rate = this_source.get_sample_rate() rate_changed = self.input_rate != this_rate self.input_rate = this_rate self.input_freq = this_source.get_freq() if self.__needs_spectrum or rate_changed: print 'Rebuilding spectrum FFT' self.__needs_spectrum = False self.__needs_reconnect = True self.spectrum_queue = gr.msg_queue(limit=10) self.spectrum_sink = blocks.message_sink( self.spectrum_resolution * gr.sizeof_float, self.spectrum_queue, True) # dont_block self.spectrum_fft_block = gnuradio.fft.logpwrfft.logpwrfft_c( sample_rate=self.input_rate, fft_size=self.spectrum_resolution, ref_scale=2, frame_rate=self.spectrum_rate, avg_alpha=1.0, average=False, ) # adjust units so displayed level is independent of resolution (log power per bandwidth rather than per bin) # TODO work out and document exactly what units we're using self.spectrum_rescale_block = blocks.add_const_vff( [10*math.log10(self.spectrum_resolution)] * self.spectrum_resolution) if rate_changed: print 'Changing sample rate' for receiver in self._receivers.itervalues(): receiver.set_input_rate(self.input_rate) if self.__needs_reconnect: print 'Reconnecting' self.__needs_reconnect = False self._recursive_lock() self.disconnect_all() # recreated each time because reusing an add_ff w/ different # input counts fails; TODO: report/fix bug audio_sum_l = blocks.add_ff() audio_sum_r = blocks.add_ff() self.connect( self.source, self.spectrum_fft_block, self.spectrum_rescale_block, self.spectrum_sink) audio_sum_index = 0 for key, receiver in self._receivers.iteritems(): self._receiver_valid[key] = receiver.get_is_valid() if self._receiver_valid[key]: if audio_sum_index >= 6: # Sanity-check to avoid burning arbitrary resources # TODO: less arbitrary constant; communicate this restriction to client print 'Refusing to connect more than 6 receivers' break self.connect(self.source, receiver) self.connect((receiver, 0), (audio_sum_l, audio_sum_index)) self.connect((receiver, 1), (audio_sum_r, audio_sum_index)) audio_sum_index += 1 if audio_sum_index > 0: # connect audio output only if there is at least one input if len(self.audio_queue_sinks) > 0: used_resamplers = set() for (queue_rate, interleaver, sink) in self.audio_queue_sinks.itervalues(): if queue_rate == self.audio_rate: self.connect(audio_sum_l, (interleaver, 0)) self.connect(audio_sum_r, (interleaver, 1)) else: if queue_rate not in self.audio_resampler_cache: # Moderately expensive due to the internals using optfir print 'Constructing resampler for audio rate', queue_rate self.audio_resampler_cache[queue_rate] = ( make_resampler(self.audio_rate, queue_rate), make_resampler(self.audio_rate, queue_rate) ) resamplers = self.audio_resampler_cache[queue_rate] used_resamplers.add(resamplers) self.connect(resamplers[0], (interleaver, 0)) self.connect(resamplers[1], (interleaver, 1)) self.connect(interleaver, sink) for resamplers in used_resamplers: self.connect(audio_sum_l, resamplers[0]) self.connect(audio_sum_r, resamplers[1]) else: # no stream sinks, gnuradio requires a dummy sink self.connect(audio_sum_l, blocks.null_sink(gr.sizeof_float)) self.connect(audio_sum_r, blocks.null_sink(gr.sizeof_float)) self._recursive_unlock() print 'Done reconnecting'
def connect(self, inputs, outputs): ''' Make all new connections (graph.disconnect_all() must have been done) between inputs and outputs. inputs and outputs must be iterables of (sample_rate, block) tuples. ''' inputs = list(inputs) outputs = list(outputs) # Determine bus rate. # The bus obviously does not need to be higher than the rate of any bus input, because that would be extraneous data. It also does not need to be higher than the rate of any bus output, because no output has use for the information. if len(inputs) > 0 and len(outputs) > 0: max_in_rate = max((rate for rate, _ in inputs)) max_out_rate = max((rate for rate, _ in outputs)) new_bus_rate = min(max_out_rate, max_in_rate) if new_bus_rate != self.__bus_rate: self.__bus_rate = new_bus_rate self.__resampler_cache.clear() # recreated each time because reusing an add_ff w/ different # input counts fails; TODO: report/fix bug bus_sums = [blocks.add_ff() for _ in self.__channels] in_index = 0 for in_rate, in_block in inputs: if in_rate == self.__bus_rate: for ch in self.__channels: self.__graph.connect((in_block, ch), (bus_sums[ch], in_index)) else: for ch in self.__channels: self.__graph.connect( (in_block, ch), # TODO pool these resamplers make_resampler(in_rate, self.__bus_rate), (bus_sums[ch], in_index)) in_index += 1 if in_index > 0: # connect output only if there is at least one input if len(outputs) > 0: used_resamplers = set() for out_rate, out_block in outputs: if out_rate == self.__bus_rate: for ch in self.__channels: self.__graph.connect(bus_sums[ch], (out_block, ch)) else: if out_rate not in self.__resampler_cache: # Moderately expensive due to the internals using optfir log.msg( 'Flow graph: Constructing resampler for audio rate %i' % out_rate) self.__resampler_cache[out_rate] = tuple( make_resampler(self.__bus_rate, out_rate) for _ in self.__channels) resamplers = self.__resampler_cache[out_rate] used_resamplers.add(resamplers) for ch in self.__channels: self.__graph.connect(resamplers[ch], (out_block, ch)) for resamplers in used_resamplers: for ch in self.__channels: self.__graph.connect(bus_sums[ch], resamplers[ch]) else: # gnuradio requires at least one connected output for ch in self.__channels: self.__graph.connect(bus_sums[ch], blocks.null_sink(gr.sizeof_float))
def _do_connect(self): """Do all reconfiguration operations in the proper order.""" rate_changed = False if self.source is not self._sources[self.source_name]: log.msg('Flow graph: Switching RF source') self.__needs_reconnect = True def tune_hook(): reactor.callLater(self.source.get_tune_delay(), tune_hook_actual) def tune_hook_actual(): if self.source is not this_source: return freq = this_source.get_freq() self.input_freq = freq self.monitor.set_input_center_freq(freq) for key, receiver in self._receivers.iteritems(): receiver.set_input_center_freq(freq) self._update_receiver_validity(key) # TODO: If multiple receivers change validity we'll do redundant reconnects in this loop; avoid that. this_source = self._sources[self.source_name] this_source.set_tune_hook(tune_hook) self.source = this_source this_rate = this_source.get_sample_rate() rate_changed = self.input_rate != this_rate self.input_rate = this_rate self.input_freq = this_source.get_freq() for key, receiver in self._receivers.iteritems(): receiver.set_input_center_freq(self.input_freq) if rate_changed: log.msg('Flow graph: Changing sample rates') self.monitor.set_sample_rate(self.input_rate) for receiver in self._receivers.itervalues(): receiver.set_input_rate(self.input_rate) if self.__needs_reconnect: log.msg('Flow graph: Rebuilding connections') self.__needs_reconnect = False self._recursive_lock() self.disconnect_all() self.connect( self.source, self.monitor) # recreated each time because reusing an add_ff w/ different # input counts fails; TODO: report/fix bug audio_sum_l = blocks.add_ff() audio_sum_r = blocks.add_ff() audio_sum_index = 0 for key, receiver in self._receivers.iteritems(): self._receiver_valid[key] = receiver.get_is_valid() if self._receiver_valid[key]: if audio_sum_index >= 6: # Sanity-check to avoid burning arbitrary resources # TODO: less arbitrary constant; communicate this restriction to client log.err('Flow graph: Refusing to connect more than 6 receivers') break self.connect(self.source, receiver) self.connect((receiver, 0), (audio_sum_l, audio_sum_index)) self.connect((receiver, 1), (audio_sum_r, audio_sum_index)) audio_sum_index += 1 if audio_sum_index > 0: # connect audio output only if there is at least one input if len(self.audio_queue_sinks) > 0: used_resamplers = set() for (queue_rate, interleaver, sink) in self.audio_queue_sinks.itervalues(): if queue_rate == self.audio_rate: self.connect(audio_sum_l, (interleaver, 0)) self.connect(audio_sum_r, (interleaver, 1)) else: if queue_rate not in self.audio_resampler_cache: # Moderately expensive due to the internals using optfir log.msg('Flow graph: Constructing resampler for audio rate %i' % queue_rate) self.audio_resampler_cache[queue_rate] = ( make_resampler(self.audio_rate, queue_rate), make_resampler(self.audio_rate, queue_rate) ) resamplers = self.audio_resampler_cache[queue_rate] used_resamplers.add(resamplers) self.connect(resamplers[0], (interleaver, 0)) self.connect(resamplers[1], (interleaver, 1)) self.connect(interleaver, sink) for resamplers in used_resamplers: self.connect(audio_sum_l, resamplers[0]) self.connect(audio_sum_r, resamplers[1]) else: # no stream sinks, gnuradio requires a dummy sink self.connect(audio_sum_l, blocks.null_sink(gr.sizeof_float)) self.connect(audio_sum_r, blocks.null_sink(gr.sizeof_float)) self._recursive_unlock() log.msg('Flow graph: ...done reconnecting.')
def __init__(self, mode="VOR", zero_point=59, **kwargs): self.channel_rate = channel_rate = 40000 internal_audio_rate = 20000 # TODO over spec'd self.zero_point = zero_point transition = 5000 SimpleAudioDemodulator.__init__( self, mode=mode, demod_rate=channel_rate, band_filter=fm_subcarrier * 1.25 + fm_deviation + transition / 2, band_filter_transition=transition, **kwargs ) self.dir_rate = dir_rate = 10 if internal_audio_rate % dir_rate != 0: raise ValueError( "Audio rate %s is not a multiple of direction-finding rate %s" % (internal_audio_rate, dir_rate) ) self.dir_scale = dir_scale = internal_audio_rate // dir_rate self.audio_scale = audio_scale = channel_rate // internal_audio_rate self.zeroer = blocks.add_const_vff((zero_point * (math.pi / 180),)) self.dir_vector_filter = grfilter.fir_filter_ccf( 1, firdes.low_pass(1, dir_rate, 1, 2, firdes.WIN_HAMMING, 6.76) ) self.am_channel_filter_block = grfilter.fir_filter_ccf( 1, firdes.low_pass(1, channel_rate, 5000, 5000, firdes.WIN_HAMMING, 6.76) ) self.goertzel_fm = fft.goertzel_fc(channel_rate, dir_scale * audio_scale, 30) self.goertzel_am = fft.goertzel_fc(internal_audio_rate, dir_scale, 30) self.fm_channel_filter_block = grfilter.freq_xlating_fir_filter_ccc( 1, (firdes.low_pass(1.0, channel_rate, fm_subcarrier / 2, fm_subcarrier / 2, firdes.WIN_HAMMING)), fm_subcarrier, channel_rate, ) self.multiply_conjugate_block = blocks.multiply_conjugate_cc(1) self.complex_to_arg_block = blocks.complex_to_arg(1) self.am_agc_block = analog.feedforward_agc_cc(1024, 1.0) self.am_demod_block = analog.am_demod_cf( channel_rate=channel_rate, audio_decim=audio_scale, audio_pass=5000, audio_stop=5500 ) self.fm_demod_block = analog.quadrature_demod_cf(1) self.phase_agc_fm = analog.agc2_cc(1e-1, 1e-2, 1.0, 1.0) self.phase_agc_am = analog.agc2_cc(1e-1, 1e-2, 1.0, 1.0) self.probe = blocks.probe_signal_f() self.audio_filter_block = make_lofi_audio_filter(internal_audio_rate) self.resampler_block = make_resampler(internal_audio_rate, self.audio_rate) ################################################## # Connections ################################################## # Input self.connect(self, self.band_filter_block) # AM chain self.connect(self.band_filter_block, self.am_channel_filter_block, self.am_agc_block, self.am_demod_block) # AM audio self.connect( self.am_demod_block, blocks.multiply_const_ff(1.0 / audio_modulation_index * 0.5), self.audio_filter_block, self.resampler_block, ) self.connect_audio_output(self.resampler_block, self.resampler_block) # AM phase self.connect(self.am_demod_block, self.goertzel_am, self.phase_agc_am, (self.multiply_conjugate_block, 0)) # FM phase self.connect( self.band_filter_block, self.fm_channel_filter_block, self.fm_demod_block, self.goertzel_fm, self.phase_agc_fm, (self.multiply_conjugate_block, 1), ) # Phase comparison and output self.connect( self.multiply_conjugate_block, self.dir_vector_filter, self.complex_to_arg_block, blocks.multiply_const_ff(-1), # opposite angle conventions self.zeroer, self.probe, )
def _do_connect(self): """Do all reconfiguration operations in the proper order.""" rate_changed = False if self.source is not self._sources[self.source_name]: log.msg('Flow graph: Switching RF source') self.__needs_reconnect = True this_source = self._sources[self.source_name] def update_input_freqs(): freq = this_source.get_freq() self.input_freq = freq self.monitor.set_input_center_freq(freq) for receiver in self._receivers.itervalues(): receiver.set_input_center_freq(freq) def tune_hook(): # Note that in addition to the flow graph delay, the callLater is also needed in order to ensure we don't do our reconfiguration in the middle of the source's own workings. reactor.callLater(self.__rx_driver.get_tune_delay(), tune_hook_actual) def tune_hook_actual(): if self.source is not this_source: return update_input_freqs() for key in self._receivers: self._update_receiver_validity(key) # TODO: If multiple receivers change validity we'll do redundant reconnects in this loop; avoid that. if self.__source_tune_subscription is not None: self.__source_tune_subscription.unsubscribe() self.__source_tune_subscription = this_source.state()['freq'].subscribe(tune_hook) self.source = this_source self.__rx_driver = this_source.get_rx_driver() source_signal_type = self.__rx_driver.get_output_type() this_rate = source_signal_type.get_sample_rate() rate_changed = self.input_rate != this_rate self.input_rate = this_rate self.monitor.set_signal_type(source_signal_type) self.__clip_probe.set_window_and_reconnect(0.5 * this_rate) update_input_freqs() if rate_changed: log.msg('Flow graph: Changing sample rates') for receiver in self._receivers.itervalues(): receiver.set_input_rate(self.input_rate) if self.__needs_reconnect: log.msg('Flow graph: Rebuilding connections') self.__needs_reconnect = False self._recursive_lock() self.disconnect_all() self.connect( self.__rx_driver, self.monitor) self.connect( self.__rx_driver, self.__clip_probe) # Determine audio bus rate. # The bus obviously does not need to be higher than the rate of any receiver, because that would be extraneous data. It also does not need to be higher than the rate of any queue, because no queue has use for the information. if len(self._receivers) > 0 and len(self.audio_queue_sinks) > 0: max_out_rate = max((receiver.get_output_type().get_sample_rate() for receiver in self._receivers.itervalues())) max_in_rate = max((queue_rate for (queue_rate, sink) in self.audio_queue_sinks.itervalues())) new_bus_rate = min(max_out_rate, max_in_rate) if new_bus_rate != self.__audio_bus_rate: self.__audio_bus_rate = new_bus_rate self.audio_resampler_cache.clear() # recreated each time because reusing an add_ff w/ different # input counts fails; TODO: report/fix bug audio_sums = [blocks.add_ff() for _ in xrange(self.__audio_channels)] audio_sum_index = 0 for key, receiver in self._receivers.iteritems(): self._receiver_valid[key] = receiver.get_is_valid() if self._receiver_valid[key]: if audio_sum_index >= 6: # Sanity-check to avoid burning arbitrary resources # TODO: less arbitrary constant; communicate this restriction to client log.err('Flow graph: Refusing to connect more than 6 receivers') break self.connect(self.__rx_driver, receiver) receiver_rate = receiver.get_output_type().get_sample_rate() if receiver_rate == self.__audio_bus_rate: for ch in xrange(self.__audio_channels): self.connect( (receiver, ch), (audio_sums[ch], audio_sum_index)) else: for ch in xrange(self.__audio_channels): self.connect( (receiver, ch), # TODO pool these resamplers make_resampler(receiver_rate, self.__audio_bus_rate), (audio_sums[ch], audio_sum_index)) audio_sum_index += 1 if audio_sum_index > 0: # connect audio output only if there is at least one input if len(self.audio_queue_sinks) > 0: used_resamplers = set() for (queue_rate, sink) in self.audio_queue_sinks.itervalues(): if queue_rate == self.__audio_bus_rate: for ch in xrange(self.__audio_channels): self.connect(audio_sums[ch], (sink, ch)) else: if queue_rate not in self.audio_resampler_cache: # Moderately expensive due to the internals using optfir log.msg('Flow graph: Constructing resampler for audio rate %i' % queue_rate) self.audio_resampler_cache[queue_rate] = tuple( make_resampler(self.__audio_bus_rate, queue_rate) for _ in xrange(self.__audio_channels)) resamplers = self.audio_resampler_cache[queue_rate] used_resamplers.add(resamplers) for ch in xrange(self.__audio_channels): self.connect(resamplers[ch], (sink, ch)) for resamplers in used_resamplers: for ch in xrange(self.__audio_channels): self.connect(audio_sums[ch], resamplers[ch]) else: # no stream sinks, gnuradio requires a dummy sink for ch in xrange(self.__audio_channels): self.connect(audio_sums[ch], blocks.null_sink(gr.sizeof_float)) self._recursive_unlock() log.msg('Flow graph: ...done reconnecting.')
def connect(self, inputs, outputs): ''' Make all new connections (graph.disconnect_all() must have been done) between inputs and outputs. inputs and outputs must be iterables of (sample_rate, block) tuples. ''' inputs = list(inputs) outputs = list(outputs) # Determine bus rate. # The bus obviously does not need to be higher than the rate of any bus input, because that would be extraneous data. It also does not need to be higher than the rate of any bus output, because no output has use for the information. max_in_rate = max((rate for rate, _ in inputs)) if len(inputs) > 0 else 0.0 max_out_rate = max((rate for rate, _ in outputs)) if len(outputs) > 0 else 0.0 new_bus_rate = min(max_out_rate, max_in_rate) if new_bus_rate == 0.0: # There are either no inputs or no outputs. Use the other side's rate so we have a well-defined value. new_bus_rate = max(max_out_rate, max_in_rate) if new_bus_rate == 0.0: # There are both no inputs and no outputs. No point in not keeping the old rate (and its resampler cache). new_bus_rate = self.__bus_rate elif new_bus_rate != self.__bus_rate: self.__bus_rate = new_bus_rate self.__resampler_cache.clear() # recreated each time because reusing an add_ff w/ different # input counts fails; TODO: report/fix bug bus_sums = [blocks.add_ff() for _ in self.__channels] in_index = 0 for in_rate, in_block in inputs: if in_rate == self.__bus_rate: for ch in self.__channels: self.__graph.connect( (in_block, ch), (bus_sums[ch], in_index)) else: for ch in self.__channels: self.__graph.connect( (in_block, ch), # TODO pool these resamplers make_resampler(in_rate, self.__bus_rate), (bus_sums[ch], in_index)) in_index += 1 if in_index > 0: # connect output only if there is at least one input if len(outputs) > 0: used_resamplers = set() for out_rate, out_block in outputs: if out_rate == self.__bus_rate: for ch in self.__channels: self.__graph.connect(bus_sums[ch], (out_block, ch)) else: if out_rate not in self.__resampler_cache: # Moderately expensive due to the internals using optfir log.msg('Flow graph: Constructing resampler for audio rate %i' % out_rate) self.__resampler_cache[out_rate] = tuple( make_resampler(self.__bus_rate, out_rate) for _ in self.__channels) resamplers = self.__resampler_cache[out_rate] used_resamplers.add(resamplers) for ch in self.__channels: self.__graph.connect(resamplers[ch], (out_block, ch)) for resamplers in used_resamplers: for ch in self.__channels: self.__graph.connect(bus_sums[ch], resamplers[ch]) else: # gnuradio requires at least one connected output for ch in self.__channels: self.__graph.connect(bus_sums[ch], blocks.null_sink(gr.sizeof_float))