├── docs ├── rt60_1k.png ├── ir_delta.png ├── ir_power.png ├── rt60_raw.png ├── ir_delta_pwr.png ├── rt60_drawn.png ├── HawleyBathroom.m4a ├── ir_spectrogram.png ├── rt60_hawleyb_125.png ├── rt60_hawleyb_4k.png ├── rt60_hawleyb_oops.png ├── rt60_octaveselect.png ├── rt60_hawleyb_4k_box.png ├── rt60_hawleyb_4k_zoom.png ├── ir_sweep_and_inv_waveform.png ├── rt60_hawleyb_4k_zoom_line.png ├── ir.md └── rt60.md ├── images ├── modes.png ├── power.png ├── rt60.png ├── sabine.png ├── spectro.png ├── waterfall.png ├── shaart_logo.jpg ├── mandrill_echo.png ├── mandrill_reverb.png ├── mandrill_wahwah.png ├── wavelet_vs_fft.png ├── mandrill_leveler.png ├── mandrill_spectro.png ├── mandrill_mp3_to_wav.png └── ir_sweep_and_inv_waveform.png ├── audio ├── mandrill.wav ├── sample_data.wav ├── HawleyBathroom.m4a └── HawleyBathroom.mp3 ├── source ├── SHAART.icns ├── shaart_logo.png ├── shaart_logo_icon.ico ├── extra-hooks │ └── hook-librosa.py ├── setup.py ├── SHAART.spec ├── spectrowidget.py ├── waterwidget.py ├── waveformwidget.py ├── pwrspecwidget.py ├── modegraphwidget.py ├── rcgraphwidget.py ├── rt60widget.py ├── SHAART.py └── ui_shaart.py ├── README.md └── LICENSE.md /docs/rt60_1k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/docs/rt60_1k.png -------------------------------------------------------------------------------- /images/modes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/images/modes.png -------------------------------------------------------------------------------- /images/power.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/images/power.png -------------------------------------------------------------------------------- /images/rt60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/images/rt60.png -------------------------------------------------------------------------------- /audio/mandrill.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/audio/mandrill.wav -------------------------------------------------------------------------------- /docs/ir_delta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/docs/ir_delta.png -------------------------------------------------------------------------------- /docs/ir_power.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/docs/ir_power.png -------------------------------------------------------------------------------- /docs/rt60_raw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/docs/rt60_raw.png -------------------------------------------------------------------------------- /images/sabine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/images/sabine.png -------------------------------------------------------------------------------- /images/spectro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/images/spectro.png -------------------------------------------------------------------------------- /source/SHAART.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/source/SHAART.icns -------------------------------------------------------------------------------- /audio/sample_data.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/audio/sample_data.wav -------------------------------------------------------------------------------- /docs/ir_delta_pwr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/docs/ir_delta_pwr.png -------------------------------------------------------------------------------- /docs/rt60_drawn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/docs/rt60_drawn.png -------------------------------------------------------------------------------- /images/waterfall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/images/waterfall.png -------------------------------------------------------------------------------- /docs/HawleyBathroom.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/docs/HawleyBathroom.m4a -------------------------------------------------------------------------------- /docs/ir_spectrogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/docs/ir_spectrogram.png -------------------------------------------------------------------------------- /images/shaart_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/images/shaart_logo.jpg -------------------------------------------------------------------------------- /source/shaart_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/source/shaart_logo.png -------------------------------------------------------------------------------- /audio/HawleyBathroom.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/audio/HawleyBathroom.m4a -------------------------------------------------------------------------------- /audio/HawleyBathroom.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/audio/HawleyBathroom.mp3 -------------------------------------------------------------------------------- /docs/rt60_hawleyb_125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/docs/rt60_hawleyb_125.png -------------------------------------------------------------------------------- /docs/rt60_hawleyb_4k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/docs/rt60_hawleyb_4k.png -------------------------------------------------------------------------------- /docs/rt60_hawleyb_oops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/docs/rt60_hawleyb_oops.png -------------------------------------------------------------------------------- /docs/rt60_octaveselect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/docs/rt60_octaveselect.png -------------------------------------------------------------------------------- /images/mandrill_echo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/images/mandrill_echo.png -------------------------------------------------------------------------------- /images/mandrill_reverb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/images/mandrill_reverb.png -------------------------------------------------------------------------------- /images/mandrill_wahwah.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/images/mandrill_wahwah.png -------------------------------------------------------------------------------- /images/wavelet_vs_fft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/images/wavelet_vs_fft.png -------------------------------------------------------------------------------- /docs/rt60_hawleyb_4k_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/docs/rt60_hawleyb_4k_box.png -------------------------------------------------------------------------------- /images/mandrill_leveler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/images/mandrill_leveler.png -------------------------------------------------------------------------------- /images/mandrill_spectro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/images/mandrill_spectro.png -------------------------------------------------------------------------------- /source/shaart_logo_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/source/shaart_logo_icon.ico -------------------------------------------------------------------------------- /docs/rt60_hawleyb_4k_zoom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/docs/rt60_hawleyb_4k_zoom.png -------------------------------------------------------------------------------- /images/mandrill_mp3_to_wav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/images/mandrill_mp3_to_wav.png -------------------------------------------------------------------------------- /docs/ir_sweep_and_inv_waveform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/docs/ir_sweep_and_inv_waveform.png -------------------------------------------------------------------------------- /docs/rt60_hawleyb_4k_zoom_line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/docs/rt60_hawleyb_4k_zoom_line.png -------------------------------------------------------------------------------- /images/ir_sweep_and_inv_waveform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drscotthawley/SHAART/HEAD/images/ir_sweep_and_inv_waveform.png -------------------------------------------------------------------------------- /source/extra-hooks/hook-librosa.py: -------------------------------------------------------------------------------- 1 | from PyInstaller.utils.hooks import collect_data_files 2 | datas = collect_data_files('librosa') 3 | 4 | -------------------------------------------------------------------------------- /source/setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is used in creating a standalone application via py2app 3 | 4 | To use this, run: 5 | python setup.py py2app 6 | """ 7 | 8 | from setuptools import setup 9 | 10 | APP = ['SHAART.py'] 11 | DATA_FILES = [] 12 | PKGS = ['scikits.audiolab'] 13 | OPTIONS = { 14 | 'argv_emulation': True, 15 | 'optimize': True, 16 | 'packages' : PKGS, 17 | 'iconfile':'SHAART.icns' 18 | } 19 | 20 | setup( 21 | app=APP, 22 | data_files=DATA_FILES, 23 | options={'py2app': OPTIONS}, 24 | setup_requires=['py2app'], 25 | ) 26 | -------------------------------------------------------------------------------- /source/SHAART.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | 3 | 4 | a = Analysis( 5 | ['SHAART.py'], 6 | pathex=[], 7 | binaries=[], 8 | datas=[], 9 | hiddenimports=['PyQt6', 'librosa'], 10 | hookspath=[], 11 | hooksconfig={}, 12 | runtime_hooks=[], 13 | excludes=[], 14 | noarchive=False, 15 | optimize=0, 16 | ) 17 | pyz = PYZ(a.pure) 18 | 19 | exe = EXE( 20 | pyz, 21 | a.scripts, 22 | a.binaries, 23 | a.datas, 24 | [], 25 | name='SHAART', 26 | debug=False, 27 | bootloader_ignore_signals=False, 28 | strip=False, 29 | upx=True, 30 | upx_exclude=[], 31 | runtime_tmpdir=None, 32 | console=False, 33 | disable_windowed_traceback=False, 34 | argv_emulation=True, 35 | target_arch=None, 36 | codesign_identity=None, 37 | entitlements_file=None, 38 | icon=['SHAART.icns'], 39 | ) 40 | app = BUNDLE( 41 | exe, 42 | name='SHAART.app', 43 | icon='SHAART.icns', 44 | bundle_identifier="edu.belmont.SHAART", 45 | ) 46 | -------------------------------------------------------------------------------- /source/spectrowidget.py: -------------------------------------------------------------------------------- 1 | # Python Qt5 bindings for GUI objects 2 | from PyQt6 import QtGui, QtWidgets 3 | 4 | # import the Qt5Agg FigureCanvas object, that binds Figure to 5 | # Qt5Agg backend. It also inherits from QWidget 6 | from matplotlib.backends.backend_qt5agg \ 7 | import FigureCanvasQTAgg as FigureCanvas 8 | 9 | # Matplotlib Figure object 10 | from matplotlib.figure import Figure 11 | 12 | from matplotlib.backends.backend_qt5 import NavigationToolbar2QT as NavigationToolbar 13 | from matplotlib import cm 14 | 15 | 16 | import numpy as np 17 | 18 | class SpectroCanvas(FigureCanvas): 19 | """Class to represent the FigureCanvas widget""" 20 | def __init__(self): 21 | # setup Matplotlib Figure and Axis 22 | self.fig = Figure() 23 | 24 | # initialization of the canvas 25 | FigureCanvas.__init__(self, self.fig) 26 | 27 | self.ax = self.fig.clear() 28 | self.ax = self.fig.add_subplot(111) 29 | self.fig.subplots_adjust(left=0.12,right=0.98,bottom=0.1, top=.97) 30 | 31 | # we define the widget as expandable 32 | FigureCanvas.setSizePolicy(self, 33 | QtWidgets.QSizePolicy.Policy.Expanding, 34 | QtWidgets.QSizePolicy.Policy.Expanding) 35 | # notify the system of updated policy 36 | FigureCanvas.updateGeometry(self) 37 | 38 | class SpectroWidget(QtWidgets.QWidget): 39 | """Widget defined in Qt Designer""" 40 | def __init__(self, parent = None): 41 | # initialization of Qt MainWindow widget 42 | QtWidgets.QWidget.__init__(self, parent) 43 | 44 | # set the canvas to the Matplotlib widget 45 | self.canvas = SpectroCanvas() 46 | 47 | # create a vertical box layout 48 | self.vbl = QtWidgets.QVBoxLayout() 49 | 50 | # add spectro widget to vertical box 51 | self.vbl.addWidget(self.canvas) 52 | 53 | # add interactive navigation 54 | self.navi_toolbar = NavigationToolbar(self.canvas, self) 55 | self.vbl.addWidget(self.navi_toolbar) 56 | 57 | # set the layout to th vertical box 58 | self.setLayout(self.vbl) 59 | 60 | def update_graph(self,amp,sample_rate,colormap_choice=0): 61 | """Updates the graph with new data/annotations""" 62 | 63 | self.canvas.ax.clear() 64 | 65 | nsamples = len(amp) 66 | 67 | NFFT = 1024 # the length of the windowing segments 68 | 69 | if (0 == colormap_choice): 70 | colormap = cm.gist_heat 71 | elif (1 == colormap_choice): 72 | colormap = cm.gist_rainbow 73 | elif (2 == colormap_choice): 74 | colormap = cm.rainbow 75 | elif (3 == colormap_choice): 76 | colormap = cm.gray 77 | elif (4 == colormap_choice): 78 | colormap = cm.Blues 79 | elif (5 == colormap_choice): 80 | colormap = cm.gist_ncar 81 | 82 | 83 | Pxx, freqs, bins, im = self.canvas.ax.specgram(amp, NFFT=NFFT, Fs=1.0*sample_rate, noverlap=900 84 | #)# ,cmap=cm.gray) 85 | # ,cmap=cm.Blues) # color map 86 | ,cmap=colormap) # color map 87 | 88 | # Annotation 89 | self.canvas.ax.set_xlabel('Time (s)') 90 | self.canvas.ax.set_ylabel('Frequency (Hz) ') 91 | self.canvas.ax.axis([0,bins[-1],0,freqs[-1]]) 92 | 93 | # Actually draw everything 94 | self.canvas.draw() 95 | -------------------------------------------------------------------------------- /docs/ir.md: -------------------------------------------------------------------------------- 1 | # Creating Impulse Responses with SHAART 2 | 3 | Download SHAART. 4 | 5 | Sure, you could press a button and have Room Eq Wizard or FuzzMeasure or Logic do it for you, but what if you want to "see under the hood" a bit, to learn what's really going on? Here we follow the now-ubiquitous method (and ISO standard) of Farina [1]. 6 | 7 | 1. Create an exponential sine sweep of uniform amplitude. 8 | 9 | - You may create the sweep in SHAART by clicking on the "Equation" tab and leaving the default equation text in place and selecting "Go". (The default is for a 10 second sweep from 20 Hz to 20000 Hz.) 10 | 11 | ```blah 12 | 0.8 * sin( 20 *2*PI*TMAX/ln(20000.0/20) * (exp(t/TMAX*ln(20000.0/20))-1) ) 13 | ``` 14 | 15 | - Alternatively you may generate a sweep in Audacity by selecting Generate > Chirp and then "Logarithmic". Be sure to set the starting and ending amplitudes to be the same. 16 | 17 | 2. Play the sweep from your speaker while recording the response. (SHAART does not currently have recording functions). Save the response to a WAV file. 18 | 19 | 3. In SHAART, choose "Equation" and this time copy & paste in the "Inverse Exponential Sine Sweep" [2] equation text... 20 | 21 | ``` 22 | exp(ln(20000.0/20)*(-t)/TMAX) * sin( 20 *2*PI*TMAX/ln(20000.0/20) * (exp((TMAX-t)/TMAX*ln(20000.0/20))-1) ) 23 | ``` 24 | 25 | ...and press Go. This will go into "File A" in SHAART. 26 | 27 | 4. Load the response WAV file into "File B" in SHAART. A comparison of the two waveforms now in memory will look like this: 28 |  29 | 30 | 5. Go to the "Convolve" tab and simply press "Go". (No other instructions or actions are necessary. Do not time-reverse file A). 31 | 32 | 6. File A now contains your Impulse Response! 33 | **TODO:** show screenshot(s) of constructed IR. 34 | 35 | 36 | 37 | ## Check: IR of a Dry Signal 38 | 39 | If we use the "original" (constant-amplitude, forward) sine sweep and convolve it with its "inverse" (exponential-amplitude, backward) sweep, in theory we should get a Dirac delta function (i.e. a "spike") in time and a flat power spectrum. Let's check: 40 | 41 | 1. As in Step 1 above, use the Equation feature to generate the forward sweep as File A and save it to a file: `sweep.wav`. 42 | 43 | 2. As in Step 3 above, use the Equation feature to generate the 'Inverse filter' as File A (i.e. overwrite what's there), and keep it there. 44 | 45 | 3. Load back the original sweep file as File B. (Take a look in the Waveform display. You should see something similar to the screenshot shown in Step 4 above.) 46 | 47 | 4. Go the Convolve tab and press the big "GO!" button. 48 | 49 | 5. Go back to the waveform display to see this 'spike': ...which is not quite perfect but pretty 'impulsive'! If we look at it on a dB scale (go to the RT60 tab), we see... 50 | 51 |  52 | 53 | ...Defects include the "blip" on the left, and an asymmetry that shows up about 50dB lower than the maximum. I'll look into those. The spectrogram looks like this: 54 | 55 | **TODO:** Feature Request: add a color bar to the side of the plot so we know the scale of the colors. 56 | 57 | 6. Check the power spectrum: Is it flat? Press the Power tab to see this:  ...pretty flat, eh? ;-) *(And if you change the equations to run from 10 Hz to 22 kHz instead of 20 to 20k it'll look even flatter, but there's not really a point to that because you won't be measuring RT60 in those extra frequency ranges.)* 58 | 59 | ## References: 60 | 61 | [1] Farina's Method: http://aurora-plugins.forumfree.it/?t=53443032 62 | 63 | [2] Inverse Exponential Sweep, see Peter Pabon: http://kc.koncon.nl/staff/pabon/IRM/IRMeasurementInstruction/assignment_IR_ExpSweepTheory.htm 64 | 65 |