main application with tuning, playing, and GUI

This commit is contained in:
Brandon4466
2024-01-14 23:41:53 -08:00
commit 7ee950b2c5
21 changed files with 388 additions and 0 deletions

13
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,13 @@
{
"python.languageServer": "Pylance",
"python.linting.pylintEnabled": false,
"python.analysis.diagnosticSeverityOverrides": {
"reportMissingModuleSource": "none"
},
"python.analysis.extraPaths": [
"",
"c:\\Users\\spong\\.vscode\\extensions\\joedevivo.vscode-circuitpython-0.1.20-win32-x64\\stubs",
"c:\\Users\\spong\\AppData\\Roaming\\Code\\User\\globalStorage\\joedevivo.vscode-circuitpython\\bundle\\20240113\\adafruit-circuitpython-bundle-py-20240113\\lib"
],
"circuitpython.board.version": null
}

19
_detectsong.py Normal file
View File

@@ -0,0 +1,19 @@
import sounddevice as sd
import numpy as np
import time
import asyncio
from shazamio import Shazam
def rec():
recording = sd.rec((44100 * 5), samplerate=44100, channels=1, detype=np.int16)
sd.wait()
return recording
async def detect():
recording = rec()
shazam = Shazam()
out = await shazam.recognize_song(recording)
print(out)
loop = asyncio.get_event_loop()
loop.run_until_complete(detect())

BIN
filtered_audio.wav Normal file

Binary file not shown.

BIN
fm_capture.bin Normal file

Binary file not shown.

BIN
fm_capture5.bin Normal file

Binary file not shown.

BIN
fm_capture6.bin Normal file

Binary file not shown.

BIN
fm_data.raw Normal file

Binary file not shown.

BIN
fm_data_test.wav Normal file

Binary file not shown.

BIN
fm_data_test1.raw Normal file

Binary file not shown.

BIN
fm_rewind.wav Normal file

Binary file not shown.

27
fmdemod.py Normal file
View File

@@ -0,0 +1,27 @@
from pydub import AudioSegment
from pydub.effects import low_pass_filter
from pydub.playback import play
import sounddevice as sd
import numpy as np
# Input file path
input_file = "fm_data.raw"
# Load the audio file
audio = AudioSegment.from_file(input_file, format="raw", sample_width=2, channels=1, frame_rate=170000)
# Apply low pass filter (if needed)
audio = low_pass_filter(audio, 75000)
play(audio)
# Convert to numpy array
# samples = np.array(audio.get_array_of_samples())
# # Play the audio using sounddevice
# sd.play(samples, samplerate=audio.frame_rate)
# sd.wait()
# audio.export("fm_data.wav", format="wav")
# rx_fm -f 94.7M -M fm -s 170k -A fast -l 0 -E deemp -d driver=hackrf fm_data.raw

103
fmdemod2.py Normal file
View File

