tictacsync 0.1a10__tar.gz → 0.1a11__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tictacsync might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tictacsync
3
- Version: 0.1a10
3
+ Version: 0.1a11
4
4
  Summary: command for syncing audio video recordings
5
5
  Home-page: https://sr.ht/~proflutz/TicTacSync/
6
6
  Author: Raymond Lutz
@@ -27,7 +27,7 @@ setup(
27
27
  entry_points = {
28
28
  "console_scripts": ['tictacsync = tictacsync.entry:main']
29
29
  },
30
- version = '0.1a10',
30
+ version = '0.1a11',
31
31
  description = "command for syncing audio video recordings",
32
32
  long_description_content_type='text/markdown',
33
33
  long_description = long_descr,
@@ -173,11 +173,11 @@ class Scanner:
173
173
  continue
174
174
  devices = [m['dev UID'] for m in media_same_length]
175
175
  if len(set(devices)) !=1:
176
- print('files with same length but diff device?')
176
+ print('There are files with same length but from different devices?')
177
177
  for media in media_same_length:
178
- print(' %s'%media['path'])
179
- print('put the offending file in its own folder and')
180
- print('rerun. Quitting...')
178
+ print(' [gold1]%s[/gold1]'%media['path'])
179
+ print('Please put the offending file in its own folder and rerun.')
180
+ print('Quitting...')
181
181
  quit()
182
182
  self.found_multifiles.append(media_same_length)
183
183
  self.found_media_files = unifile_recordings
@@ -326,6 +326,13 @@ class Scanner:
326
326
 
327
327
  Returns nothing
328
328
  """
329
+ def _list_duplicates(seq):
330
+ seen = set()
331
+ seen_add = seen.add
332
+ # adds all elements it doesn't know yet to seen and all other to seen_twice
333
+ seen_twice = set( x for x in seq if x in seen or seen_add(x) )
334
+ # turn the set into a list (as requested)
335
+ return list( seen_twice )
329
336
  folder_key = lambda m: m['path'].parent
330
337
  medias = sorted(self.found_media_files, key=folder_key)
331
338
  # build lists for multiple reference of iterators
@@ -335,9 +342,14 @@ class Scanner:
335
342
  name_of_folders = [p.name for p in complete_path_folders]
336
343
  logger.debug('complete_path_folders with media files %s'%complete_path_folders)
337
344
  logger.debug('name_of_folders with media files %s'%name_of_folders)
338
- unique_folder_names = set(name_of_folders)
339
- if len(unique_folder_names) != len(name_of_folders):
340
- print('There is conflicts for some folder names:')
345
+ # unique_folder_names = set(name_of_folders)
346
+ repeated_folders = _list_duplicates(name_of_folders)
347
+ logger.debug('repeated_folders %s'%repeated_folders)
348
+ if repeated_folders:
349
+ print('There are conflicts for some repeated folder names:')
350
+ for f in [str(p) for p in repeated_folders]:
351
+ print(' [gold1]%s[/gold1]'%f)
352
+ print('Here are the complete paths:')
341
353
  for f in [str(p) for p in complete_path_folders]:
342
354
  print(' [gold1]%s[/gold1]'%f)
343
355
  print('please rename and rerun. Quitting..')
@@ -110,7 +110,7 @@ def main():
110
110
  # logger.add(sys.stdout, filter="device_scanner")
111
111
  # logger.add(sys.stdout, filter="yaltc")
112
112
  # logger.add(sys.stdout, filter="timeline")
113
- # logger.add(sys.stdout, filter=lambda r: r["function"] == "scan_audio_for_each_ref_rec")
113
+ # logger.add(sys.stdout, filter=lambda r: r["function"] == "_get_word_envelope")
114
114
  # logger.add(sys.stdout, filter=lambda r: r["function"] == "get_timecode")
115
115
  # logger.add(sys.stdout, filter=lambda r: r["function"] == "_get_BFSK_symbols_boundaries")
116
116
  # logger.add(sys.stdout, filter=lambda r: r["function"] == "_get_BFSK_word_boundaries")
@@ -63,6 +63,7 @@ SYMBOL_LENGTH = 14.286 # ms, from FSKfreqCalculator.py
63
63
  N_SYMBOLS_SAMD21 = 35 # including sync pulse
64
64
  ##################
65
65
 
66
+ BPF_LOW_FRQ, BPF_HIGH_FRQ = (0.5*F1, 2*F2)
66
67
 
67
68
  try:
68
69
  layouts, _ = (
@@ -190,17 +191,18 @@ class Decoder:
190
191
  SN_ratio : float
191
192
  signal over noise ratio in dB.
192
193
 
193
- pulse_level : float
194
+ pulse_detection_level : float
194
195
  level used to detect sync pulse
195
196
 
196
197
  silent_zone_indices : tuple of ints
197
198
  silent zone boundary positions relative to the start
198
199
  of self.sound_extract.
200
+
201
+ estimated_pulse_position : int
202
+ pulse position (samples) relative to the start of self.sound_extract
199
203
 
200
- pulse_position : dict
201
- key 'value' : positions relative to the start of self.sound_extract
202
- of pulse;
203
- key 'type' : "estimated" or "detected"
204
+ detected_pulse_position : int
205
+ pulse position (samples) relative to the start of self.sound_extract
204
206
 
205
207
  cached_convolution_fit : dict
206
208
  if _fit_triangular_signal_to_convoluted_env() has already been called,
@@ -223,10 +225,10 @@ class Decoder:
223
225
  def clear_decoder(self):
224
226
  self.sound_data_extract = None
225
227
  self.cached_convolution_fit = {'sound_extract_position': None}
226
- self.pulse_level = None
228
+ self.pulse_detection_level = None
227
229
  self.silent_zone_indices = None
228
- self.pulse_position = {'value': None}
229
-
230
+ self.detected_pulse_position = None
231
+ self.estimated_pulse_position = None
230
232
 
231
233
  def set_sound_extract_and_sr(self, extract, s_r, where):
232
234
  self.sound_extract = extract
@@ -254,21 +256,10 @@ class Decoder:
254
256
 
255
257
  """
256
258
  WINDOW_LENGTH, POLYORDER = (15, 3) # parameters found by experiment, hit and miss
257
- abs_hil = np.abs(scipy.signal.hilbert(self.sound_extract))
258
- envelope = scipy.signal.savgol_filter(abs_hil,
259
+ absolute_of_hilbert = np.abs(scipy.signal.hilbert(self.sound_extract))
260
+ envelope = scipy.signal.savgol_filter(absolute_of_hilbert,
259
261
  WINDOW_LENGTH, POLYORDER)
260
-
261
- # plt.plot(self.sound_extract)
262
- # plt.plot(abs_hil)
263
- # plt.plot(envelope)
264
- # plt.show()
265
-
266
- # mean = envelope.mean()
267
- # if mean: # in case of zero padding
268
- # factor = 0.5/mean # since 50% duty cycle
269
- # else:
270
- # factor = 1
271
- # return factor*envelope
262
+ logger.debug('self.sound_extract envelope length %i samples'%len(envelope))
272
263
  return envelope
273
264
 
274
265
  def _get_signal_level(self):
@@ -277,30 +268,30 @@ class Decoder:
277
268
 
278
269
  def _get_pulse_position(self):
279
270
  # relative to extract beginning
280
- # return precise value
281
- if self.pulse_position['value'] is not None:
282
- return self.pulse_position['value']
271
+ if self.detected_pulse_position:
272
+ logger.debug('returning detected value')
273
+ return self.detected_pulse_position
274
+ if self.estimated_pulse_position:
275
+ return self.estimated_pulse_position
283
276
  _, silence_center_x = self._fit_triangular_signal_to_convoluted_env()
284
277
  # symbol_width_samples = 1e-3*SYMBOL_LENGTH
285
- pulse_relative_pos = int(0.5*(0.5 - 1e-3*SYMBOL_LENGTH)*self.samplerate)
286
- approx_pulse_x = silence_center_x + pulse_relative_pos
287
- self.pulse_position = {}
288
- self.pulse_position['value'] = approx_pulse_x
289
- self.pulse_position['type'] = 'estimated'
290
- return self.pulse_position['value']
291
-
292
- def _get_pulse_level(self):
278
+ self.estimated_pulse_position = silence_center_x + int(0.5*(
279
+ 0.5 - 1e-3*SYMBOL_LENGTH)*self.samplerate)
280
+ logger.debug('returning estimated value from silence mid position')
281
+ return self.estimated_pulse_position
282
+
283
+ def _get_pulse_detection_level(self):
293
284
  # return the geometric mean between silence and BFSK levels
294
- if self.pulse_level is None:
285
+ if self.pulse_detection_level is None:
295
286
  silence_floor = self._get_silence_floor()
296
287
  # lower_BFSK_level = silence_floor
297
- pulse_position = self._get_pulse_position()
298
- lower_BFSK_level = self._get_minimal_bfsk(pulse_position)
288
+ # pulse_position = self._get_pulse_position()
289
+ lower_BFSK_level = self._get_minimal_bfsk()
299
290
  value = math.sqrt(silence_floor * lower_BFSK_level)
300
- self.pulse_level = value
291
+ self.pulse_detection_level = value
301
292
  return value
302
293
  else:
303
- return self.pulse_level
294
+ return self.pulse_detection_level
304
295
 
305
296
  def _get_square_convolution(self):
306
297
  """
@@ -333,37 +324,50 @@ class Decoder:
333
324
  x = range(start, len(convol) + start)
334
325
  return [*x], convol
335
326
 
336
- def _get_word_envelope(self, pulse_position):
327
+ def _get_word_envelope(self):
337
328
  """
338
329
  Chop the signal envelope keeping the word region and smooth it over the
339
330
  longest BFSK period
340
331
  """
341
- max_period = int(self.samplerate*max(1/F1,1/F2))
342
- logger.debug('max BFSK period %i samples'%max_period)
343
- period_window = np.ones(max_period,dtype=int)/max_period
332
+ SR = self.samplerate
344
333
  envelope = self._get_envelope()
345
- symbol_width_samples = 1e-3*SYMBOL_LENGTH*self.samplerate
334
+ pulse_position = self._get_pulse_position()
335
+ samples_to_end = len(self.sound_extract) - pulse_position
336
+ is_too_near_the_end = samples_to_end/SR < 0.5
337
+ logger.debug('pulse_position is_too_near_the_end %s'%
338
+ is_too_near_the_end)
339
+ if is_too_near_the_end:
340
+ pulse_position -= SR # one second sooner
341
+ symbol_width_samples = 1e-3*SYMBOL_LENGTH*SR
346
342
  word_start = int(pulse_position + 3*symbol_width_samples)
347
- word_end = int(pulse_position + 0.5*self.samplerate)
348
- word_end -= 2*symbol_width_samples # slide to the left a little
349
- logger.debug('word start, end: %i %i'%(word_start, word_end))
350
- w_envelope = envelope[word_start:int(word_end)]
351
- # plt.plot(w_envelope)
352
- # plt.show()
353
- return w_envelope
354
- # return np.convolve(w_envelope, period_window, mode='same')
343
+ word_end = int(pulse_position + 0.5*SR)
344
+ word_end -= int(2*symbol_width_samples) # slide to the left a little
345
+ logger.debug('word start, end: %i %i (in file)'%(
346
+ word_start + self.sound_extract_position,
347
+ word_end + self.sound_extract_position))
348
+ w_envelope = envelope[word_start : word_end]
349
+ word_envelope_truncated = word_end-word_start != len(w_envelope)
350
+ logger.debug('w_envelope is sliced out of bounds: %s'%(
351
+ str(word_envelope_truncated)))
352
+ logger.debug('word envelope length %i samples %f secs'%(
353
+ len(w_envelope), len(w_envelope)/SR))
354
+ max_period = int(self.samplerate*max(1/F1,1/F2))
355
+ logger.debug('max BFSK period %i in samples'%max_period)
356
+ period_window = np.ones(max_period,dtype=int)/max_period
357
+ # smooth over longest BFSK period
358
+ return np.convolve(w_envelope, period_window, mode='same')
355
359
 
356
- def _get_minimal_bfsk(self, pulse_position):
360
+ def _get_minimal_bfsk(self):
357
361
  """
358
362
  because of non-flat frequency response, bfsk bits dont have the same
359
363
  amplitude. This returns the least of both by detecting a bimodal
360
364
  gaussian distribution
361
365
 
362
366
  """
363
- # w_envelope = self._get_word_envelope(pulse_position)
367
+ # w_envelope = self._get_word_envelope()
364
368
  # word_start = int(min_position + shift + 0.3*self.samplerate)
365
369
  # word = w_envelope[word_start : int(word_start + 0.4*self.samplerate)]
366
- word = self._get_word_envelope(pulse_position)
370
+ word = self._get_word_envelope()
367
371
  # plt.plot(word)
368
372
  # plt.show()
369
373
  n = len(word)
@@ -407,12 +411,11 @@ class Decoder:
407
411
  logger.debug('yes, fit values cached:')
408
412
  v1 = self.cached_convolution_fit['chi_square']
409
413
  v2 = self.cached_convolution_fit['minimum position']
410
- logger.debug('chi_square: %s minimum position: %s'%(v1, v2))
414
+ v2_file = v2 + self.sound_extract_position
415
+ logger.debug('cached chi_sq: %s minimum position in file: %s'%(v1, v2_file))
411
416
  return (v1, v2)
412
417
  # cached!
413
418
  x_shifted, convolution = self._get_square_convolution()
414
-
415
- shift = x_shifted[0] # convolution is shorter than sound envelope
416
419
  # see numpy.convolve(..., mode='valid')
417
420
  x = np.arange(len(convolution))
418
421
  trig_params = lmfit.Parameters()
@@ -441,12 +444,13 @@ class Decoder:
441
444
  args=(x,), kws={'signal_data': convolution}
442
445
  )
443
446
  chi_square = fit_trig.chisqr
444
- min_position = int(fit_trig.params['min_position'].value)
445
- logger.debug('chi_square %.1f minimum convolution position %i'%
446
- (chi_square, min_position + shift))
447
+ shift = x_shifted[0] # convolution is shorter than sound envelope
448
+ min_position = int(fit_trig.params['min_position'].value) + shift
449
+ logger.debug('chi_square %.1f minimum convolution position %i in file'%
450
+ (chi_square, min_position + self.sound_extract_position))
447
451
  self.cached_convolution_fit['sound_extract_position'] = self.sound_extract_position
448
452
  self.cached_convolution_fit['chi_square'] = chi_square
449
- self.cached_convolution_fit['minimum position'] = min_position + shift
453
+ self.cached_convolution_fit['minimum position'] = min_position
450
454
 
451
455
  return chi_square, min_position + shift
452
456
 
@@ -468,9 +472,7 @@ class Decoder:
468
472
  def _get_silent_zone_indices(self):
469
473
  """
470
474
  Returns silent zone boundary positions relative to the start
471
- of self.sound_extract. Adjustment are made so a complete
472
- 0.5 second signal is at the left of the silent zone for word
473
- decoding.
475
+ of self.sound_extract.
474
476
 
475
477
  Returns
476
478
  -------
@@ -480,7 +482,7 @@ class Decoder:
480
482
  right indice.
481
483
 
482
484
  """
483
- if self.silent_zone_indices is not None:
485
+ if self.silent_zone_indices:
484
486
  return self.silent_zone_indices
485
487
  _, silence_center_position = self._fit_triangular_signal_to_convoluted_env()
486
488
  srate = self.samplerate
@@ -488,9 +490,10 @@ class Decoder:
488
490
  left_window_boundary = silence_center_position - half_window
489
491
  right_window_boundary = silence_center_position + half_window
490
492
  # margin = 0.75 * srate
491
- logger.debug('silent zone, left: %i, right %i, center %i'%
492
- (left_window_boundary, right_window_boundary,
493
- silence_center_position))
493
+ values = np.array([left_window_boundary, right_window_boundary,
494
+ silence_center_position])
495
+ values += self.sound_extract_position # samples pos in file
496
+ logger.debug('silent zone, left: %i, right %i, center %i'%tuple(values))
494
497
  self.silent_zone_indices = (left_window_boundary, right_window_boundary)
495
498
  return self.silent_zone_indices
496
499
 
@@ -520,7 +523,7 @@ class Decoder:
520
523
  x_convolution, convolution = self._get_square_convolution()
521
524
  scaled_convo = self._get_signal_level()*convolution
522
525
  # since 0 < convolution < 1
523
- trig_level = self._get_pulse_level()
526
+ trig_level = self._get_pulse_detection_level()
524
527
  sound_extract_position = self.sound_extract_position
525
528
  def x2f(nx):
526
529
  return nx + sound_extract_position
@@ -545,7 +548,7 @@ class Decoder:
545
548
  approx_pulse_x, 0.1, 0.9,
546
549
  transform=xt, linewidth=1, colors='yellow'
547
550
  )
548
- bfsk_min = self._get_minimal_bfsk(approx_pulse_x)
551
+ bfsk_min = self._get_minimal_bfsk()
549
552
  ax.hlines(
550
553
  bfsk_min, 0, 1,
551
554
  transform=yt, linewidth=1, colors='red'
@@ -598,15 +601,18 @@ class Decoder:
598
601
 
599
602
  def _detect_sync_pulse_position(self):
600
603
  """
601
- Determines noise level during silence period and use it to detect
602
- the sync pulse position. Computes SN_ratio and stores it.
604
+ Determines noise level during silence period and use it to detect the
605
+ sync pulse position. Computes SN_ratio and stores it. Start searching
606
+ around end of silent zone. Adjustment are made so a complete 0.5
607
+ second signal is at the right of the starting search position so a
608
+ complete 0.5 s word is available for decoding.
609
+
603
610
  Returns the pulse position relative to the extract beginning.
604
611
  """
605
- start_silent_zone, end_silent_zone = self._get_silent_zone_indices()
606
- pulse_level = self._get_pulse_level()
612
+ pulse_detection_level = self._get_pulse_detection_level()
607
613
  abs_signal = abs(self.sound_extract)
608
614
  mean_during_word = 2*abs_signal.mean()
609
- self.SN_ratio = 20*math.log10(mean_during_word/pulse_level)
615
+ self.SN_ratio = 20*math.log10(mean_during_word/pulse_detection_level)
610
616
  logger.debug('SN ratio: %f dB'%(self.SN_ratio))
611
617
  search_pulse_start_point = self._get_pulse_position()
612
618
  search_pulse_start_point -= 3*SYMBOL_LENGTH*1e-3*self.samplerate
@@ -624,17 +630,20 @@ class Decoder:
624
630
  logger.debug('search_pulse_start_point: %i in extract'%
625
631
  search_pulse_start_point)
626
632
  abs_signal_after_silence = abs_signal[search_pulse_start_point:]
633
+ # here the real searching with numpy.argmax()
627
634
  first_point = \
628
- np.argmax(abs_signal_after_silence > pulse_level)
635
+ np.argmax(abs_signal_after_silence > pulse_detection_level)
629
636
  first_point += search_pulse_start_point
630
637
  logger.debug('found sync pulse at %i in extract'%first_point)
638
+ self.detected_pulse_position = first_point
631
639
  return first_point
632
640
 
633
- def _get_word_width_parameters(self, pulse_position):
641
+ def _get_word_width_parameters(self):
634
642
  abs_signal = abs(self.sound_extract)
643
+ pulse_position = self._get_pulse_position()
635
644
  # half_amplitude = abs_signal.mean() # since 50% duty cycle OLD
636
645
  # params = {'word_width_threshold':WORDWIDTHFACTOR*half_amplitude} OLD
637
- bfsk_min = self._get_minimal_bfsk(pulse_position)
646
+ bfsk_min = self._get_minimal_bfsk()
638
647
  params = {'word_width_threshold': 0.8*bfsk_min}
639
648
  sr = self.samplerate
640
649
  presumed_symbol_length = SYMBOL_LENGTH*1e-3*sr
@@ -649,10 +658,11 @@ class Decoder:
649
658
  params['presumed_symbol_length'] = presumed_symbol_length
650
659
  return params
651
660
 
652
- def _get_BFSK_word_boundaries(self, pulse_position):
661
+ def _get_BFSK_word_boundaries(self):
653
662
  n_bits = N_SYMBOLS_SAMD21 - 1
654
663
  sr = self.samplerate
655
- wwp = self._get_word_width_parameters(pulse_position)
664
+ wwp = self._get_word_width_parameters()
665
+ pulse_position = self._get_pulse_position()
656
666
  # search_start_position = wwp['search_start_position']
657
667
  search_end_position = wwp['search_end_position']
658
668
  word_width_threshold = wwp['word_width_threshold']
@@ -689,10 +699,11 @@ class Decoder:
689
699
  logger.debug(' relative discrepancy %.4f%%'%(abs(100*relative_error)))
690
700
  return status, left_boundary, right_boundary
691
701
 
692
- def _get_BFSK_symbols_boundaries(self, pulse_position):
702
+ def _get_BFSK_symbols_boundaries(self):
693
703
  # returns indices of start of each slice and boundaries
704
+ pulse_position = self._get_pulse_position()
694
705
  boundaries_OK, left_boundary, right_boundary = \
695
- self._get_BFSK_word_boundaries(pulse_position)
706
+ self._get_BFSK_word_boundaries()
696
707
  if left_boundary is None:
697
708
  return None, None, None
698
709
  symbol_width_samples = \
@@ -741,7 +752,8 @@ class Decoder:
741
752
  logger.debug('slicing intervals, word_intervals = %s'%
742
753
  word_intervals)
743
754
  # skip sample after pulse, start at BFSK word
744
- slices = [self.sound_extract[slice(*pair)]
755
+ filtered_sound_extract = self._band_pass_filter(self.sound_extract)
756
+ slices = [filtered_sound_extract[slice(*pair)]
745
757
  for pair in word_intervals]
746
758
  np.set_printoptions(threshold=5)
747
759
  # logger.debug('data slices: \n%s'%pprint.pformat(slices))
@@ -756,13 +768,17 @@ class Decoder:
756
768
  freq_in_hertz = abs(freq * self.samplerate)
757
769
  return int(round(freq_in_hertz))
758
770
 
771
+ # def _get_bit_from_freq(self, freq):
772
+ # if math.isclose(freq, F1, abs_tol=FSK_TOLERANCE):
773
+ # return '0'
774
+ # if math.isclose(freq, F2, abs_tol=FSK_TOLERANCE):
775
+ # return '1'
776
+ # else:
777
+ # return None
778
+
759
779
  def _get_bit_from_freq(self, freq):
760
- if math.isclose(freq, F1, abs_tol=FSK_TOLERANCE):
761
- return '0'
762
- if math.isclose(freq, F2, abs_tol=FSK_TOLERANCE):
763
- return '1'
764
- else:
765
- return None
780
+ mid_FSK = 0.5*(F1 + F2)
781
+ return '1' if freq > mid_FSK else '0'
766
782
 
767
783
  def _get_int_from_binary_str(self, string_of_01s):
768
784
  return int(''.join(reversed(string_of_01s)),2)
@@ -791,15 +807,14 @@ class Decoder:
791
807
  # save figure in filename if set, otherwise
792
808
  # start an interactive plot, title is for matplotlib
793
809
  signal = self.sound_extract
810
+ # signal = self._band_pass_filter(signal)
794
811
  start = self.sound_extract_position
795
812
  x_signal_in_file = range(
796
813
  start,
797
814
  start + len(signal)
798
815
  )
816
+ wwp = self._get_word_width_parameters()
799
817
  start_silent_zone, end_silent_zone = self._get_silent_zone_indices()
800
- # sync_pulse = self._detect_sync_pulse_position()
801
- wwp = self._get_word_width_parameters(sync_pulse)
802
- # search_start_position = wwp['search_start_position'] + start
803
818
  search_end_position = wwp['search_end_position'] + start
804
819
  fig, ax = plt.subplots()
805
820
  plt.title(title)
@@ -836,9 +851,8 @@ class Decoder:
836
851
  [end_silent_zone + start], [0],
837
852
  marker='<', markersize='10',
838
853
  linewidth=0.3, color='green', alpha=0.3)
839
- # symbols_indices = self._get_BFSK_symbols_boundaries(sync_pulse)
840
854
  boundaries_OK, word_lft, word_rght = \
841
- self._get_BFSK_word_boundaries(sync_pulse)
855
+ self._get_BFSK_word_boundaries()
842
856
  ax.vlines(
843
857
  word_lft + start, 0, 1,
844
858
  transform=ax.get_xaxis_transform(),
@@ -869,18 +883,27 @@ class Decoder:
869
883
  dpi=height/fig.get_size_inches()[1])
870
884
  plt.close()
871
885
 
886
+ def _band_pass_filter(self, data):
887
+ # return filtered data
888
+ def _bandpass(data: np.ndarray, edges: list[float], sample_rate: float, poles: int = 5):
889
+ sos = scipy.signal.butter(poles, edges, 'bandpass', fs=sample_rate, output='sos')
890
+ filtered_data = scipy.signal.sosfiltfilt(sos, data)
891
+ return filtered_data
892
+ sample_rate = self.samplerate
893
+ times = np.arange(len(data))/sample_rate
894
+ return _bandpass(data, [BPF_LOW_FRQ, BPF_HIGH_FRQ], sample_rate)
895
+
872
896
  def get_time_in_sound_extract(self, plots):
873
897
  if self.sound_extract is None:
874
898
  return None
875
899
  if plots:
876
900
  self.make_silence_analysis_plot()
877
- start_silent_zone, end_silent_zone = self._get_silent_zone_indices()
878
901
  pulse_position = self._detect_sync_pulse_position()
879
902
  pulse_pos_in_file = pulse_position + self.sound_extract_position
880
903
  pulse_position_sec = pulse_pos_in_file/self.samplerate
881
904
  logger.debug('found sync pulse at sample %i in file'%pulse_pos_in_file)
882
905
  symbols_indices, word_lft, word_rght = \
883
- self._get_BFSK_symbols_boundaries(pulse_position)
906
+ self._get_BFSK_symbols_boundaries()
884
907
  if plots:
885
908
  title = 'Bit slicing at %s, %.2f s'%(pulse_pos_in_file,
886
909
  pulse_position_sec)
@@ -891,6 +914,10 @@ class Decoder:
891
914
  if symbols_indices is None:
892
915
  return None
893
916
  sliced_data = self._slice_sound_extract(symbols_indices)
917
+ # sliced_data = [self._band_pass_filter(data_slice)
918
+ # for data_slice
919
+ # in sliced_data
920
+ # ]
894
921
  frequencies = [self._get_main_frequency(data_slice)
895
922
  for data_slice
896
923
  in sliced_data
@@ -902,12 +929,12 @@ class Decoder:
902
929
  length_ratio = eff_symbol_length / SYMBOL_LENGTH
903
930
  logger.debug('symbol length_ratio (eff/supposed) %f'%length_ratio)
904
931
  corrected_freq = np.array(frequencies)*length_ratio
905
- logger.debug('corrrected freq (using symbol length) = %s'%corrected_freq)
932
+ logger.debug('corrected freq (using symbol length) = %s'%corrected_freq)
906
933
  bits = [self._get_bit_from_freq(f) for f in corrected_freq]
907
934
  for i, bit in enumerate(bits):
908
935
  if bit == None:
909
936
  logger.warning('cant decode frequency %i for bit at %i-%i'%(
910
- frequencies[i],
937
+ corrected_freq[i],
911
938
  symbols_indices[i],
912
939
  symbols_indices[i+1]))
913
940
  if None in bits:
@@ -923,8 +950,6 @@ class Decoder:
923
950
  time_values['clock source'] = 'GPS' \
924
951
  if time_values['clock source'] == 1 else 'RTC'
925
952
  if self._demod_values_are_OK(time_values):
926
- self.pulse_position['value'] = pulse_position
927
- self.pulse_position['type'] = 'detected'
928
953
  return time_values
929
954
  else:
930
955
  return None
@@ -940,10 +965,10 @@ class Recording:
940
965
  media_without_YaLTC : pathlib.path
941
966
  path of video+sound file stripped of YaLTC channel
942
967
 
943
- device: str
968
+ device : str
944
969
  identifies the device used for the recording, set in __init__()
945
970
 
946
- new_rec_name: str
971
+ new_rec_name : str
947
972
  built using the device name, ex: "CAM_A001"
948
973
  set by Timeline._rename_all_recs()
949
974
 
@@ -958,25 +983,25 @@ class Recording:
958
983
  decoder : yaltc.decoder
959
984
  associated decoder object, if file is audiovideo
960
985
 
961
- true_samplerate: float
986
+ true_samplerate : float
962
987
  true sample rate using GPS time
963
988
 
964
- start_time: datetime or str
989
+ start_time : datetime or str
965
990
  time and date of the first sample in the file, cached
966
991
  after a call to get_start_time(). Value on initialization
967
992
  is None.
968
993
 
969
- sync_position: int
994
+ sync_position : int
970
995
  position of first detected syn pulse
971
996
 
972
- is_reference: bool (True for ref rec only)
997
+ is_reference : bool (True for ref rec only)
973
998
  in multi recorders set-ups, user decides if a sound-only recording
974
999
  is the time reference for all other audio recordings. By
975
1000
  default any video recording is the time reference for other audio,
976
1001
  so this attribute is only relevant to sound recordings and is
977
1002
  implicitly True for each video recordings (but not set)
978
1003
 
979
- device_relative_speed: float
1004
+ device_relative_speed : float
980
1005
  the ratio of the recording device clock speed relative to the
981
1006
  reference_rec clock device, in order to correct clock drift with
982
1007
  pysox tempo transform. If value < 1.0 then the recording is slower
@@ -985,7 +1010,7 @@ class Recording:
985
1010
  sound). A mean is calculated for all recordings of the same device
986
1011
  in Montage._get_concatenated_audiofile_for()
987
1012
 
988
- time_position: float
1013
+ time_position : float
989
1014
  The time (in seconds) at which the recording starts relative to the
990
1015
  reference recording. Updated by each Montage instance so the value
991
1016
  can change depending on the reference recording (a video or main
@@ -1000,7 +1025,7 @@ class Recording:
1000
1025
  contains the path of audio only of self.final_synced_file. Absolute
1001
1026
  path to tempfile.
1002
1027
 
1003
- in_cam_audio_sync_error: int
1028
+ in_cam_audio_sync_error : int
1004
1029
  in cam audio sync error, read in the camera folder. Negative value
1005
1030
  for lagging video (audio leads) positive value for lagging audio
1006
1031
  (video leads)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tictacsync
3
- Version: 0.1a10
3
+ Version: 0.1a11
4
4
  Summary: command for syncing audio video recordings
5
5
  Home-page: https://sr.ht/~proflutz/TicTacSync/
6
6
  Author: Raymond Lutz
File without changes
File without changes
File without changes