From 0dc87d5078c82a8fd64ac7bc51b5f632395b3b33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Berktu=C4=9F=20Kaan=20=C3=96zkan?= Date: Sun, 21 Aug 2022 18:59:39 +0300 Subject: [PATCH] Add files via upload --- config.py | 92 ++++++++++++++++++++++++++++ gui.py | 150 ++++++++++++++++++++++++++++++++++++++++++++++ icon.ico | Bin 0 -> 326 bytes main.py | 95 +++++++++++++++++++++++++++++ morse.py | 153 +++++++++++++++++++++++++++++++++++++++++++++++ plotting.py | 36 +++++++++++ requirements.txt | 5 ++ util.py | 73 ++++++++++++++++++++++ 8 files changed, 604 insertions(+) create mode 100644 config.py create mode 100644 gui.py create mode 100644 icon.ico create mode 100644 main.py create mode 100644 morse.py create mode 100644 plotting.py create mode 100644 requirements.txt create mode 100644 util.py diff --git a/config.py b/config.py new file mode 100644 index 0000000..b734b54 --- /dev/null +++ b/config.py @@ -0,0 +1,92 @@ +ABOUT_TEXT = """Morse Analyzer v0.1.0 + +This program is written by Berktug Kaan Ozkan. + +Please visit project's website for more information, how to use etc.: + +My GitHub: https://github.com/spaceymonk +Project's Source: https://github.com/spaceymonk/morse-analyzer + +Note: this program is lack of proper exception handling / input validation. Please make sure you have a valid inputs. +""" + +DEFAULTS = { + 'n_fft': '1024', + 'win_length': '512', + 'hop_length': '256', + 'apply_threshold_db': False, + 'apply_freq_band': False, + 'apply_time_band': False, + 'threshold_db': '-80', + 'freq_band_min': '', + 'freq_band_max': '', + 'time_band_min': '', + 'time_band_max': '', + 'plot_time_min': '', + 'plot_time_max': '', + 'plot_freq_min': '', + 'plot_freq_max': '', + 'plot_grids': False, + 'dot_length': '', + 'dash_length': '', + 'letter_spacing': '', + 'word_spacing': '' +} + +MORSE = { + "-----": "0", + ".----": "1", + "..---": "2", + "...--": "3", + "....-": "4", + ".....": "5", + "-....": "6", + "--...": "7", + "---..": "8", + "----.": "9", + ".-": "A", + "-...": "B", + "-.-.": "C", + "-..": "D", + ".": "E", + "..-.": "F", + "--.": "G", + "....": "H", + "..": "I", + ".---": "J", + "-.-": "K", + ".-..": "L", + "--": "M", + "-.": "N", + "---": "O", + ".--.": "P", + "--.-": "Q", + ".-.": "R", + "...": "S", + "-": "T", + "..-": "U", + "...-": "V", + ".--": "W", + "-..-": "X", + "-.--": "Y", + "--..": "Z", + ".-.-.-": ".", + "--..--": ",", + "..--..": "?", + ".----.": "'", + "-.-.--": "!", + "-..-.": "/", + "-.--.": "(", + "-.--.-": ")", + ".-...": "&", + "---...": ":", + "-.-.-.": ";", + "-...-": "=", + ".-.-.": "+", + "-....-": "-", + "..--.-": "_", + ".-..-.": "\"", + "..-...": "^", + "...-..-": "$", + ".--.-.": "@" +} diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..9f1e8c9 --- /dev/null +++ b/gui.py @@ -0,0 +1,150 @@ +import os + +import matplotlib.pyplot as plt +import PySimpleGUI as sg + +from config import ABOUT_TEXT, DEFAULTS + + +# ---------------------------------------------------- Theme Setup --------------------------------------------------- # + +sg.theme("DarkBlue") +plt.style.use('dark_background') +plt.rcParams['figure.facecolor'] = "#1a2835" +plt.rcParams['figure.figsize'] = (11, 5.5) +plt.rcParams['font.family'] = 'sans' +plt.rcParams['font.size'] = 10.0 +SMALLFONT = ("Helvitica", 10) +APPFONT = ('Helvitica', 12) +MONOSPACEFONT = ('Consolas', 12) + + +# -------------------------------------------------------------------------------------------------------------------- # + +def create_window(filename): + layout = generate_layout(filename) + window = sg.Window('Morse Analyzer', layout, grab_anywhere=True, finalize=True, location=(0, 0), resizable=False, + font=APPFONT) + return window + + +def show_about(): + sg.popup_ok(ABOUT_TEXT, title='About', icon='info', keep_on_top=True, grab_anywhere=True, font=APPFONT) + + +def get_filename(): + # Get the filename from the user + filename = sg.popup_get_file('Choose an audio file', keep_on_top=True, grab_anywhere=True, location=(0, 0), + font=APPFONT, file_types=(("Audio Files", "*.wav"),)) + if filename is None: + return + while not os.path.exists(filename): + sg.popup_error('Please select a file!', keep_on_top=True) + filename = sg.popup_get_file('Choose an audio file', keep_on_top=True, grab_anywhere=True, location=(0, 0), + font=APPFONT) + if filename is None: + return + return filename + + +def generate_layout(filename): + plotting_frame = sg.Frame('Plotting', layout=[[ + sg.Column(layout=[ + [ + sg.T('Time window limits:', s=25), + sg.Input(k='plot_time_min', s=10, default_text=DEFAULTS['plot_time_min']), + sg.T('-'), + sg.Input(k='plot_time_max', s=10, default_text=DEFAULTS['plot_time_max']) + ], + [ + sg.T('Frequency window limits:', s=25), + sg.Input(k='plot_freq_min', s=10, default_text=DEFAULTS['plot_freq_min']), + sg.T('-'), + sg.Input(k='plot_freq_max', s=10, default_text=DEFAULTS['plot_freq_max']) + ], + [sg.CB('Show Grids', default=DEFAULTS['plot_grids'], k='plot_grids')] + ]), + sg.Column(layout=[ + [sg.T('', k='-COORDS-', s=20, justification='center')], + [sg.T('', k='-VALUE-', s=20, justification='center')], + [sg.Button('Plot', k='-PLOT-', expand_x=True)] + ]) + ]]) + output_layout = [ + [sg.Canvas(k='-CANVAS-')], + [sg.Column([[plotting_frame]], expand_x=True, element_justification='center', p=((5, 5), (20, 10)))] + ] + shortened = filename if len(filename) < 40 else '...' + filename[-37:] + input_layout = [ + # ------------------------------------------ Filename And SR Display ----------------------------------------- # + [sg.Frame(title='File', expand_x=True, pad=((5, 5), (0, 20)), layout=[ + [sg.T(shortened, s=40, justification='center', tooltip=filename)], + [ + sg.T('Sample rate:'), + sg.T('', k='-SR-', expand_x=True, justification='end') + ] + ])], + + # ------------------------------------------- Re-sampling Settings ------------------------------------------- # + [sg.Frame(title='Sampling', pad=((5, 5), (0, 20)), element_justification='center', expand_x=True, layout=[ + [ + sg.T('FFT bins:', expand_x=True), + sg.Input(k='n_fft', s=15, justification="end", default_text=DEFAULTS['n_fft']) + ], + [ + sg.T('FFT bin size:'), + sg.T('', k='-BINSIZE-', expand_x=True, justification='end') + ], + [ + sg.T('Window length:', expand_x=True), + sg.Input(k='win_length', s=15, justification="end", default_text=DEFAULTS['win_length']) + ], + [ + sg.T('Hop length:', expand_x=True), + sg.Input(k='hop_length', s=15, justification="end", default_text=DEFAULTS['hop_length']) + ], + [sg.Button('Render', k='-RENDER-', s=10)] + ])], + + # ----------------------------------------- Audio Filtering Settings ----------------------------------------- # + [sg.Frame(title='Filtering', pad=((5, 5), (0, 20)), element_justification='center', expand_x=True, layout=[ + [ + sg.CB('Threshold (dB):', expand_x=True, default=DEFAULTS['apply_threshold_db'], k='apply_threshold_db'), + sg.Input(k='threshold_db', s=15, justification="end", default_text=DEFAULTS['threshold_db']) + ], + [ + sg.CB('Frequency band (Hz):', expand_x=True, default=DEFAULTS['apply_freq_band'], k='apply_freq_band'), + sg.Input(k='freq_band_min', s=10, justification="end", default_text=DEFAULTS['freq_band_min']), + sg.T('-'), + sg.Input(k='freq_band_max', s=10, justification="end", default_text=DEFAULTS['freq_band_max']) + ], + [ + sg.CB('Time band (s):', expand_x=True, default=DEFAULTS['apply_time_band'], k='apply_time_band'), + sg.Input(k='time_band_min', s=10, justification="end", default_text=DEFAULTS['time_band_min']), + sg.T('-'), + sg.Input(k='time_band_max', s=10, justification="end", default_text=DEFAULTS['time_band_max']) + ], + [sg.Button('Apply', k='-APPLY-', s=10)] + ])], + + # ------------------------------------------- Morse Decode Display ------------------------------------------- # + [sg.Frame(title='Decoding', pad=((5, 5), (0, 10)), expand_x=True, element_justification='center', layout=[ + [sg.Button('Solve', k='-SOLVE-', s=10)], + [sg.Multiline(s=(40, 5), k='-ENCODED-', font=MONOSPACEFONT)], + [sg.Button('Decode', k='-DECODE-', s=10)], + [sg.Multiline(s=(40, 4), k='-DECODED-', font=MONOSPACEFONT)] + ])] + ] + + # ================================================================================================================ # + # LAYOUT # + # ================================================================================================================ # + layout = [ + [sg.Menu([['&Help', ['&About::-ABOUT-']]], tearoff=False)], + [ + sg.Column(output_layout, vertical_alignment='top', expand_y=True), + sg.Column(input_layout, vertical_alignment='top', expand_y=True) + ], + [sg.StatusBar('Ready', k='-STATUS-', p=0, size=180, font=SMALLFONT)] + ] + return layout diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..faba47af1ec02ed810112ea8ea2125bc2296e69a GIT binary patch literal 326 zcmZQzU<5)11tu_Uz{tQL#=yX!0mKSG>;S|dd3@mi|Nkh`^8f$;fMW)RAM!9P|Ko=| Y3?r+-!j`dTu)v1l<^uJgSUSKQ07cq7d;kCd literal 0 HcmV?d00001 diff --git a/main.py b/main.py new file mode 100644 index 0000000..654b548 --- /dev/null +++ b/main.py @@ -0,0 +1,95 @@ +import traceback + +import matplotlib.pyplot as plt +import PySimpleGUI as sg +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg + +import morse +from config import DEFAULTS +from gui import create_window, get_filename, show_about +from plotting import plot +from util import bin_to_freq, frames_to_time, get_bin_size + + +# ------------------------------------------------- Helper Functions ------------------------------------------------- # + +def draw_figure(canvas, figure): + figure_canvas_agg = FigureCanvasTkAgg(figure, canvas) + figure_canvas_agg.draw() + figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1) + return figure_canvas_agg + + +def setup_interactions(figure, dft, sr, values, window): + def notify_mouse_move(event): + if event.xdata is not None and event.ydata is not None and int(event.ydata) < dft.shape[0]: + coords = f"{frames_to_time(event.xdata, sr, values):.4f} s {bin_to_freq(int(event.ydata), sr, values):.2f} Hz" + window['-COORDS-'].update(coords) + window['-VALUE-'].update(f"{dft[int(event.ydata)][int(event.xdata)]:.2f} dB") + figure.canvas.mpl_connect('motion_notify_event', notify_mouse_move) + + +# ---------------------------------------------------- Event Loop ---------------------------------------------------- # + +def event_loop(window): + try: + # Load the audio file + y, sr = morse.load_file(filename) + window['-SR-'].update(f"{sr} Hz") + dft = morse.get_dft(y, sr, DEFAULTS) + bin_size = get_bin_size(sr, DEFAULTS) + window['-BINSIZE-'].update(f"{bin_size:.2f} Hz") + # Plot the spectrogram + fig, ax = plot(dft, sr, DEFAULTS) + fig_canvas_agg = draw_figure(window['-CANVAS-'].TKCanvas, fig) + setup_interactions(fig, dft, sr, DEFAULTS, window) + # Wait for the user interaction + while True: + event, values = window.read() + + if event == sg.WIN_CLOSED: + break + elif event == "-RENDER-": + window['-STATUS-'].update("Rendering...") + dft = morse.get_dft(y, sr, values) + bin_size = get_bin_size(sr, values) + window['-BINSIZE-'].update(f"{bin_size:.2f} Hz") + window['-STATUS-'].update('Rendered') + elif event == "-APPLY-": + window['-STATUS-'].update("Applying...") + dft = morse.tweak_dft(dft, sr, values) + window['-STATUS-'].update(value='Filters applied') + elif event == "-DECODE-": + window['-DECODED-'].update(morse.decode(values['-ENCODED-'])) + elif event == "-SOLVE-": + window['-STATUS-'].update("Solving...") + code, status = morse.solve(dft, sr, values) + window['-STATUS-'].update(status) + window['-ENCODED-'].update(code) + window.refresh() + elif event == 'About::-ABOUT-': + show_about() + + if event in ("-PLOT-", "-RENDER-", "-APPLY-"): + plt.close('all') + fig_canvas_agg.get_tk_widget().forget() + fig, ax = plot(dft, sr, values) + fig_canvas_agg = draw_figure(window['-CANVAS-'].TKCanvas, fig) + setup_interactions(fig, dft, sr, values, window) + except Exception as e: + tb = traceback.format_exc() + print(tb) + sg.Print('An error occurred. Details:', e, tb, blocking=True) + finally: + window.close() + + +# ------------------------------------------------------ Driver ------------------------------------------------------ # + +if __name__ == '__main__': + filename = get_filename() + if filename != None: + # Create the window + window = create_window(filename) + # Run the main loop + event_loop(window) diff --git a/morse.py b/morse.py new file mode 100644 index 0000000..80cd846 --- /dev/null +++ b/morse.py @@ -0,0 +1,153 @@ +import numpy as np +import sklearn.cluster +import scipy.io.wavfile +import scipy.signal + +from config import MORSE +from util import bin_to_freq, frames_to_time, get_domains + + +def load_file(filename): + sr, y = scipy.io.wavfile.read(filename) + if y.ndim > 1: + y = np.mean(y, axis=1).flatten() + y = np.interp(y, (y.min(), y.max()), (-1, 1)) + return y, sr + + +def get_dft(y, sr, values): + # get stft + n_fft = int(values['n_fft']) + win_length = int(values['win_length']) + hop_length = int(values['hop_length']) + f,t, stft = scipy.signal.stft(y, sr, window='hann', nperseg=win_length, noverlap=win_length-hop_length, nfft=n_fft) + + # convert to dB + amin = 1e-10 + top_db = 80.0 + magnitude = np.abs(stft) + ref_value = np.max(magnitude) ** 2 + power = np.square(magnitude, out=magnitude) + + log_spec = 10.0 * np.log10(np.maximum(amin, power)) + log_spec -= 10.0 * np.log10(np.maximum(amin, ref_value)) + log_spec = np.maximum(log_spec, log_spec.max() - top_db) + + return log_spec + + +def tweak_dft(dft, sr, values): + # apply threshold db filter + if(values['apply_threshold_db']): + threshold_db = -80 if values['threshold_db'] == '' else float(values['threshold_db']) + dft = np.where(dft > threshold_db, dft, -80).astype(np.int8) + + boundaries = (values['time_band_min'], values['time_band_max'], values['freq_band_min'], values['freq_band_max']) + time_domain, freq_domain = get_domains(dft.shape, sr, boundaries, values) + + # apply frequency band filter + if values['apply_freq_band']: + top = np.zeros((freq_domain[0], dft.shape[1]), dtype=np.int8) - 80 + bottom = np.zeros((dft.shape[0] - freq_domain[1], dft.shape[1]), dtype=np.int8) - 80 + dft = np.concatenate((top, dft[freq_domain[0]:freq_domain[1], :], bottom), axis=0) + # apply time band filter + if values['apply_time_band']: + top = np.zeros((dft.shape[0], time_domain[0]), dtype=np.int8) - 80 + bottom = np.zeros((dft.shape[0], dft.shape[1] - time_domain[1]), dtype=np.int8) - 80 + dft = np.concatenate((top, dft[:, time_domain[0]:time_domain[1]], bottom), axis=1) + # return dft + return dft + + +def solve(dft, sr, values): + interp = np.interp(dft, [np.amin(dft), np.amax(dft)], [0, 1]) # map to [0, 1] + status = [] + + # -------------------------------------------- Find Dominant Frequency ------------------------------------------- # + dominant_freq_bin = np.argmax(np.mean(interp, axis=1), axis=0) + status.append('Dominant frequency found between: {:.2f} Hz and {:.2f} Hz'.format( + bin_to_freq(dominant_freq_bin, sr, values), bin_to_freq(dominant_freq_bin+1, sr, values))) + + # ----------------------------------- Find The Positions Of Rising And Falling ----------------------------------- # + binary_data = np.where(interp[dominant_freq_bin] > 0.85, 1, 0) # 0.85 is the threshold + diff = np.diff(binary_data) # find the differences between consecutive values to find the rising and falling + rising_idx = np.nonzero(diff == 1)[0] + falling_idx = np.nonzero(diff == -1)[0] + + # if we only observed the falling edge, we need to add the rising edge at the beginning + if falling_idx[0] < rising_idx[0]: + rising_idx = np.insert(rising_idx, 0, -1) + + # if value did not fell at the end, we need to add the falling edge at the end + if rising_idx[-1] > falling_idx[-1]: + falling_idx = np.insert(falling_idx, len(falling_idx), len(binary_data) - 1) + + on_frames = falling_idx - rising_idx # the number of samples between rising and falling + off_frames = rising_idx[1:] - falling_idx[:len(falling_idx)-1] # the number of samples btwn falling and rising + + # ------------------------------------------------- Find Symbols ------------------------------------------------- # + if len(on_frames) == 0: + status.append('!Could not found any dash/dot symbols!') + return '', ' | '.join(status) + + # separate symbols into dot and dash + symbol_cluster = sklearn.cluster.KMeans(2).fit(on_frames.reshape(-1, 1)) + + sorted_label_idx = np.argsort(symbol_cluster.cluster_centers_.flatten()) + dot_label = sorted_label_idx[0] + dash_label = sorted_label_idx[1] + status.append('Dot: {:.0f} ms, Dash: {:.0f} ms'.format( + 1000*frames_to_time(symbol_cluster.cluster_centers_.flatten()[dot_label], sr, values), + 1000*frames_to_time(symbol_cluster.cluster_centers_.flatten()[dash_label], sr, values))) + + # extract symbols + symbols = ['.' if i == dot_label else '-' for i in symbol_cluster.labels_] + + # ------------------------------------------------- Find Spacings ------------------------------------------------ # + if len(off_frames) == 0: + status.append('!Could not find spacing between symbols!') + return '', ' | '.join(status) + + # separate spacings into symbol, letter, and word lengths + spacing_cluster = sklearn.cluster.KMeans(3).fit(off_frames.reshape(-1, 1)) + + sorted_label_idx = np.argsort(spacing_cluster.cluster_centers_.flatten()) + symbol_spacing = sorted_label_idx[0] + letter_spacing = sorted_label_idx[1] + word_spacing = sorted_label_idx[2] + + # break into symbols + symbol_break_idx = np.flatnonzero(spacing_cluster.labels_ != symbol_spacing) + 1 + remaining_spacings = spacing_cluster.labels_[spacing_cluster.labels_ != symbol_spacing] + # break into words + word_break_idx = np.flatnonzero(remaining_spacings == word_spacing) + 1 + + status.append('Symbol spacing: {:.0f} ms, Letter spacing: {:.0f} ms, Word spacing: {:.0f} ms'.format( + 1000*frames_to_time(spacing_cluster.cluster_centers_.flatten()[symbol_spacing], sr, values), + 1000*frames_to_time(spacing_cluster.cluster_centers_.flatten()[letter_spacing], sr, values), + 1000*frames_to_time(spacing_cluster.cluster_centers_.flatten()[word_spacing], sr, values))) + + # --------------------------------------------------- Find Code -------------------------------------------------- # + symbol_start_idx = [0] + symbol_break_idx.tolist() + symbol_end_idx = symbol_break_idx.tolist() + [len(symbols)] + letters = ["".join(symbols[i:j]) for i, j in zip(symbol_start_idx, symbol_end_idx)] + + word_start_idx = [0] + (word_break_idx).tolist() + word_end_idx = (word_break_idx).tolist() + [len(letters)] + words = [" ".join(letters[i:j]) for i, j in zip(word_start_idx, word_end_idx)] + + return '/'.join(words), ' | '.join(status) + + +def decode(encoded): + code = encoded.strip() + if code == '': + return '' + words = code.split('/') + decoded = [] + for word in words: + letters = word.split(' ') + for letter in letters: + decoded.append(MORSE[letter] if letter in MORSE else '¿') + decoded.append(' ') + return ''.join(decoded) diff --git a/plotting.py b/plotting.py new file mode 100644 index 0000000..ac1707d --- /dev/null +++ b/plotting.py @@ -0,0 +1,36 @@ +import matplotlib.pyplot as plt +import numpy as np + +from util import fft_frequencies, frames_to_time, get_domains + + +def plot(dft, sr, values): + fig, ax = plt.subplots() + img = ax.imshow(dft, aspect='auto', origin='lower', interpolation="none") + fig.colorbar(img, ax=ax, format="%+2.f dB") + ax.set_title('Spectrogram') + ax.grid(values['plot_grids']) + plt.subplots_adjust(left=0.1, right=1.05, top=0.95, bottom=0.15) + + boundaries = (values['plot_time_min'], values['plot_time_max'], values['plot_freq_min'], values['plot_freq_max']) + time_domain, freq_domain = get_domains(dft.shape, sr, boundaries, values) + + # Handle time axis + frame_times = frames_to_time(np.arange(dft.shape[1]), sr, values) + x_ticks = np.linspace(time_domain[0], time_domain[1], 15, endpoint=False).astype(int) + x_ticks_labels = [f"{x:.2f}" for x in frame_times[x_ticks]] + ax.set_xticks(x_ticks) + ax.set_xticklabels(x_ticks_labels, rotation=45) + ax.set_xlabel('Time (s)') + ax.set_xlim(time_domain) + + # Handle frequency axis + fft_freqs = fft_frequencies(sr, values) + y_ticks = np.linspace(freq_domain[0], freq_domain[1], 10, endpoint=False).astype(int) + y_ticks_labels = [f"{x:.2f}" for x in fft_freqs[y_ticks]] + ax.set_yticks(y_ticks) + ax.set_yticklabels(y_ticks_labels) + ax.set_ylabel('Frequency (Hz)') + ax.set_ylim(freq_domain) + + return fig, ax diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b561e48 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +matplotlib==3.5.1 +numpy==1.21.5 +PySimpleGUI==4.60.3 +scikit_learn==1.1.2 +scipy==1.7.3 diff --git a/util.py b/util.py new file mode 100644 index 0000000..145e842 --- /dev/null +++ b/util.py @@ -0,0 +1,73 @@ +import numpy as np + + +def frames_to_samples(f, values): + offset = int(int(values['n_fft']) // 2) + return (np.asanyarray(f) * int(values['hop_length']) + offset).astype(int) + + +def samples_to_frames(s, values): + offset = int(int(values['n_fft']) // 2) + return np.floor((np.asanyarray(s) - offset) // int(values['hop_length'])).astype(int) + + +def samples_to_time(s, sr): + return np.asanyarray(s) / float(sr) + + +def time_to_samples(t, sr): + return (np.asanyarray(t) * sr).astype(int) + + +def frames_to_time(f, sr, values): + samples = frames_to_samples(f, values) + return samples_to_time(samples, sr) + + +def time_to_frames(t, sr, values): + samples = time_to_samples(t, sr) + return samples_to_frames(samples, values) + + +def fft_frequencies(sr, values): + return np.fft.rfftfreq(n=int(values['n_fft']), d=1.0 / sr) + + +def freq_to_bin(f, sr, values): + return int(f / get_bin_size(sr, values)) + + +def get_bin_size(sr, values): + return sr / int(values['n_fft']) + + +def bin_to_freq(f, sr, values): + return f * get_bin_size(sr, values) + + +def get_domains(shape, sr, boundaries, values): + # time domain + if boundaries[0] == '': + time_min = 0 + else: + in_time_min = float(boundaries[0]) + time_min = max(0, time_to_frames(in_time_min, sr, values)) + if boundaries[1] == '': + time_max = shape[1] + else: + in_time_max = float(boundaries[1]) + time_max = min(shape[1], time_to_frames(in_time_max, sr, values)) + + # frequency domain + if boundaries[2] == '': + freq_min = 0 + else: + in_freq_min = float(boundaries[2]) + freq_min = max(0, freq_to_bin(in_freq_min, sr, values)) + if boundaries[3] == '': + freq_max = shape[0] + else: + in_freq_max = float(boundaries[3]) + freq_max = min(shape[0], freq_to_bin(in_freq_max, sr, values)) + + return ((time_min, time_max), (freq_min, freq_max))