├── README.md ├── contrib └── small-rooms-classifier │ ├── README.md │ └── labels.json ├── realrirs ├── __init__.py ├── base.py └── datasets.py ├── setup.py └── tools └── gentable.py /README.md: -------------------------------------------------------------------------------- 1 | # RealRIRs 2 | 3 | A collection of loaders for real (recorded) impulse response databases, because apparently people cannot to stick to a single way of storing audio data. 4 | 5 | ## Installation 6 | 7 | Use ``pip install realrirs``. Depending on what databases you want to use, you will have to install additional dependencies. To install all dependencies, use ``pip install realrirs[full]``. 8 | 9 | ## Usage 10 | 11 | ```python 12 | import pathlib 13 | import realrirs.datasets 14 | 15 | aachen_impulse_response_database = realrirs.datasets.AIRDataset( 16 | pathlib.Path("/path/to/AIR_1_4") # Can also pass simple str 17 | ) 18 | 19 | # List all IRs in database, tuples of (name, n_channels, n_samples, sample_rate) 20 | aachen_impulse_response_database.list_irs() 21 | # => [(PosixPath('/path/to/AIR_1_4/air_phone_stairway_hfrp_1.mat'), 1, 144000, 48000), 22 | # (PosixPath('/path/to/AIR_1_4/air_binaural_booth_1_1_1.mat'), 1, 32767, 48000)], 23 | # ...] 24 | 25 | # Get single IR, ndarray of shape (n_channels, n_samples) 26 | aachen_impulse_response_database[aachen_impulse_response_database.list_irs()[0][0]] 27 | # => array([[ 1.32764243e-07, -2.18957279e-08, 1.28081465e-07, ..., 28 | # 0.00000000e+00, 0.00000000e+00, 0.00000000e+00]]) 29 | 30 | # Get all IRs, much faster than using [] (__getitem__) multiple times 31 | aachen_impulse_response_database.getall() 32 | # => 33 | 34 | # Generator contains (name, sample_rate, ir) tuples. 35 | next(aachen_impulse_response_database.getall()) 36 | # => (PosixPath('/path/to/AIR_1_4/air_binaural_aula_carolina_0_1_1_90_3.mat'), 48000, 37 | # array([[-2.73920884e-06, -3.49019781e-06, -1.70998298e-06, ..., -7.13979890e-11]])) 38 | ``` 39 | 40 | ## Supported datasets 41 | 42 | | Dataset | License | Number of IRs | Total IR duration | Total IR duration (all channels) | 43 | |-|-|-|-|-| 44 | | [360° Binaural Room Impulse Response (BRIR) Database for 6DOF spatial perception research](https://zenodo.org/record/2641166) | CC BY 4.0 | 1726 | 143.8 s | 292.0 s | 45 | | [Aachen Impulse Response Database](https://www.iks.rwth-aachen.de/forschung/tools-downloads/databases/aachen-impulse-response-database/) | ? | 214 | 8.0 s | 8.0 s | 46 | | [Audio Spatialisation for Headphones (ASH) Impulse Response Dataset](https://github.com/ShanonPearce/ASH-IR-Dataset) | CC BY-CC-SA 4.0 | 787 | 11.6 s | 23.1 s | 47 | | [BUT Speech@FIT Reverb Database](https://speech.fit.vutbr.cz/software/but-speech-fit-reverb-database) | CC BY 4.0 | 2325 | 38.7 s | 38.7 s | 48 | | [Concert Hall Impulse Responses – Pori, Finland](http://legacy.spa.aalto.fi/projects/poririrs/) | Custom, similar to CC BY-NC-SA | 90 | 5.7 s | 16.6 s | 49 | | [DRR-scaled Individual Binaural Room Impulse Responses](https://zenodo.org/record/61072) | CC BY-NC-SA 4.0 | 4936 | 125.1 s | 250.3 s | 50 | | [Database of Omnidirectional and B-Format Impulse Responses](http://isophonics.net/content/room-impulse-response-data-set) | ? | 2041 | 68.0 s | 68.0 s | 51 | | [Dataset of In-The-Ear and Behind-The-Ear Binaural Room Impulse Responses](https://github.com/pyBinSim/HeadRelatedDatabase) | CC BY-NC 4.0 | 192 | 2.1 s | 4.3 s | 52 | | [Dataset of measured binaural room impulse responses for use in an position-dynamic auditory augmented reality application](https://zenodo.org/record/1321996) | CC BY-NC 4.0 | 3888 | 129.6 s | 259.2 s | 53 | | [EchoThief Impulse Response Library](http://www.echothief.com/downloads/) | ? | 115 | 2.5 s | 4.9 s | 54 | | [Greg Hopkins IR 1 – Digital, Analog, Real Spaces](https://www.dropbox.com/sh/vjf5bsi28hcrkli/AAAmln01N4awOuclCi5q0DOia/Greg%20Hopkins%20IR%201%20-%20Digital%2C%20Analog%2C%20Real%20Spaces) | ? | 22 | 1.1 s | 2.2 s | 55 | | [GTU-RIR Gebze Technical University Room Impulse Reponse Dataset](https://github.com/mehmetpekmezci/gtu-rir) | GPL | 15000 | 500 s | 500 s | 56 | | [Impulse Response Database for HybridReverb2](https://github.com/jpcima/HybridReverb2-impulse-response-database) | CC BY-SA 4.0 | 472 | 16.5 s | 16.5 s | 57 | | [Impulse Responses from the Bell Labs Varechoic Chamber](?) | ? | 12 | 0.2 s | 0.2 s | 58 | | [METU SPARG Eigenmike em32 Acoustic Impulse Response Dataset v0.1.0](https://zenodo.org/record/2635758) | CC BY 4.0 | 8052 | 268.4 s | 268.4 s | 59 | | [Multi-Channel Impulse Response Database](https://www.iks.rwth-aachen.de/forschung/tools-downloads/databases/multi-channel-impulse-response-database/) | ? | 1872 | 312.0 s | 312.0 s | 60 | | [Multichannel Acoustic Reverberation Database at York](https://www.commsp.ee.ic.ac.uk/~sap/resources/mardy-multichannel-acoustic-reverberation-database-at-york-database/) | ? | 72 | 1.6 s | 1.6 s | 61 | | [Open Acoustic Impulse Response (Open AIR) Library](https://openairlib.net/) | ? | 504 | 55.6 s | 181.9 s | 62 | | [R-Prox RIR samples Darmstadt June 2017](https://zenodo.org/record/1209820) | CC BY 4.0 | 2313 | 84.7 s | 84.7 s | 63 | | [REVERB challenge RealData](http://reverb2014.dereverberation.com/) | ? | 36 | 0.6 s | 4.8 s | 64 | | [RIR samples Darmstadt and Helsinki, Summer-Autumn 2018](https://zenodo.org/record/1434786) | CC BY 4.0 | 1788 | 25.3 s | 25.3 s | 65 | | [RWCP Sound Scene Database in Real Acoustical Environments](https://www.openslr.org/13/) | ? | 6758 | 64.6 s | 64.6 s | 66 | | [Single- and Multichannel Audio Recordings Database (SMARD)](https://www.smard.es.aau.dk/) | ? | 1008 | 198.3 s | 198.3 s | 67 | | [Statistics of natural reverberation enable perceptual separation of sound and space](https://mcdermottlab.mit.edu/Reverb/IR_Survey.html) | ? | 270 | 2.9 s | 2.9 s | 68 | | [Surrey Binaural Room Impulse Response Measurements](https://github.com/IoSR-Surrey/RealRoomBRIRs) | MIT | 370 | 4.2 s | 4.2 s | 69 | | [The IoSR listening room multichannel BRIR dataset](https://github.com/IoSR-Surrey/IoSR_ListeningRoom_BRIRs) | CC BY 4.0 | 3456 | 78.6 s | 157.3 s | 70 | | [Voxengo Free Reverb Impulse Responses](https://www.voxengo.com/impulses/) | Custom, similar to CC BY-SA | 38 | 1.4 s | 2.7 s | 71 | -------------------------------------------------------------------------------- /contrib/small-rooms-classifier/README.md: -------------------------------------------------------------------------------- 1 | # Small rooms classifier data 2 | 3 | ~1.5k manually labeled room impulse responses from the RealRIRs datasets. Can be used for training a "small rooms" classifier. Format is as follows: 4 | 5 | ``` 6 | [ 7 | [ 8 | dataset: Name of dataset that contains the IR, 9 | ir_name: Name of the IR that has been labeled (as used with __getitem__), 10 | channel: Channel of the IR that has been labeled, 11 | is_small_room: Does the IR sound like a "small room"? (Not like a church, etc.), 12 | ], 13 | ... 14 | ] 15 | ``` 16 | 17 | Note that all of the labeling was done by me, so will be highly subjective. -------------------------------------------------------------------------------- /realrirs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonashaag/RealRIRs/36fd139bb735e99fed062b5dbde0e9672b5ce015/realrirs/__init__.py -------------------------------------------------------------------------------- /realrirs/base.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import os 3 | import pathlib 4 | from typing import Generic, Iterable, List, NewType, Optional, Sequence, Tuple, TypeVar 5 | import numpy as np 6 | 7 | #: IR = Type of IR; numpy array of shape (chans, samples). Note that both `chans` and `samples` may be different for each IR of a dataset. 8 | IR = NewType("IR", np.ndarray) 9 | #: NAME_T = Type of IR name; eg., str or Tuple[str, int] 10 | NAME_T = TypeVar("NAME_T") 11 | 12 | 13 | def shape(things): 14 | """Get shape in a way that also understands Python lists.""" 15 | try: 16 | return things.shape 17 | except AttributeError: 18 | shapes = list(map(shape, things)) 19 | assert len(set(shapes)) == 1 20 | return (len(shapes), *shapes[0]) 21 | 22 | 23 | def check_nonmono(x): 24 | """Check that `x` is of shape `(chans, samples)`""" 25 | assert ( 26 | len(shape(x)) == 2 and shape(x)[0] < 10 27 | ), f"Shape should be (channels, samples) but is {shape(x)}" 28 | 29 | 30 | class IRDataset(Generic[NAME_T]): 31 | """Base class for all IR datasets. 32 | 33 | Each dataset contains many IRs. An IR is a audio array of shape `(chans, samples)`, 34 | i.e. always a *non-mono* audio array. Each IR has a "name" (of type str or any 35 | other type compatible with `__getitem__`). IRs may be loaded from a dataset using 36 | `__getitem__`: ``ds[ir_name]``. 37 | 38 | Most datasets will be file-based, but that is not a requirement. 39 | """ 40 | 41 | #: (required) Unique name for this dataset. 42 | name: str 43 | #: (optional) Where to find about more about this dataset. 44 | url: Optional[str] = None 45 | #: (optional) Direct dataset download URLs. 46 | download_urls: Sequence[str] = () 47 | #: (optional) Copyright notice, list of authors, license name/URL, etc. 48 | license: Optional[str] = None 49 | 50 | @abc.abstractmethod 51 | def getall(self) -> Iterable[Tuple[NAME_T, int, IR]]: 52 | """All IRs in this dataset. 53 | 54 | The number of IRs returned must be the same as by list_irs(), 55 | and order must be identical. 56 | 57 | Returns: 58 | Iterator or sequence of (name: NAME_T, sr: int, ir: IR) pairs, 59 | where `sr` is the IR's sample rate. 60 | """ 61 | 62 | @abc.abstractmethod 63 | def __getitem__(self, name: NAME_T) -> IR: 64 | """Get a single IR by name. 65 | 66 | Returns: 67 | The IR audio array 68 | """ 69 | 70 | @abc.abstractmethod 71 | def __len__(self) -> int: 72 | """Number of IRs in this dataset.""" 73 | 74 | @abc.abstractmethod 75 | def list_irs(self) -> List[Tuple[NAME_T, int, int, int]]: 76 | """List of IRs in this dataset, with metadata. 77 | 78 | The number of IRs returned must be the same as by getall(), 79 | and order must be identical. 80 | 81 | Returns: 82 | List of (name, nchan, nsamples, sr) pairs, where `nchan` is the 83 | number of audio channels in the IR, `nsamples` is the number of 84 | samples (duration) of the IR, and `sr` is the IR's sample rate. 85 | """ 86 | 87 | def __str__(self): 88 | return f"{self.__class__} ({self.name})" 89 | 90 | 91 | class FileIRDataset(IRDataset[NAME_T]): 92 | """Base class for datasets whose IRs are read from files. 93 | 94 | Args: 95 | root (pathlib.Path or str): Root directory where the dataset resides. 96 | """ 97 | 98 | file_patterns: Sequence[str] 99 | exclude_patterns: Sequence[str] = () 100 | _files_list: List[pathlib.Path] 101 | 102 | def __init__(self, root: pathlib.Path): 103 | super().__init__() 104 | self.root = pathlib.Path(root) 105 | 106 | @abc.abstractmethod 107 | def _get_ir(self, name: NAME_T) -> IR: 108 | """Retrieve a single IR.""" 109 | 110 | @abc.abstractmethod 111 | def _list_irs(self) -> List[Tuple[NAME_T, int, int, int]]: 112 | """See ``.list_irs``.""" 113 | 114 | def __str__(self): 115 | return super().__str__() + f" root={self.root}" 116 | 117 | def list_files(self) -> List[pathlib.Path]: 118 | """List all files in the dataset.""" 119 | self._populate_files_list() 120 | return self._files_list 121 | 122 | def _populate_files_list(self): 123 | if not hasattr(self, "_files_list"): 124 | self._files_list = self._list_files() 125 | 126 | def _list_files(self) -> List[pathlib.Path]: 127 | return [ 128 | f 129 | for p in self.file_patterns 130 | for f in sorted(self.root.glob(p.replace("/", os.sep))) 131 | if not any(f.match(e) for e in self.exclude_patterns) 132 | ] 133 | 134 | def getall(self): 135 | self._populate_irs_list() 136 | for name, sr, ir in self._getall(): 137 | check_nonmono(ir) 138 | yield name, sr, ir 139 | 140 | def __getitem__(self, name): 141 | self._populate_irs_list() 142 | ir = self._get_ir(name) 143 | check_nonmono(ir) 144 | return ir 145 | 146 | def __len__(self): 147 | return len(self.list_irs()) 148 | 149 | def list_irs(self): 150 | self._populate_irs_list() 151 | return self._irs_list 152 | 153 | def _populate_irs_list(self): 154 | if not hasattr(self, "_irs_list"): 155 | self._irs_list = self._list_irs() 156 | 157 | def _getall(self): 158 | return ((name, sr, self[name]) for name, *_, sr in self.list_irs()) 159 | 160 | 161 | class CacheMixin: 162 | def __init__(self): 163 | self.__cache = {} 164 | 165 | def cached(self, key, func, *args, **kwargs): 166 | if key not in self.__cache: 167 | self.__cache[key] = func(*args, **kwargs) 168 | return self.__cache[key] 169 | -------------------------------------------------------------------------------- /realrirs/datasets.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | from typing import Tuple 3 | 4 | import numpy as np 5 | 6 | from .base import CacheMixin, FileIRDataset 7 | 8 | 9 | class DelayedImportError: 10 | def __init__(self, name): 11 | self.name = name 12 | 13 | def __getattr__(self, attr): 14 | raise ImportError(f"Please install {self.name!r} to perform this operation.") 15 | 16 | 17 | try: 18 | import scipy.io as scipy_io 19 | except ImportError: 20 | scipy_io = DelayedImportError("scipy") 21 | try: 22 | import soundfile 23 | except ImportError: 24 | soundfile = DelayedImportError("soundfile") 25 | try: 26 | import pysofaconventions as sofa_conv 27 | except ImportError: 28 | sofa_conv = DelayedImportError("pysofaconventions") 29 | try: 30 | import librosa 31 | except ImportError: 32 | librosa = DelayedImportError("librosa") 33 | 34 | 35 | def _soundfile_info(f: pathlib.Path) -> Tuple[int, int, int]: 36 | with soundfile.SoundFile(str(f)) as fobj: 37 | return fobj.channels, len(fobj), fobj.samplerate 38 | 39 | 40 | def _audioread_info(f: pathlib.Path) -> Tuple[int, int, int]: 41 | with librosa.core.audio.audioread.audio_open(str(f)) as fobj: 42 | return fobj.channels, fobj.duration * fobj.samplerate, fobj.samplerate 43 | 44 | 45 | class FOAIRDataset(FileIRDataset[Tuple[str, int]], CacheMixin): 46 | name = "360° Binaural Room Impulse Response (BRIR) Database for 6DOF spatial perception research" 47 | url = "https://zenodo.org/record/2641166" 48 | license = "CC BY 4.0" 49 | 50 | file_patterns = ["**/*.mat", "**/*.wav"] 51 | 52 | def _list_irs(self): 53 | files = self.list_files() 54 | mat_irs = [ 55 | ((f, i), 2, n_samples, 48000) 56 | for f, _, (n_samples, n_irs), _ in [ 57 | (f, *scipy_io.whosmat(f)[0]) for f in files if f.match("*.mat") 58 | ] 59 | for i in range(n_irs) 60 | ] 61 | wav_irs = [((f, -1), *_soundfile_info(f)) for f in files if f.match("*.wav")] 62 | return mat_irs + wav_irs 63 | 64 | def _get_ir(self, name): 65 | fpath, idx = name 66 | if idx == -1: 67 | with soundfile.SoundFile(fpath) as fobj: 68 | return fobj.read().T 69 | else: 70 | mat = self.cached(("loadmat", fpath), scipy_io.loadmat, fpath) 71 | return np.array([mat["IR_L"][:, idx], mat["IR_R"][:, idx]]) 72 | 73 | 74 | class AIRDataset(FileIRDataset[str]): 75 | name = "Aachen Impulse Response Database" 76 | url = "https://www.iks.rwth-aachen.de/forschung/tools-downloads/databases/aachen-impulse-response-database/" 77 | license = "?" 78 | 79 | file_patterns = ["**/*.mat"] 80 | 81 | def _list_irs(self): 82 | files = self.list_files() 83 | return [ 84 | ( 85 | f, 86 | *[ 87 | info 88 | for name, info, _ in scipy_io.whosmat(f, struct_as_record=False) 89 | if name == "h_air" 90 | ][0], 91 | 48000, 92 | ) 93 | for f in files 94 | ] 95 | 96 | def _get_ir(self, name): 97 | return scipy_io.loadmat(name, struct_as_record=False)["h_air"] 98 | 99 | 100 | class SoundfileDataset(FileIRDataset[pathlib.Path]): 101 | """Base class for datasets that can be read by `soundfile`.""" 102 | 103 | def _list_irs(self): 104 | files = self.list_files() 105 | return [(f, *_soundfile_info(f)) for f in files] 106 | 107 | def _get_ir(self, name): 108 | with soundfile.SoundFile(name) as fobj: 109 | data = fobj.read() 110 | if data.ndim == 1: 111 | return data.reshape((1, -1)) 112 | else: 113 | return data.T 114 | 115 | 116 | class LibrosaDataset(FileIRDataset[pathlib.Path]): 117 | """Base class for datasets that can be read by `librosa` (most audio files).""" 118 | 119 | def _list_irs(self): 120 | files = self.list_files() 121 | return [(f, *_audioread_info(f)) for f in files] 122 | 123 | def _get_ir(self, name): 124 | data = librosa.core.load(name, sr=None, mono=False)[0] 125 | if data.ndim == 1: 126 | return data.reshape((1, -1)) 127 | else: 128 | return data 129 | 130 | 131 | class WavDataset(SoundfileDataset): 132 | file_patterns = ["**/*.wav"] 133 | 134 | 135 | class FlacDataset(LibrosaDataset): 136 | file_patterns = ["**/*.flac"] 137 | 138 | 139 | class ASHIRDataset(WavDataset): 140 | name = "Audio Spatialisation for Headphones (ASH) Impulse Response Dataset" 141 | url = "https://github.com/ShanonPearce/ASH-IR-Dataset" 142 | license = "CC BY-CC-SA 4.0" 143 | 144 | file_patterns = ["BRIRs/**/*.wav"] 145 | 146 | 147 | class HopkinsDataset(WavDataset): 148 | name = "Greg Hopkins IR 1 – Digital, Analog, Real Spaces" 149 | url = "https://www.dropbox.com/sh/vjf5bsi28hcrkli/AAAmln01N4awOuclCi5q0DOia/Greg%20Hopkins%20IR%201%20-%20Digital%2C%20Analog%2C%20Real%20Spaces" 150 | license = "?" 151 | 152 | file_patterns = ["Real Spaces/**/*.wav"] 153 | 154 | 155 | class IOSRRealRoomsDataset(FileIRDataset[Tuple[pathlib.Path, int, int]], CacheMixin): 156 | name = "Surrey Binaural Room Impulse Response Measurements" 157 | url = "https://github.com/IoSR-Surrey/RealRoomBRIRs" 158 | license = "MIT" 159 | 160 | file_patterns = ["**/*_48k.sofa"] 161 | 162 | def _list_irs(self): 163 | files = self.list_files() 164 | return [ 165 | ((f, measurement, receiver), 1, dims["N"].size, 48000) 166 | for f, dims in [ 167 | (f, sofa_conv.SOFAFile(str(f), "r").getDimensionsAsDict()) 168 | for f in files 169 | ] 170 | for measurement in range(dims["M"].size) 171 | for receiver in range(dims["R"].size) 172 | ] 173 | 174 | def _get_ir(self, name): 175 | fpath, measurement, receiver = name 176 | return self.cached(fpath, sofa_conv.SOFAFile(str(fpath), "r").getDataIR)[ 177 | measurement, receiver 178 | ].reshape((1, -1)) 179 | 180 | 181 | class KEMARDataset(FileIRDataset[Tuple[pathlib.Path, str]]): 182 | name = "Dataset of measured binaural room impulse responses for use in an position-dynamic auditory augmented reality application" 183 | url = "https://zenodo.org/record/1321996" 184 | license = "CC BY-NC 4.0" 185 | 186 | file_patterns = ["**/*.mat"] 187 | surround_types = ["L", "LS", "R", "RS", "C", "S"] 188 | 189 | def _list_irs(self): 190 | files = self.list_files() 191 | return [((f, t), 2, 96000, 48000) for f in files for t in self.surround_types] 192 | 193 | def _get_ir(self, name): 194 | fpath, surround_type = name 195 | irs = scipy_io.loadmat(fpath, struct_as_record=False)["brirData"][0][ 196 | 0 197 | ].impulseResponse 198 | (ir,) = [ir for t, ir in irs if t[0] == surround_type] 199 | return ir.T 200 | 201 | def _getall(self): 202 | for f in self.list_files(): 203 | irs = scipy_io.loadmat(f, struct_as_record=False)["brirData"][0][ 204 | 0 205 | ].impulseResponse 206 | irs = {t[0]: ir for t, ir in irs} 207 | for t in self.surround_types: 208 | yield (f, t), 48000, irs[t].T 209 | 210 | 211 | class MIRDDataset(FileIRDataset[Tuple[pathlib.Path, int]]): 212 | name = "Multi-Channel Impulse Response Database" 213 | url = "https://www.iks.rwth-aachen.de/forschung/tools-downloads/databases/multi-channel-impulse-response-database/" 214 | license = "?" 215 | 216 | file_patterns = ["**/*.mat"] 217 | 218 | def _list_irs(self): 219 | files = self.list_files() 220 | return [((f, i), 1, 480000, 48000) for f in files for i in range(8)] 221 | 222 | def _get_ir(self, name): 223 | fpath, i = name 224 | return scipy_io.loadmat(fpath, struct_as_record=False)["impulse_response"][ 225 | :, i 226 | ].reshape((1, -1)) 227 | 228 | def _getall(self): 229 | for f in self.list_files(): 230 | irs = scipy_io.loadmat(f, struct_as_record=False)["impulse_response"] 231 | for idx, ir in enumerate(irs.T): 232 | yield (f, idx), 48000, ir.reshape((1, -1)) 233 | 234 | 235 | class Reverb2014Dataset(WavDataset): 236 | name = "REVERB challenge RealData" 237 | url = "http://reverb2014.dereverberation.com/" 238 | download_urls = [ 239 | "http://reverb2014.dereverberation.com/tools/reverb_tools_for_Generate_mcTrainData.tgz" 240 | ] 241 | license = "?" 242 | 243 | file_patterns = ["**/RIR_*.wav"] 244 | 245 | 246 | class TUIInEarBehindEarDataset(FileIRDataset[Tuple[pathlib.Path, str, int]]): 247 | name = "Dataset of In-The-Ear and Behind-The-Ear Binaural Room Impulse Responses" 248 | url = "https://github.com/pyBinSim/HeadRelatedDatabase" 249 | license = "CC BY-NC 4.0" 250 | 251 | def _list_files(self): 252 | return [ 253 | self.root.joinpath("lab_brirs.mat"), 254 | self.root.joinpath("reha_brirs.mat"), 255 | self.root.joinpath("tvstudio_brirs.mat"), 256 | ] 257 | 258 | def _list_irs(self): 259 | lab, reha, tvstudio = self.list_files() 260 | cfgs = [(t, i) for t in ["inear", "btear"] for i in range(32)] 261 | return ( 262 | [((lab, t, i), 2, 22050, 44100) for t, i in cfgs] 263 | + [((reha, t, i), 2, 44100, 44100) for t, i in cfgs] 264 | + [((tvstudio, t, i), 2, 22050, 44100) for t, i in cfgs] 265 | ) 266 | 267 | def _get_ir(self, name): 268 | fpath, t, i = name 269 | mat = scipy_io.loadmat(fpath, struct_as_record=False) 270 | mat = getattr(mat[list(mat.keys())[-1]][0][0], t)[0][0] 271 | return np.array([mat.left[i], mat.right[i]]) 272 | 273 | def _getall(self): 274 | for f in self.list_files(): 275 | mat = scipy_io.loadmat(f, struct_as_record=False) 276 | mat = mat[list(mat.keys())[-1]][0][0] 277 | for t in ["inear", "btear"]: 278 | data = getattr(mat, t)[0][0] 279 | for idx, (l, r) in enumerate(zip(data.left, data.right)): 280 | yield (f, t, idx), 44100, np.array([l, r]) 281 | 282 | 283 | class BellVarechoicDataset(FileIRDataset[Tuple[pathlib.Path, int]]): 284 | name = "Impulse Responses from the Bell Labs Varechoic Chamber" 285 | url = "?" 286 | license = "?" 287 | 288 | def _list_files(self): 289 | return [ 290 | self.root.joinpath("IR_00.mat"), 291 | self.root.joinpath("IR_43.mat"), 292 | self.root.joinpath("IR_100.mat"), 293 | ] 294 | 295 | def _list_irs(self): 296 | files = self.list_files() 297 | return [((f, i), 1, 8192, 10000) for i in range(4) for f in files] 298 | 299 | def _get_ir(self, name): 300 | fpath, i = name 301 | return ( 302 | list(scipy_io.loadmat(fpath).values())[0][:, i] 303 | .astype("float64") 304 | .reshape((1, -1)) 305 | ) 306 | 307 | 308 | class IOSRListeningRoomsDataset(FileIRDataset[Tuple[pathlib.Path, int]], CacheMixin): 309 | name = "The IoSR listening room multichannel BRIR dataset" 310 | url = "https://github.com/IoSR-Surrey/IoSR_ListeningRoom_BRIRs" 311 | license = "CC BY 4.0" 312 | 313 | file_patterns = ["IoSR_ListeningRoom_BRIRs.sofa"] 314 | 315 | def _list_irs(self): 316 | files = self.list_files() 317 | return [ 318 | ((f, measurement), dims["R"].size, dims["N"].size, 48000) 319 | for f, dims in [ 320 | (f, sofa_conv.SOFAFile(str(f), "r").getDimensionsAsDict()) 321 | for f in files 322 | ] 323 | for measurement in range(dims["M"].size) 324 | ] 325 | 326 | def _get_ir(self, name): 327 | fpath, measurement = name 328 | return self.cached(fpath, sofa_conv.SOFAFile(str(fpath), "r").getDataIR)[ 329 | measurement 330 | ] 331 | 332 | 333 | class BinaryArrayDataset(FileIRDataset[pathlib.Path]): 334 | """Base class for datasets that are stored as binary audio arrays and can 335 | be read using ``np.fromfile`` or ``np.memmap``. 336 | 337 | Args: 338 | use_memmap (bool, default True): Whether to use ``np.memmap`` to read the files. 339 | """ 340 | 341 | #: Sample rate of the array 342 | sample_rate: int 343 | #: Data type of the array (str or dtype object) 344 | dtype = "float32" 345 | 346 | def __init__(self, *args, **kwargs): 347 | self.use_memmap = kwargs.pop("use_memmap", True) 348 | super().__init__(*args, **kwargs) 349 | 350 | def _list_irs(self): 351 | files = self.list_files() 352 | return [ 353 | (f, 1, f.stat().st_size / np.dtype(self.dtype).itemsize, self.sample_rate) 354 | for f in files 355 | ] 356 | 357 | def _get_ir(self, name): 358 | if self.use_memmap: 359 | return np.memmap(name, self.dtype, "r").reshape((1, -1)) 360 | else: 361 | return np.fromfile(name, self.dtype).reshape((1, -1)) 362 | 363 | 364 | class RWCPDataset(BinaryArrayDataset): 365 | name = "RWCP Sound Scene Database in Real Acoustical Environments" 366 | url = "https://www.openslr.org/13/" 367 | license = "?" 368 | 369 | sample_rate = 48000 370 | file_patterns = ["near/data/rsp*/*", "micarray/**/imp*.*"] 371 | 372 | def _get_ir(self, name): 373 | data = super()._get_ir(name) 374 | # Normalize very large values 375 | return data / np.abs(data).max() 376 | 377 | 378 | class BUTDataset(WavDataset): 379 | name = "BUT Speech@FIT Reverb Database" 380 | url = "https://speech.fit.vutbr.cz/software/but-speech-fit-reverb-database" 381 | license = "CC BY 4.0" 382 | 383 | file_patterns = ["**/IR_*.wav"] 384 | 385 | 386 | class OpenAIRDataset(WavDataset): 387 | name = "Open Acoustic Impulse Response (Open AIR) Library" 388 | url = "https://openairlib.net/" 389 | license = "?" 390 | 391 | exclude_patterns = ["examples/*"] 392 | 393 | 394 | class Darmstadt2017SamplesDataset(WavDataset): 395 | name = "R-Prox RIR samples Darmstadt June 2017" 396 | url = "https://zenodo.org/record/1209820" 397 | license = "CC BY 4.0" 398 | 399 | file_patterns = ["**/*rir.wav"] 400 | 401 | 402 | class Darmstadt2018SamplesDataset(WavDataset): 403 | name = "RIR samples Darmstadt and Helsinki, Summer-Autumn 2018" 404 | url = "https://zenodo.org/record/1434786" 405 | license = "CC BY 4.0" 406 | 407 | file_patterns = ["**/*rir.wav"] 408 | 409 | 410 | class DRRDataset(WavDataset): 411 | name = "DRR-scaled Individual Binaural Room Impulse Responses" 412 | url = "https://zenodo.org/record/61072" 413 | license = "CC BY-NC-SA 4.0" 414 | 415 | 416 | class IsophonicsDataset(WavDataset): 417 | name = "Database of Omnidirectional and B-Format Impulse Responses" 418 | url = "http://isophonics.net/content/room-impulse-response-data-set" 419 | license = "?" 420 | 421 | 422 | class PoriIRsDataset(WavDataset): 423 | name = "Concert Hall Impulse Responses – Pori, Finland" 424 | url = "http://legacy.spa.aalto.fi/projects/poririrs/" 425 | license = "Custom, similar to CC BY-NC-SA" 426 | 427 | 428 | class SPARGAIRDataset(WavDataset): 429 | name = "METU SPARG Eigenmike em32 Acoustic Impulse Response Dataset v0.1.0" 430 | url = "https://zenodo.org/record/2635758" 431 | license = "CC BY 4.0" 432 | 433 | 434 | class VoxengoDataset(WavDataset): 435 | name = "Voxengo Free Reverb Impulse Responses" 436 | url = "https://www.voxengo.com/impulses/" 437 | license = "Custom, similar to CC BY-SA" 438 | 439 | 440 | class MARDYDataset(WavDataset): 441 | name = "Multichannel Acoustic Reverberation Database at York" 442 | url = "https://www.commsp.ee.ic.ac.uk/~sap/resources/mardy-multichannel-acoustic-reverberation-database-at-york-database/" 443 | license = "?" 444 | 445 | 446 | class HybridReverb2Dataset(FlacDataset): 447 | name = "Impulse Response Database for HybridReverb2" 448 | url = "https://github.com/jpcima/HybridReverb2-impulse-response-database" 449 | license = "CC BY-SA 4.0" 450 | 451 | 452 | class MITDataset(WavDataset): 453 | name = "Statistics of natural reverberation enable perceptual separation of sound and space" 454 | url = "https://mcdermottlab.mit.edu/Reverb/IR_Survey.html" 455 | license = "?" 456 | 457 | 458 | class EchoThiefDataset(WavDataset): 459 | name = "EchoThief Impulse Response Library" 460 | url = "http://www.echothief.com/downloads/" 461 | license = "?" 462 | 463 | 464 | class SMARDDataset(WavDataset): 465 | name = "Single- and Multichannel Audio Recordings Database (SMARD)" 466 | url = "https://www.smard.es.aau.dk/" 467 | license = "?" 468 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | with open("README.md", encoding="utf-8") as fh: 4 | long_description = fh.read() 5 | 6 | setup( 7 | name="realrirs", 8 | version="0.1.0", 9 | author="Jonas Haag", 10 | author_email="jonas@lophus.org", 11 | url="https://github.com/jonashaag/RealRIRs", 12 | description="Python loaders for many Real Room Impulse Response databases", 13 | long_description=long_description, 14 | long_description_content_type="text/markdown", 15 | license="ISC", 16 | python_requires=">=3.4", 17 | extras_require={ 18 | "full": ["librosa", "pysofaconventions", "scipy", "soundfile"], 19 | }, 20 | packages=find_packages(), 21 | include_package_data=True, 22 | classifiers=[ 23 | "Development Status :: 4 - Beta", 24 | "Programming Language :: Python :: 3", 25 | "License :: OSI Approved :: ISC License", 26 | "Operating System :: OS Independent", 27 | ], 28 | ) 29 | -------------------------------------------------------------------------------- /tools/gentable.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pathlib 3 | import textwrap 4 | 5 | import numpy as np 6 | 7 | import realrirs.datasets 8 | 9 | R = pathlib.Path(os.environ["REALRIRS_ROOT"]) 10 | 11 | datasets = [ 12 | realrirs.datasets.AIRDataset(R.joinpath("AIR_1_4")), 13 | realrirs.datasets.DRRDataset(R.joinpath("DRR scaled BRIRs")), 14 | realrirs.datasets.ASHIRDataset(R.joinpath("ASH-IR-Dataset")), 15 | realrirs.datasets.HopkinsDataset(R.joinpath("Hopkins IR Library")), 16 | realrirs.datasets.HybridReverb2Dataset( 17 | R.joinpath("HybridReverb2-impulse-response-database") 18 | ), 19 | realrirs.datasets.IOSRRealRoomsDataset(R.joinpath("IOSR RealRoomBRIRs")), 20 | realrirs.datasets.IsophonicsDataset(R.joinpath("isophonics")), 21 | realrirs.datasets.KEMARDataset(R.joinpath("KEMAR")), 22 | realrirs.datasets.PoriIRsDataset(R.joinpath("poririrs")), 23 | realrirs.datasets.Reverb2014Dataset(R.joinpath("reverb2014")), 24 | realrirs.datasets.Darmstadt2018SamplesDataset( 25 | R.joinpath("RIR_samples_2018_summer-autumn") 26 | ), 27 | realrirs.datasets.Darmstadt2017SamplesDataset(R.joinpath("RIRsc_Darmstadt_June")), 28 | realrirs.datasets.SPARGAIRDataset(R.joinpath("spargair")), 29 | realrirs.datasets.VoxengoDataset(R.joinpath("voxengo")), 30 | realrirs.datasets.MARDYDataset(R.joinpath("MARDY")), 31 | realrirs.datasets.BellVarechoicDataset(R.joinpath("varechoic")), 32 | realrirs.datasets.TUIInEarBehindEarDataset( 33 | R.joinpath("TUI_InEar_BehindEar_BRIR_dataset") 34 | ), 35 | realrirs.datasets.RWCPDataset(R.joinpath("RWCP")), 36 | realrirs.datasets.BUTDataset(R.joinpath("BUT_ReverbDB_rel_19_06_RIR-Only")), 37 | realrirs.datasets.OpenAIRDataset(R.joinpath("openair")), 38 | realrirs.datasets.MIRDDataset(R.joinpath("MIRD")), 39 | realrirs.datasets.IOSRListeningRoomsDataset(R.joinpath("IoSR_ListeningRoom_BRIRs")), 40 | realrirs.datasets.FOAIRDataset(R.joinpath("360-BRIR-FOAIR-database")), 41 | realrirs.datasets.MITDataset(R.joinpath("MIT")), 42 | realrirs.datasets.EchoThiefDataset(R.joinpath("EchoThiefImpulseResponseLibrary")), 43 | realrirs.datasets.SMARDDataset(R.joinpath("SMARD")), 44 | ] 45 | 46 | 47 | def process_ds(ds): 48 | print("Processing", ds) 49 | trimmed_ir_shapes = [ 50 | (ir.shape[0], len(np.trim_zeros(ir[0])) / sr) for _, sr, ir in ds.getall() 51 | ] 52 | return ds.name, { 53 | "n_irs": len(trimmed_ir_shapes), 54 | "total_duration": sum(1 * trimmed_len for _, trimmed_len in trimmed_ir_shapes), 55 | "total_duration_channels": sum( 56 | n_channels * trimmed_len for n_channels, trimmed_len in trimmed_ir_shapes 57 | ), 58 | "license": ds.license, 59 | "url": ds.url, 60 | } 61 | 62 | 63 | for ds_name, ds_meta in sorted(map(process_ds, datasets)): 64 | print( 65 | " | ".join( 66 | [ 67 | "", 68 | f'[{ds_name}]({ds_meta["url"]})', 69 | ds_meta["license"] or "", 70 | str(ds_meta["n_irs"]), 71 | f'{ds_meta["total_duration"]/60:.1f} s', 72 | f'{ds_meta["total_duration_channels"]/60:.1f} s', 73 | "", 74 | ] 75 | ).strip() 76 | ) 77 | --------------------------------------------------------------------------------