File size: 10,442 Bytes
48cf656
 
 
 
 
 
 
 
4771b23
 
24aa623
 
48cf656
 
 
 
 
 
 
4771b23
24aa623
 
48cf656
24aa623
 
 
 
 
 
 
 
 
48cf656
24aa623
48cf656
24aa623
 
48cf656
 
24aa623
48cf656
 
4771b23
24aa623
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4771b23
48cf656
 
24aa623
 
 
 
 
 
 
 
 
 
48cf656
 
 
4771b23
48cf656
24aa623
 
 
 
 
 
 
 
 
 
 
48cf656
 
4771b23
275c3ae
24aa623
 
 
 
 
 
 
 
 
 
48cf656
 
4771b23
48cf656
24aa623
 
 
 
 
 
 
 
 
 
48cf656
 
 
24aa623
48cf656
 
 
 
 
 
24aa623
48cf656
 
4771b23
48cf656
24aa623
 
 
 
 
 
 
 
 
 
48cf656
 
 
 
24aa623
48cf656
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4771b23
48cf656
24aa623
 
 
 
 
 
 
 
 
 
 
48cf656
24aa623
 
48cf656
24aa623
 
48cf656
24aa623
 
48cf656
 
 
4771b23
48cf656
24aa623
 
 
 
 
 
 
 
 
 
48cf656
 
24aa623
48cf656
 
 
24aa623
48cf656
 
 
 
 
 
 
 
 
 
4771b23
48cf656
24aa623
 
 
 
 
 
 
 
 
 
48cf656
 
 
 
 
 
24aa623
48cf656
 
 
 
4771b23
48cf656
24aa623
 
 
 
 
 
 
 
 
 
 
b9d5055
 
275c3ae
24aa623
 
 
 
 
 
 
b9d5055
24aa623
 
 
 
 
48cf656
24aa623
48cf656
 
 
4771b23
48cf656
 
24aa623
48cf656
 
24aa623
48cf656
24aa623
48cf656
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
import numpy as np
from scipy.io.wavfile import write, read
from tqdm import tqdm
import gradio as gr
from scipy.signal import butter, lfilter
import reedsolo
import os

# ---------------Parameters--------------- #

input_file = 'input_text.wav'
output_file = 'output_filtered_sender.wav'

low_frequency = 18000
high_frequency = 19000
bit_duration = 0.007
sample_rate = 44100
amplitude_scaling_factor = 15.0


# ----------------Useless----------------  #

def delete_file(file_path):
    """
    This function deletes a file at the specified path.

    Parameters:
    file_path (str): The path to the file to be deleted.

    Returns:
    None
    """
    try:
        # Attempt to remove the file
        os.remove(file_path)

        # If successful, print a success message
        print(f"File '{file_path}' deleted successfully.")
    except OSError as e:
        # If an error occurs (like the file does not exist), print an error message
        print(f"Error deleting file '{file_path}': {e}")


# -----------------Filter----------------- #

def butter_bandpass(sr, order=5):
    """
    This function designs a Butterworth bandpass filter.

    Parameters:
    sr (int): The sample rate of the audio.
    order (int): The order of the filter.

    Returns:
    tuple: The filter coefficients `b` and `a`.
    """
    # Calculate the Nyquist frequency
    nyquist = 0.5 * sr

    # Normalize the cutoff frequencies with a 500 Hz offset
    low = (low_frequency - 500) / nyquist
    high = (high_frequency + 500) / nyquist

    # Design the Butterworth bandpass filter
    coef = butter(order, [low, high], btype='band')

    # Extract the filter coefficients
    b = coef[0]
    a = coef[1]

    return b, a


def butter_bandpass_filter(data, sr, order=5):
    """
    This function applies the Butterworth bandpass filter to a given data.

    Parameters:
    data (array): The audio data to be filtered.
    sr (int): The sample rate of the audio.
    order (int): The order of the filter.

    Returns:
    array: The filtered audio data.
    """
    # Get the filter coefficients
    b, a = butter_bandpass(sr, order=order)

    # Apply the filter to the data
    y = lfilter(b, a, data)

    return y


def filtered():
    """
    This function reads an audio file, applies the bandpass filter to the audio data,
    and then writes the filtered data to an output file.

    Returns:
    str: A success message if the audio is filtered correctly, otherwise an error message.
    """
    # Read the audio data from the input file
    sr, data = read(input_file)

    # Apply the bandpass filter to the audio data
    filtered_data = butter_bandpass_filter(data, sr)

    # Write the filtered data to the output file
    write(output_file, sr, np.int16(filtered_data))

    return "Filtered Audio Generated"


# -----------------Sender----------------- #

def text_to_binary(text):
    """
    This function converts a text string to a binary string.

    Parameters:
    text (str): The text string.

    Returns:
    str: The binary string.
    """
    # Convert each character in the text to its ASCII value, format it as an 8-bit binary number, and join them together
    binary_string = ''.join(format(ord(char), '08b') for char in text)
    return binary_string


def signal_function(frequency, time):
    """
    This function generates a sinusoidal signal with a given frequency and time.

    Parameters:
    frequency (float): The frequency of the signal.
    time (array): The time values for the signal.

    Returns:
    array: The generated signal.
    """
    # Return a sinusoidal signal with the given frequency and time
    return np.sin(2 * np.pi * frequency * time)


def generate_silence(duration):
    """
    This function generates a silence signal with a given duration.

    Parameters:
    duration (float): The duration of the silence.

    Returns:
    array: The silence signal.
    """
    # Return a zero signal with the length corresponding to the given duration
    return np.zeros(int(sample_rate * duration))


