nimRum 1.1.0__py3-none-linux_armv7l.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nimRum/__init__.py +0 -0
- nimRum/gitStatus.txt +5 -0
- nimRum/libNimRumAlsaCapture_py.so +0 -0
- nimRum/libNimRumRx_py.so +0 -0
- nimRum/libNimRumTx_py.so +0 -0
- nimRum/nimRumCheckHW.py +42 -0
- nimRum/nimRumMeasSpikes.py +274 -0
- nimRum/nimRumMeasure.py +166 -0
- nimRum/nimRumPyCommon.py +83 -0
- nimRum/nimRumPyLed.py +113 -0
- nimRum/nimRumRotaryEnc.py +100 -0
- nimRum/nimRumRx.py +138 -0
- nimRum/nimRumRxCfg.py +156 -0
- nimRum/nimRumRxVolumeConversion.py +305 -0
- nimRum/nimRumToneGen.py +221 -0
- nimRum/nimRumTx.py +222 -0
- nimRum/nimRumTxCfg.py +253 -0
- nimRum/nimRumTxReadFile_soundfile.py +86 -0
- nimRum/nimRumTxReadFile_wavio.py +96 -0
- nimRum/nimRumTxRemote.py +109 -0
- nimRum/nonBlockingConsole.py +41 -0
- nimRum/runNimRumForever.py +134 -0
- nimRum/runNimRumRx.py +37 -0
- nimRum/runNimRumTx.py +42 -0
- nimRum/txConfig.yaml +63 -0
- nimrum-1.1.0.dist-info/METADATA +174 -0
- nimrum-1.1.0.dist-info/RECORD +30 -0
- nimrum-1.1.0.dist-info/WHEEL +4 -0
- nimrum-1.1.0.dist-info/entry_points.txt +5 -0
- nimrum-1.1.0.dist-info/licenses/LICENSE.txt +22 -0
nimRum/__init__.py
ADDED
|
File without changes
|
nimRum/gitStatus.txt
ADDED
|
Binary file
|
nimRum/libNimRumRx_py.so
ADDED
|
Binary file
|
nimRum/libNimRumTx_py.so
ADDED
|
Binary file
|
nimRum/nimRumCheckHW.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
|
|
3
|
+
#/******************************************************************************
|
|
4
|
+
# * COPYRIGHT (c) 2021-2025, AbtAudio AB, All rights reserved.
|
|
5
|
+
# *
|
|
6
|
+
# * The copyright to the computer program(s) herein is the property of
|
|
7
|
+
# * AbtAudio AB. The computer programs(s) may implement properties protected by
|
|
8
|
+
# * patent(s) such as arising from SE544478C2, US12034828B2 or WO2021/220149.
|
|
9
|
+
# *
|
|
10
|
+
# * Redistribution in any form, with or without modification, is only permitted
|
|
11
|
+
# * with the written permission from AbtAudio AB.
|
|
12
|
+
# *
|
|
13
|
+
# * The computer program(s) may be used for private purposes and for evaluation
|
|
14
|
+
# * purposes.
|
|
15
|
+
# *
|
|
16
|
+
# * The computer program(s) may NOT be used for any commercial purposes without
|
|
17
|
+
# * the written permission from AbtAudio AB.
|
|
18
|
+
# *
|
|
19
|
+
# * The computer program(s) are provided as is, without any warranties, but
|
|
20
|
+
# * feedback in form of suggestions for improvements is encouraged.
|
|
21
|
+
# ******************************************************************************/
|
|
22
|
+
|
|
23
|
+
import platform
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
This is kind of a placeholder for future development
|
|
27
|
+
"""
|
|
28
|
+
class nimRumCheckHW:
|
|
29
|
+
def __init__(self):
|
|
30
|
+
print(platform.uname())
|
|
31
|
+
self.cpuArch = platform.machine()
|
|
32
|
+
self.testHW()
|
|
33
|
+
|
|
34
|
+
def testHW(self):
|
|
35
|
+
if self.cpuArch not in ['aarch64', 'armv6l', 'armv7l']:
|
|
36
|
+
print("Platform not supported")
|
|
37
|
+
quit()
|
|
38
|
+
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
t = nimRumCheckHW()
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
|
|
3
|
+
#/******************************************************************************
|
|
4
|
+
# * COPYRIGHT (c) 2021-2025, AbtAudio AB, All rights reserved.
|
|
5
|
+
# *
|
|
6
|
+
# * The copyright to the computer program(s) herein is the property of
|
|
7
|
+
# * AbtAudio AB. The computer programs(s) may implement properties protected by
|
|
8
|
+
# * patent(s) such as arising from SE544478C2, US12034828B2 or WO2021/220149.
|
|
9
|
+
# *
|
|
10
|
+
# * Redistribution in any form, with or without modification, is only permitted
|
|
11
|
+
# * with the written permission from AbtAudio AB.
|
|
12
|
+
# *
|
|
13
|
+
# * The computer program(s) may be used for private purposes and for evaluation
|
|
14
|
+
# * purposes.
|
|
15
|
+
# *
|
|
16
|
+
# * The computer program(s) may NOT be used for any commercial purposes without
|
|
17
|
+
# * the written permission from AbtAudio AB.
|
|
18
|
+
# *
|
|
19
|
+
# * The computer program(s) are provided as is, without any warranties, but
|
|
20
|
+
# * feedback in form of suggestions for improvements is encouraged.
|
|
21
|
+
# ******************************************************************************/
|
|
22
|
+
|
|
23
|
+
import numpy as np
|
|
24
|
+
import matplotlib.pyplot as plt
|
|
25
|
+
import wavio
|
|
26
|
+
import time
|
|
27
|
+
import statistics
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
import os
|
|
30
|
+
|
|
31
|
+
class NIMRUM_MEAS_SPIKES():
|
|
32
|
+
|
|
33
|
+
def __init__(self, capRate=48000, maxTime_sec=200, maxDiff_us=250*1000, maxInt=1, resultFolder=None):
|
|
34
|
+
self.capRate = capRate
|
|
35
|
+
self.maxDiff_us = maxDiff_us
|
|
36
|
+
self.trigMinLvl = maxInt / 500
|
|
37
|
+
|
|
38
|
+
self.synchDiff_X = np.array([])
|
|
39
|
+
self.synchDiff_Y = np.array([])
|
|
40
|
+
self.rXArr = np.array([])
|
|
41
|
+
self.lXArr = np.array([])
|
|
42
|
+
|
|
43
|
+
self.maxTime_sec = maxTime_sec # To limit size of stored data
|
|
44
|
+
self.currentTime_sec = 0.0
|
|
45
|
+
|
|
46
|
+
self.storeInterval_sec = 10*60
|
|
47
|
+
|
|
48
|
+
self.plotTitle = "Synch Diff - Px VS Py"
|
|
49
|
+
plotFileName = self.plotTitle.replace(" ", "_") + ".pdf"
|
|
50
|
+
if resultFolder == None:
|
|
51
|
+
resultFolder = str(Path.home())
|
|
52
|
+
self.plotFileName = os.path.join(resultFolder, plotFileName)
|
|
53
|
+
self.plotLabel = "Px VS Py"
|
|
54
|
+
|
|
55
|
+
self.lastStorageTime = time.time()
|
|
56
|
+
|
|
57
|
+
def storeResult(self):
|
|
58
|
+
if len(self.synchDiff_X) == 2:
|
|
59
|
+
print("No result to store")
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
ax1 = plt.subplot(111)
|
|
63
|
+
ax1.clear()
|
|
64
|
+
|
|
65
|
+
ax1.plot(self.synchDiff_X, self.synchDiff_Y, label=self.plotLabel, color="black")#, marker=".")
|
|
66
|
+
|
|
67
|
+
ax1.set_title(self.plotTitle)
|
|
68
|
+
ax1.set_xlabel("Time [s]")
|
|
69
|
+
ax1.set_ylabel("Channel accuracy [us]")
|
|
70
|
+
#ax1.set_ylim(-550, 550)
|
|
71
|
+
|
|
72
|
+
plt.savefig(self.plotFileName, format="pdf", bbox_inches="tight")
|
|
73
|
+
|
|
74
|
+
npyFileName = self.plotFileName+'.npy'
|
|
75
|
+
with open(npyFileName, 'wb') as f:
|
|
76
|
+
np.save(f, self.synchDiff_X)
|
|
77
|
+
np.save(f, self.synchDiff_Y)
|
|
78
|
+
|
|
79
|
+
print("Stored: {} and {}".format(self.plotFileName, npyFileName))
|
|
80
|
+
|
|
81
|
+
def _limitdata(self):
|
|
82
|
+
"""
|
|
83
|
+
Just keep latest latest self.maxTime_sec seconds
|
|
84
|
+
In case not spikes detected for a long time, or at least 20 data points
|
|
85
|
+
"""
|
|
86
|
+
while (self.currentTime_sec > self.maxTime_sec) and (len(self.synchDiff_X) > 20):
|
|
87
|
+
self.currentTime_sec -= self.synchDiff_X[0]
|
|
88
|
+
self.synchDiff_X -= self.synchDiff_X[0]
|
|
89
|
+
|
|
90
|
+
self.synchDiff_X = np.delete(self.synchDiff_X, 0)
|
|
91
|
+
self.synchDiff_Y = np.delete(self.synchDiff_Y, 0)
|
|
92
|
+
|
|
93
|
+
self.rXArr = np.delete(self.rXArr, 0)
|
|
94
|
+
self.lXArr = np.delete(self.lXArr, 0)
|
|
95
|
+
|
|
96
|
+
def add(self, indata):
|
|
97
|
+
capR = indata[:,0]
|
|
98
|
+
capL = indata[:,1]
|
|
99
|
+
|
|
100
|
+
capRSpikeIdxs = self.getSpikeIdxs(capR)
|
|
101
|
+
capLSpikeIdxs = self.getSpikeIdxs(capL)
|
|
102
|
+
|
|
103
|
+
capRIdxs, capLIdxs, diffXVals = self.removeUnmatchedSpikes(
|
|
104
|
+
capRSpikeIdxs, capLSpikeIdxs
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if (capRIdxs.size!=0) and (capLIdxs.size!=0):
|
|
108
|
+
|
|
109
|
+
rX = self.approxZeroCrossing(capR, capRIdxs)
|
|
110
|
+
lX = self.approxZeroCrossing(capL, capLIdxs)
|
|
111
|
+
|
|
112
|
+
synchDiffTime_us = rX - lX
|
|
113
|
+
|
|
114
|
+
# This just stored what can be plotted/stored to file
|
|
115
|
+
self.synchDiff_X = np.append(self.synchDiff_X, self.currentTime_sec + (np.array(capRIdxs)/float(self.capRate)) )
|
|
116
|
+
self.synchDiff_Y = np.append(self.synchDiff_Y, list(synchDiffTime_us))
|
|
117
|
+
|
|
118
|
+
if (len(self.rXArr) > 3) and (len(self.lXArr) > 3):
|
|
119
|
+
# TODO: This must be improved
|
|
120
|
+
rD = (rX-self.rXArr[-1]) - np.round(np.median(np.diff(self.rXArr)))
|
|
121
|
+
lD = (lX-self.lXArr[-1]) - np.round(np.median(np.diff(self.lXArr)))
|
|
122
|
+
|
|
123
|
+
meanY = statistics.mean(self.synchDiff_Y)
|
|
124
|
+
else:
|
|
125
|
+
rD = [0]
|
|
126
|
+
lD = [0]
|
|
127
|
+
|
|
128
|
+
meanY = 0.0
|
|
129
|
+
|
|
130
|
+
self.rXArr = np.append(self.rXArr, rX)
|
|
131
|
+
self.lXArr = np.append(self.lXArr, lX)
|
|
132
|
+
|
|
133
|
+
# TODO: This script will soon need cleanup. This is to avoid when captured sample array wraps
|
|
134
|
+
if np.abs(rD) > 500000 and np.abs(lD) > 500000 and len(self.rXArr) > 1:
|
|
135
|
+
self.rXArr = np.delete(self.rXArr, 0)
|
|
136
|
+
self.lXArr = np.delete(self.lXArr, 0)
|
|
137
|
+
|
|
138
|
+
sTimeInt = int(np.round(synchDiffTime_us[0]))
|
|
139
|
+
print("synchDiffTime_us: {:4d} Mean: {:4.0f} DistanceToMean: {:4.0f} Diff R/L: {:4.0f}/{:4.0f} (R-L={:4.0f})".format(
|
|
140
|
+
sTimeInt, np.round(meanY), np.round(sTimeInt - meanY), rD[0], lD[0], rD[0]-lD[0]))
|
|
141
|
+
|
|
142
|
+
self.currentTime_sec += ( float(len(capR)) / float(self.capRate) )
|
|
143
|
+
self._limitdata()
|
|
144
|
+
|
|
145
|
+
if (self.lastStorageTime + self.storeInterval_sec) < time.time():
|
|
146
|
+
print("Storing to file every {}s".format(self.storeInterval_sec))
|
|
147
|
+
self.storeResult()
|
|
148
|
+
self.lastStorageTime = time.time()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def getSpikeIdxs(self, din):
|
|
152
|
+
"""
|
|
153
|
+
Return index where input array is about to cross 0 with highest gradient
|
|
154
|
+
Expects about one spike per capture
|
|
155
|
+
Only returns max 1 peak
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
# Detect if too low signal
|
|
159
|
+
trigLvl = np.max(din)/2
|
|
160
|
+
highValArr = np.where(din > trigLvl)[0]
|
|
161
|
+
|
|
162
|
+
#if (len(highValArr) > len(din)/500) or (trigLvl < self.trigMinLvl):
|
|
163
|
+
if trigLvl < self.trigMinLvl:
|
|
164
|
+
print("Just noise? High Samples:{}/{} TrigLvl:{}<{}".format(len(highValArr), len(din), trigLvl, self.trigMinLvl))
|
|
165
|
+
return np.array([])
|
|
166
|
+
|
|
167
|
+
# Float to ensure no overflow when retreiving diff
|
|
168
|
+
#dinF = din.astype("float64")
|
|
169
|
+
|
|
170
|
+
# Get where din is passing 0, while 'going up'
|
|
171
|
+
a = np.gradient(din)
|
|
172
|
+
resIdx = np.argmax(a)
|
|
173
|
+
|
|
174
|
+
# Want to get the idx of the last negative valued sample before crossing 0
|
|
175
|
+
while (din[resIdx] > 0) and (resIdx > 0):
|
|
176
|
+
resIdx -= 1
|
|
177
|
+
if resIdx == 0:
|
|
178
|
+
print("Max gradient at fishy position")
|
|
179
|
+
return np.array([])
|
|
180
|
+
|
|
181
|
+
# Double check we are in the middle of a square wave
|
|
182
|
+
# Some soundcards have strong overshoot from the transient
|
|
183
|
+
w = 10
|
|
184
|
+
ok = True
|
|
185
|
+
numOfHigh = (din[resIdx+1:resIdx+w+1] >= 0).sum()
|
|
186
|
+
numOfLow = (din[resIdx-w:resIdx] < 0).sum()
|
|
187
|
+
if (numOfHigh != w):
|
|
188
|
+
ok = False
|
|
189
|
+
if (numOfLow != w):
|
|
190
|
+
ok = False
|
|
191
|
+
|
|
192
|
+
if ok == False:
|
|
193
|
+
print("Failed 'middle of square' check. H/L: {}/{}".format(numOfLow, numOfHigh))
|
|
194
|
+
return np.array([])
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
return np.array([resIdx])
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def removeUnmatchedSpikes(self, rIxds, lIdxs):
|
|
201
|
+
"""
|
|
202
|
+
Compare two arrays with indexes.
|
|
203
|
+
Return two arrays where indexes with too large diff has been removed
|
|
204
|
+
TODO: This function made more sense when getSpikeIdxs could return multiple indexes
|
|
205
|
+
"""
|
|
206
|
+
maxAllowedDiff = (self.maxDiff_us * self.capRate) / 1000000
|
|
207
|
+
rRes = []
|
|
208
|
+
lRes = []
|
|
209
|
+
xRes = [] # X-values for kept indexes
|
|
210
|
+
|
|
211
|
+
maxCnt = min(len(rIxds), len(lIdxs))
|
|
212
|
+
idx = 0
|
|
213
|
+
while idx < maxCnt:
|
|
214
|
+
rVal = rIxds[idx]
|
|
215
|
+
lVal = lIdxs[idx]
|
|
216
|
+
idxDiff = rVal - lVal
|
|
217
|
+
|
|
218
|
+
# Spikes seems missing in R
|
|
219
|
+
if idxDiff > maxAllowedDiff:
|
|
220
|
+
idx += 2
|
|
221
|
+
print("Skipped L")
|
|
222
|
+
continue
|
|
223
|
+
# Seems to be missing from L
|
|
224
|
+
if idxDiff < -maxAllowedDiff:
|
|
225
|
+
idx += 2
|
|
226
|
+
print("Skipped R")
|
|
227
|
+
continue
|
|
228
|
+
|
|
229
|
+
rRes.append(rIxds[idx])
|
|
230
|
+
lRes.append(lIdxs[idx])
|
|
231
|
+
xRes.append(idx)
|
|
232
|
+
idx += 1
|
|
233
|
+
|
|
234
|
+
return np.asarray(rRes), np.asarray(lRes), np.asarray(xRes)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def approxZeroCrossing(self, dinArrInt, dinArrXPrevCross):
|
|
238
|
+
"""
|
|
239
|
+
Returns X values in us where zero corrings occur in us.
|
|
240
|
+
Linear approximation
|
|
241
|
+
"""
|
|
242
|
+
timePerSample = 1000000.0 / float(self.capRate)
|
|
243
|
+
|
|
244
|
+
# Float to ensure no overflow when retreiving diff
|
|
245
|
+
dinArr = dinArrInt.astype("float64")
|
|
246
|
+
|
|
247
|
+
newX = []
|
|
248
|
+
for idx in dinArrXPrevCross:
|
|
249
|
+
k = (dinArr[idx + 1] - dinArr[idx])
|
|
250
|
+
k /= timePerSample
|
|
251
|
+
|
|
252
|
+
zeroYatX = (idx * timePerSample) - (dinArr[idx] / k)
|
|
253
|
+
|
|
254
|
+
newX.append(zeroYatX) # round(zeroYatX) )
|
|
255
|
+
|
|
256
|
+
return np.asarray(newX)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def main():
|
|
262
|
+
import sys
|
|
263
|
+
|
|
264
|
+
f = wavio.read(sys.argv[1])
|
|
265
|
+
print("Opened: {} ({},{})".format(sys.argv[1], f.rate, f.sampwidth))
|
|
266
|
+
|
|
267
|
+
o = NIMRUM_MEAS_SPIKES(capRate=f.rate)
|
|
268
|
+
o.add(f.data)
|
|
269
|
+
|
|
270
|
+
o.storeResult()
|
|
271
|
+
plt.show()
|
|
272
|
+
|
|
273
|
+
if __name__ == '__main__':
|
|
274
|
+
main()
|
nimRum/nimRumMeasure.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
|
|
3
|
+
#/******************************************************************************
|
|
4
|
+
# * COPYRIGHT (c) 2021-2025, AbtAudio AB, All rights reserved.
|
|
5
|
+
# *
|
|
6
|
+
# * The copyright to the computer program(s) herein is the property of
|
|
7
|
+
# * AbtAudio AB. The computer programs(s) may implement properties protected by
|
|
8
|
+
# * patent(s) such as arising from SE544478C2, US12034828B2 or WO2021/220149.
|
|
9
|
+
# *
|
|
10
|
+
# * Redistribution in any form, with or without modification, is only permitted
|
|
11
|
+
# * with the written permission from AbtAudio AB.
|
|
12
|
+
# *
|
|
13
|
+
# * The computer program(s) may be used for private purposes and for evaluation
|
|
14
|
+
# * purposes.
|
|
15
|
+
# *
|
|
16
|
+
# * The computer program(s) may NOT be used for any commercial purposes without
|
|
17
|
+
# * the written permission from AbtAudio AB.
|
|
18
|
+
# *
|
|
19
|
+
# * The computer program(s) are provided as is, without any warranties, but
|
|
20
|
+
# * feedback in form of suggestions for improvements is encouraged.
|
|
21
|
+
# ******************************************************************************/
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import signal
|
|
25
|
+
import numpy as np
|
|
26
|
+
|
|
27
|
+
import sounddevice
|
|
28
|
+
import wavio
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
import os
|
|
31
|
+
|
|
32
|
+
from nimRum import nimRumMeasSpikes
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
sudo pip3 install sounddevice
|
|
36
|
+
sudo apt-get install libportaudio2
|
|
37
|
+
sudo apt-get install libopenblas-dev
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
class NIMRUM_MEAS():
|
|
41
|
+
|
|
42
|
+
def __init__(self, cfg, maxTime_sec=5*60):
|
|
43
|
+
self.cfg = cfg
|
|
44
|
+
self.maxTime_sec = float(maxTime_sec)
|
|
45
|
+
self.dataEmpty = True
|
|
46
|
+
|
|
47
|
+
self.dataFileName = os.path.join(self.cfg['resultFolder'], "nimRumMeas.wav")
|
|
48
|
+
|
|
49
|
+
if self.cfg['measMode'] == 'synchErr':
|
|
50
|
+
self.measType = nimRumMeasSpikes.NIMRUM_MEAS_SPIKES(capRate=self.cfg['sampleRate'], maxTime_sec=60*60, maxInt=self.cfg['maxInt'], resultFolder=self.cfg['resultFolder'])
|
|
51
|
+
else:
|
|
52
|
+
print("ERROR: Unknown measure type: {}".format(capRate=self.cfg['measMode']))
|
|
53
|
+
|
|
54
|
+
def _limitdata(self):
|
|
55
|
+
blockSize = int(self.cfg['sampleRate'] * self.cfg['interval'])
|
|
56
|
+
|
|
57
|
+
while (float(self.data.shape[0]) / float(self.cfg['sampleRate'])) > self.maxTime_sec:
|
|
58
|
+
self.data = np.delete(self.data, np.s_[0:blockSize], axis=0)
|
|
59
|
+
|
|
60
|
+
def add(self, indata):
|
|
61
|
+
if self.dataEmpty or not self.cfg['storeData']:
|
|
62
|
+
self.data = indata
|
|
63
|
+
self.dataEmpty = False
|
|
64
|
+
else:
|
|
65
|
+
self.data = np.concatenate((self.data, indata), axis=0)
|
|
66
|
+
self._limitdata()
|
|
67
|
+
|
|
68
|
+
# TODO: This behavior depends on measType...
|
|
69
|
+
self.measType.add(indata)
|
|
70
|
+
|
|
71
|
+
def storeData(self):
|
|
72
|
+
wavio.write(self.dataFileName, self.data, self.cfg['sampleRate'], sampwidth=self.cfg['sampW'])
|
|
73
|
+
print("Stored: {} (Rate:{} SampleSize:{} Shape:{} )".format(self.dataFileName, self.cfg['sampleRate'], self.cfg['sampW'], self.data.shape))
|
|
74
|
+
|
|
75
|
+
def storeResult(self):
|
|
76
|
+
self.measType.storeResult()
|
|
77
|
+
|
|
78
|
+
def getCfg():
|
|
79
|
+
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
|
80
|
+
|
|
81
|
+
parser.add_argument('--listDevices', '-l', action="store_true", help='List audio devices')
|
|
82
|
+
parser.add_argument('--device', '-d', default=0, help='Input device ID')
|
|
83
|
+
parser.add_argument('--interval', '-i', type=float, default=1.0, help='Capture interval: Seconds')
|
|
84
|
+
parser.add_argument('--sampleRate', '-f', type=float, default=48000, help='Sample rate: 0=Use device default')
|
|
85
|
+
parser.add_argument('--sampleSize', '-s', type=str, default='int32', help='Sample size: float32|int32|int16|int8|uint8')
|
|
86
|
+
parser.add_argument('--measMode', '-m', type=str, default='synchErr', help='Meaure mode: synchErr|chirp|...')
|
|
87
|
+
parser.add_argument('--storeData', '-S', action="store_true", help='Store all collected data to file')
|
|
88
|
+
parser.add_argument('--resultFolder', '-R', default='/home/pi/LOGS', help='Folder where measurements are stored')
|
|
89
|
+
|
|
90
|
+
args = parser.parse_args()
|
|
91
|
+
|
|
92
|
+
if args.listDevices:
|
|
93
|
+
print(sounddevice.query_devices())
|
|
94
|
+
quit()
|
|
95
|
+
|
|
96
|
+
if args.sampleRate == 0:
|
|
97
|
+
args.sampleRate = sounddevice.query_devices(args.device, 'input')['default_samplerate']
|
|
98
|
+
|
|
99
|
+
cfg = vars(args) # Convert to dict
|
|
100
|
+
|
|
101
|
+
cfg['sampW'] = 0
|
|
102
|
+
if args.sampleSize == 'int32':
|
|
103
|
+
cfg['sampW'] = 4
|
|
104
|
+
cfg['maxInt'] = 2147483647
|
|
105
|
+
elif args.sampleSize == 'int16':
|
|
106
|
+
cfg['sampW'] = 2
|
|
107
|
+
cfg['maxInt'] = 32767
|
|
108
|
+
else:
|
|
109
|
+
print("ERROR: Only supports int32 or int16, not {}".format(args.sampleSize))
|
|
110
|
+
quit()
|
|
111
|
+
|
|
112
|
+
return cfg
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def signal_handler(sig, frame):
|
|
116
|
+
print("Catched Ctrl-C")
|
|
117
|
+
global storeData
|
|
118
|
+
global measObj
|
|
119
|
+
global run
|
|
120
|
+
global alreadyClosing
|
|
121
|
+
|
|
122
|
+
run = False
|
|
123
|
+
|
|
124
|
+
if not alreadyClosing:
|
|
125
|
+
alreadyClosing = True
|
|
126
|
+
|
|
127
|
+
if storeData:
|
|
128
|
+
measObj.storeData()
|
|
129
|
+
|
|
130
|
+
measObj.storeResult()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def callBack(indata, frames, time, status):
|
|
134
|
+
global measObj
|
|
135
|
+
|
|
136
|
+
if status:
|
|
137
|
+
print("Got callback status:{}".format(status))
|
|
138
|
+
|
|
139
|
+
#print("CAPTURES: ", indata.shape, frames, time)
|
|
140
|
+
measObj.add(indata)
|
|
141
|
+
|
|
142
|
+
def main():
|
|
143
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
144
|
+
|
|
145
|
+
global storeData
|
|
146
|
+
global measObj
|
|
147
|
+
global run
|
|
148
|
+
global alreadyClosing
|
|
149
|
+
|
|
150
|
+
cfg = getCfg()
|
|
151
|
+
storeData = cfg['storeData']
|
|
152
|
+
|
|
153
|
+
measObj = NIMRUM_MEAS(cfg)
|
|
154
|
+
run = True
|
|
155
|
+
alreadyClosing = False
|
|
156
|
+
|
|
157
|
+
blockSize = int(cfg['sampleRate'] * cfg['interval'])
|
|
158
|
+
|
|
159
|
+
with sounddevice.InputStream(device=cfg['device'], channels=2, dtype=cfg['sampleSize'], callback=callBack,
|
|
160
|
+
blocksize=blockSize, samplerate=cfg['sampleRate']):
|
|
161
|
+
while run:
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
if __name__ == '__main__':
|
|
166
|
+
main()
|
nimRum/nimRumPyCommon.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
|
|
3
|
+
#/******************************************************************************
|
|
4
|
+
# * COPYRIGHT (c) 2021-2025, AbtAudio AB, All rights reserved.
|
|
5
|
+
# *
|
|
6
|
+
# * The copyright to the computer program(s) herein is the property of
|
|
7
|
+
# * AbtAudio AB. The computer programs(s) may implement properties protected by
|
|
8
|
+
# * patent(s) such as arising from SE544478C2, US12034828B2 or WO2021/220149.
|
|
9
|
+
# *
|
|
10
|
+
# * Redistribution in any form, with or without modification, is only permitted
|
|
11
|
+
# * with the written permission from AbtAudio AB.
|
|
12
|
+
# *
|
|
13
|
+
# * The computer program(s) may be used for private purposes and for evaluation
|
|
14
|
+
# * purposes.
|
|
15
|
+
# *
|
|
16
|
+
# * The computer program(s) may NOT be used for any commercial purposes without
|
|
17
|
+
# * the written permission from AbtAudio AB.
|
|
18
|
+
# *
|
|
19
|
+
# * The computer program(s) are provided as is, without any warranties, but
|
|
20
|
+
# * feedback in form of suggestions for improvements is encouraged.
|
|
21
|
+
# ******************************************************************************/
|
|
22
|
+
|
|
23
|
+
import sys
|
|
24
|
+
import signal
|
|
25
|
+
import socket
|
|
26
|
+
import re
|
|
27
|
+
import socket
|
|
28
|
+
import os
|
|
29
|
+
from nimRum import nimRumCheckHW
|
|
30
|
+
|
|
31
|
+
import netifaces as ni
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class nimRumPyCommon:
|
|
35
|
+
def __init__(self, meName=os.path.basename(__file__)):
|
|
36
|
+
|
|
37
|
+
self.meStr = "**" + meName + "**: "
|
|
38
|
+
|
|
39
|
+
self.run = 1
|
|
40
|
+
signal.signal(signal.SIGINT, self.signal_handler)
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
self.myName = socket.gethostname()
|
|
44
|
+
except:
|
|
45
|
+
self.myName = "Unknown1"
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
# TODO: This is not failsafe. Expect all devices to be named 'prezoXX'
|
|
49
|
+
self.myId = int(re.findall(r"\d+", self.myName)[0])
|
|
50
|
+
except:
|
|
51
|
+
self.myId = 666
|
|
52
|
+
|
|
53
|
+
t = nimRumCheckHW.nimRumCheckHW()
|
|
54
|
+
|
|
55
|
+
def lclPrint(self, pStr):
|
|
56
|
+
print(self.meStr + pStr)
|
|
57
|
+
sys.stdout.flush()
|
|
58
|
+
|
|
59
|
+
def signal_handler(self, sig, frame):
|
|
60
|
+
self.lclPrint("Catched Ctrl-C, stopping")
|
|
61
|
+
self.run = 0
|
|
62
|
+
|
|
63
|
+
def getMyUniqueName(self):
|
|
64
|
+
return self.myName
|
|
65
|
+
|
|
66
|
+
def getMyUniqueId(self):
|
|
67
|
+
return self.myId
|
|
68
|
+
|
|
69
|
+
def getBCAddr(self, nicName):
|
|
70
|
+
try:
|
|
71
|
+
useLocalClock = 0
|
|
72
|
+
if nicName == "lo":
|
|
73
|
+
useLocalClock = 1
|
|
74
|
+
bcastAddr = "127.0.0.255"
|
|
75
|
+
else:
|
|
76
|
+
bcastAddr = ni.ifaddresses(nicName)[ni.AF_INET][0]["broadcast"]
|
|
77
|
+
except:
|
|
78
|
+
self.lclPrint("'nic' must be one of the following:")
|
|
79
|
+
self.lclPrint(str(ni.interfaces()))
|
|
80
|
+
quit()
|
|
81
|
+
|
|
82
|
+
self.lclPrint("BCAddr:" + str(bcastAddr))
|
|
83
|
+
return bcastAddr, useLocalClock
|
nimRum/nimRumPyLed.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
|
|
3
|
+
#/******************************************************************************
|
|
4
|
+
# * COPYRIGHT (c) 2021-2025, AbtAudio AB, All rights reserved.
|
|
5
|
+
# *
|
|
6
|
+
# * The copyright to the computer program(s) herein is the property of
|
|
7
|
+
# * AbtAudio AB. The computer programs(s) may implement properties protected by
|
|
8
|
+
# * patent(s) such as arising from SE544478C2, US12034828B2 or WO2021/220149.
|
|
9
|
+
# *
|
|
10
|
+
# * Redistribution in any form, with or without modification, is only permitted
|
|
11
|
+
# * with the written permission from AbtAudio AB.
|
|
12
|
+
# *
|
|
13
|
+
# * The computer program(s) may be used for private purposes and for evaluation
|
|
14
|
+
# * purposes.
|
|
15
|
+
# *
|
|
16
|
+
# * The computer program(s) may NOT be used for any commercial purposes without
|
|
17
|
+
# * the written permission from AbtAudio AB.
|
|
18
|
+
# *
|
|
19
|
+
# * The computer program(s) are provided as is, without any warranties, but
|
|
20
|
+
# * feedback in form of suggestions for improvements is encouraged.
|
|
21
|
+
# ******************************************************************************/
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
import platform
|
|
25
|
+
if platform.machine() == 'aarch64':
|
|
26
|
+
import wiringpi
|
|
27
|
+
else:
|
|
28
|
+
from gpiozero import LED
|
|
29
|
+
except:
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
class nimRumPyLed:
|
|
33
|
+
def __init__(self, Rpin=12, Gpin=13, Bpin=26):
|
|
34
|
+
|
|
35
|
+
self.ledAvailable = False
|
|
36
|
+
try:
|
|
37
|
+
self.platform = platform.machine()
|
|
38
|
+
if self.platform == 'aarch64':
|
|
39
|
+
wiringpi.wiringPiSetup()
|
|
40
|
+
self.ledR = 24
|
|
41
|
+
self.ledG = 26
|
|
42
|
+
self.ledB = 25
|
|
43
|
+
else:
|
|
44
|
+
self.ledR = LED(Rpin)
|
|
45
|
+
self.ledG = LED(Gpin)
|
|
46
|
+
self.ledB = LED(Bpin)
|
|
47
|
+
|
|
48
|
+
self.ledAvailable = True
|
|
49
|
+
except:
|
|
50
|
+
print("nimRumPyLed NOT enabled, reading " + __file__ + " migth help")
|
|
51
|
+
|
|
52
|
+
self.off()
|
|
53
|
+
|
|
54
|
+
def off(self):
|
|
55
|
+
if self.ledAvailable:
|
|
56
|
+
if self.platform == 'aarch64':
|
|
57
|
+
wiringpi.pinMode(self.ledR, wiringpi.GPIO.INPUT)
|
|
58
|
+
wiringpi.pinMode(self.ledG, wiringpi.GPIO.INPUT)
|
|
59
|
+
wiringpi.pinMode(self.ledB, wiringpi.GPIO.INPUT)
|
|
60
|
+
else:
|
|
61
|
+
self.ledR.off()
|
|
62
|
+
self.ledG.off()
|
|
63
|
+
self.ledB.off()
|
|
64
|
+
|
|
65
|
+
def red(self):
|
|
66
|
+
if self.ledAvailable:
|
|
67
|
+
if self.platform == 'aarch64':
|
|
68
|
+
wiringpi.pinMode(self.ledR, wiringpi.GPIO.OUTPUT)
|
|
69
|
+
wiringpi.pinMode(self.ledG, wiringpi.GPIO.INPUT)
|
|
70
|
+
wiringpi.pinMode(self.ledB, wiringpi.GPIO.INPUT)
|
|
71
|
+
wiringpi.digitalWrite(self.ledR, wiringpi.GPIO.LOW)
|
|
72
|
+
else:
|
|
73
|
+
self.ledR.on()
|
|
74
|
+
self.ledG.off()
|
|
75
|
+
self.ledB.off()
|
|
76
|
+
|
|
77
|
+
def green(self):
|
|
78
|
+
if self.ledAvailable:
|
|
79
|
+
if self.platform == 'aarch64':
|
|
80
|
+
wiringpi.pinMode(self.ledR, wiringpi.GPIO.INPUT)
|
|
81
|
+
wiringpi.pinMode(self.ledG, wiringpi.GPIO.OUTPUT)
|
|
82
|
+
wiringpi.pinMode(self.ledB, wiringpi.GPIO.INPUT)
|
|
83
|
+
wiringpi.digitalWrite(self.ledG, wiringpi.GPIO.LOW)
|
|
84
|
+
else:
|
|
85
|
+
self.ledR.off()
|
|
86
|
+
self.ledG.on()
|
|
87
|
+
self.ledB.off()
|
|
88
|
+
|
|
89
|
+
def blue(self):
|
|
90
|
+
if self.ledAvailable:
|
|
91
|
+
if self.platform == 'aarch64':
|
|
92
|
+
wiringpi.pinMode(self.ledR, wiringpi.GPIO.INPUT)
|
|
93
|
+
wiringpi.pinMode(self.ledG, wiringpi.GPIO.INPUT)
|
|
94
|
+
wiringpi.pinMode(self.ledB, wiringpi.GPIO.OUTPUT)
|
|
95
|
+
wiringpi.digitalWrite(self.ledB, wiringpi.GPIO.LOW)
|
|
96
|
+
else:
|
|
97
|
+
self.ledR.off()
|
|
98
|
+
self.ledG.off()
|
|
99
|
+
self.ledB.on()
|
|
100
|
+
|
|
101
|
+
def white(self):
|
|
102
|
+
if self.ledAvailable:
|
|
103
|
+
if self.platform == 'aarch64':
|
|
104
|
+
wiringpi.pinMode(self.ledR, wiringpi.GPIO.OUTPUT)
|
|
105
|
+
wiringpi.digitalWrite(self.ledR, wiringpi.GPIO.LOW)
|
|
106
|
+
wiringpi.pinMode(self.ledG, wiringpi.GPIO.OUTPUT)
|
|
107
|
+
wiringpi.digitalWrite(self.ledG, wiringpi.GPIO.LOW)
|
|
108
|
+
wiringpi.pinMode(self.ledB, wiringpi.GPIO.OUTPUT)
|
|
109
|
+
wiringpi.digitalWrite(self.ledB, wiringpi.GPIO.LOW)
|
|
110
|
+
else:
|
|
111
|
+
self.ledR.on()
|
|
112
|
+
self.ledG.on()
|
|
113
|
+
self.ledB.on()
|