Skip to content
This repository was archived by the owner on Jun 11, 2025. It is now read-only.

Commit 9344d24

Browse files
authored
Merge pull request #77 from IQTLabs/siggen
signal generator.
2 parents b2b03cd + 4d8035f commit 9344d24

File tree

1 file changed

+160
-0
lines changed

1 file changed

+160
-0
lines changed

rfml/siggen.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import random
5+
import sys
6+
import tempfile
7+
from argparse import ArgumentParser
8+
from subprocess import Popen, PIPE, STDOUT
9+
from gnuradio import analog
10+
from gnuradio import blocks
11+
from gnuradio import filter as grfilter
12+
from gnuradio import gr
13+
from scipy.io import wavfile
14+
15+
16+
class fmsiggen(gr.top_block):
17+
18+
def __init__(
19+
self,
20+
wav_file,
21+
sample_file,
22+
samp_rate,
23+
audio_samp_rate,
24+
audio_gain,
25+
audio_interp=4,
26+
):
27+
gr.top_block.__init__(self, "fmsiggen", catch_exceptions=True)
28+
29+
##################################################
30+
# Variables
31+
##################################################
32+
self.samp_rate = samp_rate
33+
self.audio_samp_rate = audio_samp_rate
34+
self.audio_interp = audio_interp
35+
self.audio_gain = audio_gain
36+
37+
##################################################
38+
# Blocks
39+
##################################################
40+
41+
self.rational_resampler_xxx_0 = grfilter.rational_resampler_ccc(
42+
interpolation=samp_rate,
43+
decimation=(audio_samp_rate * audio_interp),
44+
taps=[],
45+
fractional_bw=0,
46+
)
47+
self.blocks_wavfile_source_0 = blocks.wavfile_source(wav_file, False)
48+
self.blocks_sigmf_sink_minimal_0 = blocks.sigmf_sink_minimal(
49+
item_size=gr.sizeof_gr_complex,
50+
filename=sample_file,
51+
sample_rate=samp_rate,
52+
center_freq=100e6,
53+
author="",
54+
description="",
55+
hw_info="",
56+
is_complex=True,
57+
)
58+
self.blocks_multiply_const_xx_0 = blocks.multiply_const_ff(audio_gain, 1)
59+
self.analog_nbfm_tx_0 = analog.nbfm_tx(
60+
audio_rate=audio_samp_rate,
61+
quad_rate=(audio_samp_rate * audio_interp),
62+
tau=(75e-6),
63+
max_dev=5e3,
64+
fh=(-1.0),
65+
)
66+
67+
##################################################
68+
# Connections
69+
##################################################
70+
self.connect((self.analog_nbfm_tx_0, 0), (self.rational_resampler_xxx_0, 0))
71+
self.connect((self.blocks_multiply_const_xx_0, 0), (self.analog_nbfm_tx_0, 0))
72+
self.connect(
73+
(self.blocks_wavfile_source_0, 0), (self.blocks_multiply_const_xx_0, 0)
74+
)
75+
self.connect(
76+
(self.rational_resampler_xxx_0, 0), (self.blocks_sigmf_sink_minimal_0, 0)
77+
)
78+
79+
80+
def run_siggen(
81+
siggen_cls, int_count, sample_file, samp_rate, audio_samp_rate, audio_gain
82+
):
83+
numbers = [str(random.randint(0, 1000)) for _ in range(int_count)]
84+
with tempfile.TemporaryDirectory() as tmpdir:
85+
wav_file = os.path.join(tmpdir, "test.wav")
86+
try:
87+
p = Popen(
88+
["text2wave", "-F", str(audio_samp_rate), "-o", wav_file],
89+
stdout=PIPE,
90+
stdin=PIPE,
91+
stderr=PIPE,
92+
text=True,
93+
)
94+
except FileNotFoundError:
95+
print("error running text2wave: need festival installed")
96+
sys.exit(-1)
97+
print(f"writing {int_count} numbers")
98+
print(p.communicate(input=" ".join(numbers))[0])
99+
out_samp_rate, audio_data = wavfile.read(wav_file)
100+
audio_secs = audio_data.shape[0] / out_samp_rate
101+
print(f"{audio_secs} seconds of audio")
102+
tb = siggen_cls(wav_file, sample_file, samp_rate, audio_samp_rate, audio_gain)
103+
tb.start()
104+
tb.wait()
105+
106+
107+
def argument_parser():
108+
parser = ArgumentParser()
109+
parser.add_argument(
110+
"--sample_file",
111+
dest="sample_file",
112+
type=str,
113+
default="siggen",
114+
help="base file name for sample file name output",
115+
)
116+
parser.add_argument(
117+
"--samp_rate",
118+
dest="samp_rate",
119+
type=int,
120+
default=int(20.48e6),
121+
help="sample rate",
122+
)
123+
parser.add_argument(
124+
"--int_count",
125+
dest="int_count",
126+
type=int,
127+
default=int(10),
128+
help="number of random integers to say",
129+
)
130+
parser.add_argument(
131+
"--audio_samp_rate",
132+
dest="audio_samp_rate",
133+
type=int,
134+
default=int(44100),
135+
help="audio sample rate",
136+
)
137+
parser.add_argument(
138+
"--audio_gain",
139+
dest="audio_gain",
140+
type=int,
141+
default=int(20),
142+
help="audio gain",
143+
)
144+
return parser
145+
146+
147+
def main():
148+
options = argument_parser().parse_args()
149+
run_siggen(
150+
fmsiggen,
151+
options.int_count,
152+
options.sample_file,
153+
options.samp_rate,
154+
options.audio_samp_rate,
155+
options.audio_gain,
156+
)
157+
158+
159+
if __name__ == "__main__":
160+
main()

0 commit comments

Comments
 (0)