aboutsummaryrefslogtreecommitdiff
path: root/utils.py
blob: d7ed7d8841cdab9e1f992c7221c5d0b0f9585fa8 (plain)
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
# given the cmdline arg, turns the byte sequencies into a list of frequencies, and vice versa

# 1875 1924 +24, -25, range/2, 1, flipping new info 2 sending or not
import numpy as np
import pyaudio
import struct
from scipy.fftpack import fft


def calculate_send_frequencies(start_freq, freq_range, bytes_per_transmit):
    bits_to_send = 8 * bytes_per_transmit + 2  # 8 bits per byte, 2 bits for flags
    freq_interval = freq_range / (bits_to_send + 1)  # +1 to not include endpoints of range

    freq_list = []
    for i in range(bits_to_send):
        f = int(start_freq + (i + 1) * freq_interval)
        freq_list.append(f)

    # print(freq_list)

    return freq_list


def frequencies_to_bytes(frequencies, expected_freqs):
    # get the interval between frequencies, so we can clamp the range around them
    freq_interval = expected_freqs[1] - expected_freqs[0]
    plus_minus = freq_interval // 2

    byte_list = ['0'] * len(expected_freqs)
    for freq in frequencies:
        for i in range(len(expected_freqs)):
            # clamp the range around the frequency to the frequency
            if expected_freqs[i] - plus_minus <= freq < expected_freqs[i] + plus_minus:
                byte_list[i] = '1'

    return byte_list

def play_data(data, start_freq, freq_step, bytes_per_transmit, p):
    freq_list = calculate_send_frequencies(start_freq, freq_step, bytes_per_transmit)

    for byte in data:
        # print(byte)
        samples = None
        for i, bit in enumerate(byte):
            if bit == '1':
                # print(freq_list[i])
                s = .125 * np.sin(2 * np.pi * np.arange(44100 * 10.0) * freq_list[i] / 44100)
                if samples is None:
                    samples = s
                else:
                    samples = np.add(samples, s)
        if samples is not None:
            # print(samples)
            stream = p.open(format=pyaudio.paFloat32, channels=1, rate=44100, output=True)
            stream.write(samples.astype(np.float32).tobytes())
            stream.stop_stream()
            stream.close()
    # listening_stream = p.open(
    #     format=pyaudio.paInt32,
    #     channels=1,
    #     rate=44100,
    #     input=True,
    #     output=True,
    #     frames_per_buffer=2048 * 2,
    # )
    # if receive_data(listening_stream, start_freq, freq_step, bytes_per_transmit):
    #     print("Success")

"""
:param data: A string of characters.
:return: A list of binary strings.
"""
def string_to_binary(data):
    data_list = []
    for char in data:
        binary_representation = format(ord(char), 'b').zfill(8)
        data_list.append(binary_representation)
    return data_list

def receive_string(binary):
    binary_string = ''.join(binary)
    try:
        print(chr(int(binary_string, 2)))
        return chr(int(binary_string, 2))
    except ValueError:
        print("Error: Invalid binary data")

CHUNK = 2048 * 2
RATE = 44100

def read_audio_stream(stream):
    data = stream.read(CHUNK)
    data_int = struct.unpack(str(CHUNK) + 'i', data)
    return data_int

def get_fundamental_frequency(audio_waveform, start_freq, freq_step, bytes_per_transmit):
        spectrum = fft(audio_waveform)

        # scale and normalize the spectrum, some are imaginary
        scaled_spectrum = np.abs(spectrum)
        scaled_spectrum = scaled_spectrum / (np.linalg.norm(scaled_spectrum) + 1e-16)

        # FIXME: update to self values, given if ur a sender or receiver
        starting_freq = 19800
        end_freq = 20000
        freq_to_index_ratio = CHUNK / RATE
        # only accept the scaled spectrum from our starting range to 20000 Hz
        starting_range_index = int(starting_freq * freq_to_index_ratio)
        ending_range_index = int(end_freq * freq_to_index_ratio)
        # print(starting_freq, end_freq, starting_range_index, ending_range_index)
        restricted_spectrum = scaled_spectrum[starting_range_index:ending_range_index + 1]

        # normalize the restricted spectrum
        indices = np.argwhere(restricted_spectrum > .125)
        # print(indices)

        freqs = [int((indices[i] + starting_range_index) / freq_to_index_ratio) for i in range(len(indices))]
        # print(freqs)

        p = frequencies_to_bytes(freqs, calculate_send_frequencies(start_freq, freq_step, bytes_per_transmit))
        data = p[:8]
        # print(data)
        data = receive_string(data)
        return data


def receive_data(stream, start_freq, freq_step, bytes_per_transmit):
    # freq_list = calculate_send_frequencies(start_freq, freq_step, bytes_per_transmit)

    data = []
    while not data:
        waveform = read_audio_stream(stream)
        freqs = get_fundamental_frequency(waveform, start_freq, freq_step, bytes_per_transmit)
        data.append(freqs)


        # if we see the same data twice in a row, we stop receiving
        # if data[-1] == data[-2]:
        #     break
        # print(data)
    return data[0], True