add sound option in results for Q

test_sound
Theophile Terraz 2025-11-06 16:28:59 +01:00
parent 02db91f3d7
commit 6d60a2017c
5 changed files with 514 additions and 203 deletions

View File

@ -31,14 +31,23 @@ except Exception as e:
print(f"Module 'rasterio' is not available: {e}") print(f"Module 'rasterio' is not available: {e}")
_rasterio_loaded = False _rasterio_loaded = False
try:
import pyaudio
import wave
_audio_loaded = True
except Exception as e:
print(f"Either 'pyaudio' or 'wave' is not available: {e}")
_audio_loaded = False
from functools import reduce from functools import reduce
from numpy import sqrt import numpy as np
from datetime import datetime from datetime import datetime
from tools import trace, timer, logger_exception from tools import trace, timer, logger_exception
from View.Tools.PamhyrWindow import PamhyrWindow from View.Tools.PamhyrWindow import PamhyrWindow
from View.WaitingDialog import WaitingDialog
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QKeySequence, QIcon, QPixmap, QKeySequence, QIcon, QPixmap,
@ -349,7 +358,8 @@ class ResultsWindow(PamhyrWindow):
"action_add": self._add_custom_plot, "action_add": self._add_custom_plot,
"action_export": self._export, "action_export": self._export,
# "action_export": self.export_current, # "action_export": self.export_current,
"action_import_data": self.import_data "action_import_data": self.import_data,
"action_play_sound": self.play_sound
} }
if _rasterio_loaded: if _rasterio_loaded:
actions["action_Geo_tiff"] = self.import_geotiff actions["action_Geo_tiff"] = self.import_geotiff
@ -357,6 +367,12 @@ class ResultsWindow(PamhyrWindow):
self.find(QAction, "action_Geo_tiff")\ self.find(QAction, "action_Geo_tiff")\
.setEnabled(False) .setEnabled(False)
if _audio_loaded:
actions["action_play_sound"] = self.play_sound
else:
self.find(QAction, "action_play_sound")\
.setVisible(False)
if len(self._results) > 1: if len(self._results) > 1:
self.find(QAction, "action_reload").setEnabled(False) self.find(QAction, "action_reload").setEnabled(False)
@ -959,7 +975,7 @@ class ResultsWindow(PamhyrWindow):
map( map(
lambda p: lambda p:
p.get_ts_key(timestamp, "V") / p.get_ts_key(timestamp, "V") /
sqrt(9.81 * ( np.sqrt(9.81 * (
p.geometry.wet_area( p.geometry.wet_area(
p.get_ts_key(timestamp, "Z")) / p.get_ts_key(timestamp, "Z")) /
p.geometry.wet_width( p.geometry.wet_width(
@ -973,7 +989,7 @@ class ResultsWindow(PamhyrWindow):
map( map(
lambda p: lambda p:
p.get_ts_key(timestamp, "V") / p.get_ts_key(timestamp, "V") /
sqrt(9.81 * ( np.sqrt(9.81 * (
p.geometry.wet_area( p.geometry.wet_area(
p.get_ts_key(timestamp, "Z")) / p.get_ts_key(timestamp, "Z")) /
p.geometry.wet_width( p.geometry.wet_width(
@ -986,7 +1002,7 @@ class ResultsWindow(PamhyrWindow):
map( map(
lambda p: lambda p:
p.get_ts_key(timestamp, "V") / p.get_ts_key(timestamp, "V") /
sqrt(9.81 * ( np.sqrt(9.81 * (
p.geometry.wet_area( p.geometry.wet_area(
p.get_ts_key(timestamp, "Z")) / p.get_ts_key(timestamp, "Z")) /
p.geometry.wet_width( p.geometry.wet_width(
@ -1122,7 +1138,7 @@ class ResultsWindow(PamhyrWindow):
if self._current_results != 2: if self._current_results != 2:
fr = my_dict[dict_y["froude"]] = list( fr = my_dict[dict_y["froude"]] = list(
map(lambda z, v: map(lambda z, v:
v / sqrt(9.81 * ( v / np.sqrt(9.81 * (
profile.geometry.wet_area(z) / profile.geometry.wet_area(z) /
profile.geometry.wet_width(z)) profile.geometry.wet_width(z))
), z, v) ), z, v)
@ -1131,7 +1147,7 @@ class ResultsWindow(PamhyrWindow):
fr1 = list( fr1 = list(
map(lambda z, v: map(lambda z, v:
v / v /
sqrt(9.81 * ( np.sqrt(9.81 * (
profile1.geometry.wet_area(z) / profile1.geometry.wet_area(z) /
profile1.geometry.wet_width(z)) profile1.geometry.wet_width(z))
), z1, v1) ), z1, v1)
@ -1139,7 +1155,7 @@ class ResultsWindow(PamhyrWindow):
fr2 = list( fr2 = list(
map(lambda z, v: map(lambda z, v:
v / v /
sqrt(9.81 * ( np.sqrt(9.81 * (
profile2.geometry.wet_area(z) / profile2.geometry.wet_area(z) /
profile2.geometry.wet_width(z)) profile2.geometry.wet_width(z))
), z2, v2) ), z2, v2)
@ -1313,3 +1329,84 @@ class ResultsWindow(PamhyrWindow):
for p in self._additional_plot: for p in self._additional_plot:
self._additional_plot[p].add_imported_plot(data) self._additional_plot[p].add_imported_plot(data)
def play_sound(self):
def get_sine_wave(frequency, duration,
sample_rate=44100, amplitude=4096):
t = np.linspace(0, duration, int(sample_rate*duration))
return amplitude*np.sin(2*np.pi*frequency*t)
if not _audio_loaded:
return
# results
results=self._results[self._current_results[0]]
if results is None:
return
# reach
reach = results.river.reachs[self._get_current_reach()]
profile_id = self._get_current_profile()
profile = reach.profile(profile_id)
t = self._timestamps
q = profile.get_key("Q")
scale=[]
low = 30; high = 84
for k in range(low, high):
note=440*2**((k-49)/12)
scale.append(note) # add musical note
n_notes = len(scale) # number of musical notes
# rescale
# we start at low to high
tmax = max(t); tmin = min(t);
qmax = max(q); qmin = min(q); qfact = (n_notes) / (qmax - qmin + 1)
for i in range(len(q)):
q[i] = int((q[i] - qmin) * qfact)
nq = [q[0]] # (notes)
dq = [0.1] # durée
for i in range(len(t)-1):
if q[i] == q[i+1]:
dq[-1] += 0.1
else:
nq.append(q[i+1])
dq.append(0.1)
wq=[]
for i in range(len(dq)): # loop over dataset observations, create one note per observation
volume = 2048
new_w = get_sine_wave(frequency = scale[nq[i]], duration = dq[i], amplitude = volume)
wq = np.concatenate((wq,new_w))
p = pyaudio.PyAudio()
chunk = 1048
stream = p.open(format =
p.get_format_from_width(2),
channels = 1,
rate = 44100,
output = True)
def fn():
i = 0
while i < len(wq):
stream.write(
wq[i:min(i+chunk,len(wq))].astype(np.int16).tobytes()
)
i += chunk
title = self._trad["playing_sound"]
dlg = WaitingDialog(
payload_fn=fn,
title=title,
parent=self
)
dlg.exec_()
#stop stream
stream.stop_stream()
stream.close()
#close PyAudio
p.terminate()

View File

@ -61,6 +61,9 @@ class ResultsTranslate(MainTranslate):
self._dict["ImageCoordinates"] = _translate( self._dict["ImageCoordinates"] = _translate(
"Results", "Image coordinates" "Results", "Image coordinates"
) )
self._dict["playing_sound"] = _translate(
"Results", "Playing sound..."
)
self._sub_dict["table_headers_reach"] = { self._sub_dict["table_headers_reach"] = {
"name": _translate("Results", "Reach name"), "name": _translate("Results", "Reach name"),

View File

@ -240,6 +240,7 @@
<addaction name="action_reload"/> <addaction name="action_reload"/>
<addaction name="action_Geo_tiff"/> <addaction name="action_Geo_tiff"/>
<addaction name="action_import_data"/> <addaction name="action_import_data"/>
<addaction name="action_play_sound"/>
</widget> </widget>
<action name="action_add"> <action name="action_add">
<property name="icon"> <property name="icon">
@ -301,6 +302,18 @@
<string>Import data from SCV file</string> <string>Import data from SCV file</string>
</property> </property>
</action> </action>
<action name="action_play_sound">
<property name="icon">
<iconset>
<normaloff>ressources/multimedia-volume-control-symbolic.symbolic.png</normaloff>ressources/multimedia-volume-control-symbolic.symbolic.png</iconset>
</property>
<property name="text">
<string>play_sound</string>
</property>
<property name="toolTip">
<string>Play discharge as sound</string>
</property>
</action>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

File diff suppressed because it is too large Load Diff