@@ -0,0 +1,103 @@
import numpy as np
import matplotlib.pylab as plt
import time
# inspired by https://github.com/osmocom/rtl-sdr/blob/master/src/rtl_fm.c
# loading in the .wav
signal = np.memmap("HackRF_20240111_025820Z_94700kHz_IQ.wav", offset=44)
result = np.zeros(len(signal)//2, dtype=np.cfloat)
downsample = 128
def low_pass(signal):
# simple square window FIR
lowpassed = signal[:]
# needs to be go outside this function
now_r = 0
now_j = 0
i=0
i2=0
prev_index = 0
while (i < len(lowpassed)):
now_r += lowpassed[i]
now_j += lowpassed[i + 1]
i += 2
prev_index += 2
if (prev_index < downsample):
continue
lowpassed[i2] = now_r
lowpassed[i2 + 1] = now_j
prev_index = 0
now_r = 0
now_j = 0
i2 += 2
lp_len = i2
return lowpassed
def multiply(ar, aj, br, bj):
cr = ar * br - aj * bj
cj = aj * br + ar * bj
return cr, cj
def polar_discriminant(ar, aj, br, bj):
cr , cj = multiply(ar, aj, br, -bj)
#print(cr, cj)
angle = np.arctan2(cj, cr)
#print("angle", angle)
# return (angle / np.pi * (1<<14))
return (angle * 180.0 / np.pi)
def fm_demod(signal, result):
pre_r = 0
pre_j = 0
i = 0
pcm = 0
lp_len = len(signal)
# low-passing
lp = low_pass(signal)
#print(lp[0], lp[1], pre_r, pre_j)
pcm = polar_discriminant(lp[0], lp[1], pre_r, pre_j)
result[0] = pcm
for i in range(2, lp_len-1, 2):
# being lazy, only case 0 for now...
# case 0
pcm = polar_discriminant(lp[i], lp[i + 1], lp[i - 2], lp[i - 1])
result[i // 2] = pcm
pre_r = lp[lp_len - 2]
pre_j = lp[lp_len - 1]
result_len = lp_len // 2
#return(result)
time1 = time.time()
signal = -127.5 + signal
fm_demod(signal, result)
print(time.time() - time1)
plt.plot(result[::10])
plt.show()

36
fmdemod3.py Normal file
View File

@@ -0,0 +1,36 @@
import numpy as np
import sounddevice as sd
from scipy.io import wavfile
from scipy.signal import decimate, butter, filtfilt
# Load the raw IQ samples from the file
filename = "fm_capture6.bin"
iq_samples = np.fromfile(filename, dtype=np.int8)
# Define the FM demodulation parameters
sample_rate = int(2e6) # Sample rate of the IQ samples
audio_sample_rate = int(48e3) # Desired audio sample rate
fm_deviation = int(75e3) # FM deviation of the radio signal
channel_width = int(200e3) # Channel width of the low-pass filter
# Design the low-pass filter
nyquist_freq = sample_rate / 2
cutoff_freq = channel_width / nyquist_freq
b, a = butter(10, int(75e3), btype='low', fs=int(2e6))
# Apply the low-pass filter
filtered_audio = filtfilt(b, a, iq_samples)
# Demodulate the FM signal
demodulated_audio = np.diff(filtered_audio.astype(np.float32)) * np.conj(filtered_audio[:-1]).astype(np.float32)
demodulated_audio = decimate(demodulated_audio, int(100))
# Normalize the audio to the range [-1, 1]
normalized_audio = demodulated_audio / np.max(np.abs(demodulated_audio))
# Play the audio
sd.play(normalized_audio, audio_sample_rate)
sd.wait()
# output_filename = "demodulated_audio.wav"
# wavfile.write(output_filename, audio_sample_rate, normalized_audio)

50
fmdemod_old.py Normal file
View File

@@ -0,0 +1,50 @@
import numpy as np
import scipy.signal
import wave
def read_raw_iq_samples(file_path):
with open(file_path, 'rb') as file:
raw_data = file.read()
iq_samples = np.frombuffer(raw_data, dtype=np.int8)
return iq_samples
def fm_demodulation(iq_samples, sample_rate, fm_deviation=75e3):
# Hilbert transform for FM demodulation
analytic_signal = scipy.signal.hilbert(iq_samples)
instantaneous_phase = np.unwrap(np.angle(analytic_signal))
demodulated_signal = np.unwrap(np.angle(iq_samples[1:] * np.conj(iq_samples[:-1]))) / (2.0 * np.pi * 1.0/sample_rate * fm_deviation)
return demodulated_signal
def decimate_audio(signal, decimation_factor):
return scipy.signal.resample(signal, len(signal)//decimation_factor)
def low_pass_filter(signal, cutoff_frequency, sample_rate):
nyquist = 0.5 * sample_rate
normal_cutoff = cutoff_frequency / nyquist
b, a = scipy.signal.butter(6, normal_cutoff, btype='low', analog=False)
filtered_signal = scipy.signal.filtfilt(b, a, signal)
return filtered_signal
def save_wav_file(signal, sample_rate, output_file):
scaled_signal = np.int16(signal / np.max(np.abs(signal)) * 32767)
with wave.open(output_file, 'wb') as wav_file:
wav_file.setnchannels(1)
wav_file.setsampwidth(2)
wav_file.setframerate(sample_rate)
wav_file.writeframes(scaled_signal.tobytes())
def main(input_file, output_file, sample_rate, channel_width, decimation_factor=10, cutoff_frequency=75e3):
iq_samples = read_raw_iq_samples(input_file)
demodulated_signal = fm_demodulation(iq_samples, sample_rate)
decimated_signal = decimate_audio(demodulated_signal, decimation_factor)
filtered_signal = low_pass_filter(decimated_signal, cutoff_frequency, sample_rate/decimation_factor)
new_sample_rate = sample_rate / decimation_factor
save_wav_file(filtered_signal, new_sample_rate, output_file)
if __name__ == "__main__":
input_file_path = "fm_capture.bin"
output_wav_path = "output.wav"
sample_rate = 2e6 # Replace with the actual sample rate of your FM stream
channel_width = 200e3 # Replace with the channel width of your FM signal
main(input_file_path, output_wav_path, sample_rate, channel_width)

77
fmplay.py Normal file
View File

@@ -0,0 +1,77 @@
import subprocess
from time import sleep
import tkinter as ttk
import _detectsong as ds
root = ttk.Tk()
root.title("Radio Controller")
# root.geometry("1280x400")
root.geometry("800x400")
root.attributes("-topmost", True)
# sv_ttk.use_dark_theme()
st1_button = ttk.Button(root, text="94.7M", borderwidth=0, command=lambda: tune(94.7))
st2_button = ttk.Button(root, text="96.9M", borderwidth=0, command=lambda: tune(96.9))
st3_button = ttk.Button(root, text="98.5M", borderwidth=0, command=lambda: tune(98.5))
st4_button = ttk.Button(root, text="100.5M", borderwidth=0, command=lambda: tune(100.5))
st5_button = ttk.Button(root, text="101.1M", borderwidth=0, command=lambda: tune(101.1))
st6_button = ttk.Button(root, text="102.5M", borderwidth=0, command=lambda: tune(102.5))
st7_button = ttk.Button(root, text="103.5M", borderwidth=0, command=lambda: tune(103.5))
st8_button = ttk.Button(root, text="106.5M", borderwidth=0, command=lambda: tune(106.5))
st9_button = ttk.Button(root, text="107.9M", borderwidth=0, command=lambda: tune(107.9))
exit_button = ttk.Button(root, text="Exit", command=lambda: on_close(True))
volume_slider = ttk.Scale(root, from_=0, to=100, orient=ttk.HORIZONTAL)
stop_button = ttk.Button(root, text="Stop", command=lambda: on_close())
# root.protocol("WM_DELETE_WINDOW", lambda e: on_close())
st1_button.grid(row=0, column=0)
st2_button.grid(row=0, column=1)
st3_button.grid(row=0, column=2)
st4_button.grid(row=0, column=3)
st5_button.grid(row=0, column=4)
st6_button.grid(row=0, column=5)
st7_button.grid(row=0, column=6)
st8_button.grid(row=0, column=7)
st9_button.grid(row=0, column=8)
exit_button.grid(row=1, column=0)
volume_slider.grid(row=1, column=1, columnspan=3)
stop_button.grid(row=1, column=4)
# Input file path
input_file = "fm_data.raw"
tuner = None
player = None
def tune(station):
global tuner
global player
# random_num = random.randint(0, 1000000)
# print(random_num)
if tuner is not None:
tuner.kill()
sleep(3)
if player is not None:
player.kill()
tuner = subprocess.Popen(["rx_fm", "-f", f"{station}M", "-M", "fm", "-s", "170k", "-A", "fast", "-l", "0", "-E", "deemp", "-E", "wav", "-d", "driver=hackrf", input_file])
sleep(2.5)
player = subprocess.Popen(["C:\\Program Files\\VideoLAN\\VLC\\vlc", "--play-and-exit", "C:\\Users\\spong\\Documents\\Python Programs\\PySDR\\FM Radio\\fm_data.raw"])
def on_close(halt=False):
if tuner:
tuner.kill()
if player:
player.kill()
if halt == True:
exit()
def adj_vol():
pass
try:
root.mainloop()
except KeyboardInterrupt:
on_close()

47
main.py Normal file
View File

@@ -0,0 +1,47 @@
import numpy as np
import matplotlib.pyplot as plt
from pysdr import IQFileSource
from pysdr import FMReceiver
from hackrf import HackRfSource
# Set the HackRF parameters
sample_rate = 2e6 # 2 MHz sample rate
center_freq = 100e6 # 100 MHz center frequency
# Set the FM receiver parameters
audio_sample_rate = 48e3 # 48 kHz audio sample rate
fm_deviation = 75e3 # FM deviation (typical for broadcast FM)
# Create HackRF source
hackrf_source = HackRfSource(sample_rate, center_freq)
# Create IQ file source
iq_file_source = IQFileSource('fm_radio.iq', sample_rate)
# Create FM receiver
fm_receiver = FMReceiver(sample_rate, audio_sample_rate, fm_deviation)
# Read samples from the source and process them
try:
while True:
samples = hackrf_source.read_samples(1024)
if len(samples) == 0:
break
# Process the samples using the FM receiver
audio_samples = fm_receiver.process(samples)
# Do something with the audio samples (e.g., play them)
# For simplicity, let's just plot the spectrum
plt.specgram(audio_samples, Fs=audio_sample_rate, cmap='viridis')
plt.xlabel('Time (s)')
plt.ylabel('Frequency (Hz)')
plt.title('FM Radio Spectrum')
plt.show()
except KeyboardInterrupt:
pass
finally:
# Close the HackRF source
hackrf_source.close()

BIN
output55.wav Normal file

Binary file not shown.

BIN
output55.zip Normal file

Binary file not shown.

BIN
output66.wav Normal file

Binary file not shown.

11
test_vlc.py Normal file
View File

@@ -0,0 +1,11 @@
import vlc
import time
instance = vlc.Instance('--no-xlib')
player = instance.media_player_new()
media = instance.media_new('C:\\Users\\spong\\Documents\\Python Programs\\PySDR\\FM Radio\\fm_data.raw')
player.set_media(media)
player.play()

5
todo.txt Normal file
View File

@@ -0,0 +1,5 @@
implement volume slider to control the VLC volume.
put stop button in
get a 10 second slice of audio and use shazamio to get the song name etc