def binary_signal(binary_string):
    """
    This function converts a binary string to a signal.

    Parameters:
    binary_string (str): The binary string.

    Returns:
    array: The signal.
    """
    # Generate the time values for the signal
    t = np.linspace(0, bit_duration, int(sample_rate * bit_duration), False)
    signal = []

    # For each bit in the binary string, generate a signal with the low or high frequency depending on the bit value
    for bit in tqdm(binary_string, desc="Generating Signal"):
        if bit == '0':
            signal.append(amplitude_scaling_factor * np.sign(signal_function(low_frequency, t)))
        else:
            signal.append(amplitude_scaling_factor * np.sign(signal_function(high_frequency, t)))

    # Concatenate the generated signals into one signal
    return np.concatenate(signal)


def flag_encoding(bit_value):
    """
    This function encodes a bit value into a flag signal.

    Parameters:
    bit_value (int): The bit value (0 or 1).

    Returns:
    array: The flag signal.
    """
    # Generate the time values for the flag signal
    flag_duration = 6 * 0.0014
    t_flag = np.linspace(0, flag_duration, int(sample_rate * flag_duration), False)
    signal = []

    # Depending on the bit value, generate a flag signal with the corresponding binary flag
    if bit_value == 0:
        binary_flag = "100001"
        for bit in binary_flag:
            if bit == '0':
                signal.append(amplitude_scaling_factor * np.sign(signal_function(low_frequency, t_flag)))
            else:
                signal.append(amplitude_scaling_factor * np.sign(signal_function(high_frequency, t_flag)))

        return np.concatenate(signal)
    else:
        binary_flag = "011110"
        for bit in tqdm(binary_flag, desc="Generating Signal"):
            if bit == '0':
                signal.append(amplitude_scaling_factor * np.sign(signal_function(low_frequency, t_flag)))
            else:
                signal.append(amplitude_scaling_factor * np.sign(signal_function(high_frequency, t_flag)))

        return np.concatenate(signal)


def encode_rs(binary_string, ecc_bytes):
    """
    This function encodes a binary string using Reed-Solomon encoding.

    Parameters:
    binary_string (str): The binary string.
    ecc_bytes (int): The number of error correction bytes used in the encoding.

    Returns:
    str: The encoded binary string.
    """
    # Convert the binary string to a bytearray
    byte_data = bytearray(int(binary_string[i:i + 8], 2) for i in range(0, len(binary_string), 8))

    # Initialize a Reed-Solomon codec
    rs = reedsolo.RSCodec(ecc_bytes)

    # Encode the bytearray
    encoded_data = rs.encode(byte_data)

    # Convert the encoded bytearray back to a binary string
    encoded_binary_string = ''.join(format(byte, '08b') for byte in encoded_data)
    return encoded_binary_string


def manchester_encoding(binary_string):
    """
    This function encodes a binary string using Manchester encoding.

    Parameters:
    binary_string (str): The binary string.

    Returns:
    array: The Manchester encoded signal.
    """
    # Encode the binary string using Reed-Solomon encoding
    encode_binary_string = encode_rs(binary_string, 20)

    # Generate the time values for the signal
    t = np.linspace(0, bit_duration, int(sample_rate * bit_duration), False)
    signal = []

    # For each bit in the encoded binary string, generate a Manchester encoded signal
    for bit in tqdm(encode_binary_string, desc="Generating Signal"):
        if bit == '0':
            signal.append(amplitude_scaling_factor * np.sign(signal_function(low_frequency, t)))
            signal.append(amplitude_scaling_factor * np.sign(signal_function(high_frequency, t)))
        else:
            signal.append(amplitude_scaling_factor * np.sign(signal_function(high_frequency, t)))
            signal.append(amplitude_scaling_factor * np.sign(signal_function(low_frequency, t)))

    return np.concatenate(signal)


def binary_to_signal(binary_string):
    """
    This function converts a binary string to a signal.

    Parameters:
    binary_string (str): The binary string.

    Returns:
    array: The signal.
    """
    # Generate the start and end flags and the silence signals
    flag_start = flag_encoding(0)
    flag_end = flag_encoding(1)
    silence_duration = 0.1
    silence_before = generate_silence(silence_duration)
    silence_after = generate_silence(silence_duration)

    # Concatenate the silence signals, the start and end flags, and the Manchester encoded signal into one signal
    signal = np.concatenate([silence_before, flag_start, manchester_encoding(binary_string), flag_end, silence_after])

    return signal


def encode_and_generate_audio(text):
    """
    This function encodes a text string into a binary string, converts the binary string to a signal, and writes the signal to an audio file.

    Parameters:
    text (str): The text string.

    Returns:
    str: A success message if the audio file is generated correctly, otherwise an error message.
    """
    try: 
        # Delete the input and output files if they exist
        #delete_file(input_file)
        #delete_file(output_file)
    
        # Convert the text to a binary string
        binary_string_to_send = text_to_binary(text)
    
        # Convert the binary string to a signal
        signal = binary_to_signal(binary_string_to_send)
    
        # Write the signal to an audio file
        write('input_text.wav', 44100, signal.astype(np.int16))
    
        # Apply the bandpass filter to the audio data and write the filtered data to an output file
        filtered()
    
        return "WAV file generated and ready to be sent."
    except Exception as e:
        # If an error occurs, return an error message
        return f"Error: {str(e)}"


# -----------------Player----------------- #

def play_sound():
    return gr.Audio(output_file, autoplay=True)


# -----------------Interface-----------------#

# Start a Gradio Blocks interface
with gr.Blocks() as demo:
    name = gr.Textbox(label="Your Text")
    output = gr.Textbox(label="Output")
    submit = gr.Button("Generate Audio")
    submit.click(fn=encode_and_generate_audio, inputs=name, outputs=output)

    gr.Interface(fn=play_sound, inputs=[], outputs=gr.Audio(), live=False)

demo.launch()