main application with tuning, playing, and GUI
This commit is contained in:
13
.vscode/settings.json
vendored
Normal file
13
.vscode/settings.json
vendored
Normal 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
19
_detectsong.py
Normal 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
BIN
filtered_audio.wav
Normal file
Binary file not shown.
BIN
fm_capture.bin
Normal file
BIN
fm_capture.bin
Normal file
Binary file not shown.
BIN
fm_capture5.bin
Normal file
BIN
fm_capture5.bin
Normal file
Binary file not shown.
BIN
fm_capture6.bin
Normal file
BIN
fm_capture6.bin
Normal file
Binary file not shown.
BIN
fm_data.raw
Normal file
BIN
fm_data.raw
Normal file
Binary file not shown.
BIN
fm_data_test.wav
Normal file
BIN
fm_data_test.wav
Normal file
Binary file not shown.
BIN
fm_data_test1.raw
Normal file
BIN
fm_data_test1.raw
Normal file
Binary file not shown.
BIN
fm_rewind.wav
Normal file
BIN
fm_rewind.wav
Normal file
Binary file not shown.
27
fmdemod.py
Normal file
27
fmdemod.py
Normal 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
103
fmdemod2.py
Normal 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
36
fmdemod3.py
Normal 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
50
fmdemod_old.py
Normal 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
77
fmplay.py
Normal 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
47
main.py
Normal 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
BIN
output55.wav
Normal file
Binary file not shown.
BIN
output55.zip
Normal file
BIN
output55.zip
Normal file
Binary file not shown.
BIN
output66.wav
Normal file
BIN
output66.wav
Normal file
Binary file not shown.
11
test_vlc.py
Normal file
11
test_vlc.py
Normal 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()
|
||||
Reference in New Issue
Block a user