Skip to content

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Unreleased

Added

Changed

Removed

[0.1.0] 2026-11-05

Migration from the previous main

Nothing is kept under its old name. What replaces what:

Before Now
Simulator(scene).rir_from_ray_tracing(image_source_order=, n_rays=, air_absorption=, random_seed=) scene.simulate(Mono(ray_tracing=True, image_source_order=, n_rays=, air_absorption=, seed=))
Simulator(scene).rir_from_image_source_method(order) scene.simulate(Mono(image_source_order=order, ray_tracing=False))
simulator.sampling_rate = fs Scene(name, sampling_rate=fs) or scene.sampling_rate = fs
simulator.trace_rays(...) RayField.from_scene(scene, ...)
Ray(..., sound_speed=), RayField(..., sound_speed=) removed; travel times are at Defaults().DEFAULT_SPEED_OF_SOUND
compas_acoustics.ray, compas_acoustics.specular compas_acoustics.rays (or the classes from compas_acoustics)
compas_acoustics.acoustic_material, compas_acoustics.materials_library compas_acoustics.materials (or the classes from compas_acoustics)
compas_acoustics.geometry compas_acoustics.acoustic_object (or the classes from compas_acoustics)
compas_acoustics.numerical.fitness compas_acoustics.fitness, or compas_acoustics.regularized_fitness
compas_acoustics.pra_converter compas_acoustics.room.pra (no longer an attribute of compas_acoustics)
JSON with "dtype": "compas_acoustics.<module>/<Class>" "dtype": "compas_acoustics/<Class>"; files saved before do not load
simulator.rirs, simulator.results the responses returned by scene.simulate, or scene.impulse_responses(method_name), scene.get_impulse_response(source, receiver)
Scene(simulation_platform=), ImpulseResponse(simulation_platform=) removed; ir.generation names the method
AcousticObjectsGroup; scene.iter_acoustic_objects() scene.add_acoustic_objects(shoebox_room(...)) (a list); scene.acoustic_objects
scene.acoustic_materials = {...} shoebox_room(materials=...), AcousticObject.from_obj(path, materials=...): a material per object
AcousticAnalyser.compute_rt, compute_clarity_definition, compute_drr, compute_sti, compute_stage_support, compute_strength reverberation_times, clarity_definition, direct_to_reverberant_ratio, speech_transmission_index, stage_support, sound_strength
stage_acoustics.compute_ST1, compute_ST2, compute_G_stage, compute_ITDG Analyser.stage_support()
Auraliser.multiconvolution(), Auraliser.convolve(ir, sound) Auraliser(...).convolve(), in the format of the impulse responses
Auraliser.decode_stereo_simple(), Auraliser.resample_signal() no decoder: binaural responses from Binaural(), ambisonic ones from Ambisonics() saved for a decoder elsewhere; pyfar.dsp.resample
Auraliser(output_format=...) removed: the signal has the format of its impulse responses, Auraliser.format (mono, binaural or ambisonic)
sample_rate (ImpulseResponse, Auraliser, AudioPlayer.play, receivers_to_pra_mic_array) sampling_rate
pra_converter.material_to_pra, mesh_to_pra_wall, acoustic_object_to_pra_walls RoomGeometry.to_pra, compas_acoustics.room.pra.scene_to_pra_room
Tools removed
compas_acoustics.orchestra_layouts; the Orchestra Layout component removed
AcousticAnalyser Analyser
Auraliser, AcousticAnalyser, MaterialsLibrary as Data plain classes: no guid, copy() or compas.json_dumps
Grasshopper: Simulate Ray Tracing, with a sample_rate input Simulate Mono; the rate is the sampling_rate input of Acoustic Scene
Grasshopper: Analyse IR output STI no longer an output, with G: they need pyrato >= 1.0
Source direction default +z; shoebox_room(materials={'left_wall': ..., 'right_wall': ...}) with left_wall at y = 0 direction default +x, as the Grasshopper Source component has it; left_wall is at y = width and right_wall at y = 0, the sides of a receiver at the centre facing +x (swap the two keys to keep a room)
scene.simulate(hybrid), then hybrid.binaural(hrtf) scene.simulate(Binaural(hrtf=...)), stored under 'binaural'
Hybrid() returning ambisonic responses Ambisonics(sh_order=...), stored under 'ambisonics'; the same seed gives both methods the same late field
synthesize(...), binaural_rir(result, hrtf, filters) core = prepare(...), then render_ambisonics(core, sh_order, normalization) or render_binaural(core, hrtf): each delay line through its own HRIR
ir.generation["output"] ir.format ('mono', 'binaural' or 'ambisonic'), read from the channels
shape_late(late, early, reference, fs) shape_late(signals, early_w, late_w, reference, fs)
Grasshopper: Simulate Ray Tracing output ir_graph Simulate Mono output mono_ir_graph
compas_acoustics.worker.proxy(), a compas.rpc server with COMPAS JSON and WAV files in temporary folders compas_acoustics.worker.client(), an HTTP service with compas_pb payloads
Grasshopper: the hybrid's reference given as a method name on the scene the mono_ir_graph wire of Simulate Mono only
Installer input worker (hybrid) not used: the installer always sets up the worker
Grasshopper: Simulate Hybrid output ir_graph (ambisonic) Simulate Binaural (hrtf, output binaural_ir_graph) and Simulate Ambisonics (the SH order dropdown, output ambisonic_ir_graph)
Grasshopper: Plot 2D with plane, x_size, y_size, settings and text, point and polyline outputs Plot 2D draws its charts on the canvas: input data (a list), no outputs
Grasshopper: Plot 2D Settings removed, with Plot 2D's geometry
Grasshopper: Material Library's nested right-click menu a picker dialog with search and a chart, opened from the body or "Choose material..."
Grasshopper: value lists and buttons placed next to new components controls drawn on the component body
Grasshopper: Analyse IR input time_scale right-click the time output: Time scale dB or linear
Grasshopper: Auralise input level, Acoustic Object input role, Deconstruct Parameter input select the switch on the body only
Grasshopper: Room Scene Acoustic Scene (nickname Scene), the same inputs and output
Grasshopper: Receiver input plane removed: position and direction, the receiver upright (+z up)
Grasshopper: Auralise inputs save, save_file_path, save_file_name the Save button on its body: a dialog chooses the folder and the name
Grasshopper: the Directivity component; Source input directivity the directivity dropdown on Source
Grasshopper: Audio Player input normalize (default True) the Norm toggle on its body, off by default
Grasshopper: Audio Player inputs play, stop; Visualise Billiard output t the Play, Pause and Stop buttons on the body; removed
Grasshopper: Audio Player input sampling_rate (unused) removed: a signal carries its rate
Grasshopper: Visualise Billiard input speed in ms of sound per second a factor of the default (the whole field in 10 s), the Speed dropdown 0.25x to 4x
Grasshopper: Visualise Energy Map Play and Rewind removed: the map shows the levels
Grasshopper: ism_order value list 0 to 6 the ISM order slider, 0 to 10
Grasshopper: Simulate Ambisonics input sh_order (1 to 3) the SH order dropdown on its body, 1 to 7
Grasshopper: Acoustic Scene input sampling_rate the Sampling rate dropdown on its body (44.1, 48, 96 kHz)
Grasshopper: Simulate Mono input time_thres duration, the length of the responses, as Mono(duration=) names it
RayField.from_scene(time_thres=) RayField.from_scene(duration=)
Grasshopper: Visualise Energy Map inputs n_rays, image_source_order, seed fixed: the rays from the room's volume, order 1, seed 42; the input spacing meshes the surface given

The Grasshopper components are now a compiled plugin (CompasAcoustics.gha) instead of CPython script components: a definition saved with the old components does not open with the new ones. Delete the old components in it and place the new ones from the toolbar; they have the same names, inputs and outputs.

Added

  • Added Scene.simulate(method) as the one way to simulate, with the methods Mono(image_source_order=3, ray_tracing=True, ...), Binaural(...) and Ambisonics(...) in compas_acoustics.simulation_methods.
  • Added Scene.sampling_rate (default 48000, serialised with the scene), the rate at which every method runs; the Acoustic Scene component has the input.
  • Added Binaural and Ambisonics, binaural and ambisonic room impulse responses from one hybrid engine: image sources, optional edge diffraction (diffraction="udfa" or "btms") and a directional feedback delay network tail; needs Python >= 3.11. Binaural(hrtf=None) takes "kemar" (the default) or a SOFA file and records it in generation["hrtf"]; Ambisonics(sh_order=1, normalization="SN3D") gives ACN channels. Responses are stored under 'binaural' and 'ambisonics'.
  • Added tail=... to Binaural and Ambisonics, with a reference response, the method's own ray-traced reference, or an Eyring or Sabine prediction as decay target.
  • Added edge diffraction at the edges of the room's own boundary (re-entrant corners) to the hybrid engine.
  • Added scattering=False to Binaural and Ambisonics, surface scattering after Ewert et al. (2025): each image's specular part, the product of sqrt(1 - s) over its reflections, stays in the early field, and its scattered part, the product of sqrt(s), enters the delay lines on its own hemisphere through an allpass cascade. Off by default.
  • Added compas_acoustics.hybrid.synthesis.prepare, compas_acoustics.hybrid.ambisonics.render_ambisonics (with ambisonic_output_matrix and default_num_delays, moved from hybrid.late) and compas_acoustics.hybrid.binaural.render_binaural (HrtfSet): one format-neutral core per pair (its early paths and delay lines, HybridCore) and two renderers; the binaural one filters every path and every delay line with the HRIR measured nearest its direction. Each renderer evaluates every path's spectrum, and every UDFA transfer function, once, and applies the late envelope (HybridCore.shaped_lines) against its own omnidirectional channel.
  • Added HybridResult.late_lines and HybridCore.line_directions: the late field per delay line before it is rendered, and each line's direction in the receiver frame.
  • Added Mono(ray_tracing=True), ray tracing on top of the image sources (n_rays, energy_thres, duration, receiver_radius, seed).
  • Added AirConditions(temperature=20, humidity=50, pressure=101325): speed of sound after Cramer (1993) and air absorption after ISO 9613-1, for every method, recorded in ImpulseResponse.generation.
  • Added RoomGeometry, the scene's surfaces as one validated room (welded, outward oriented, closedness checked, coplanar faces merged), with to_pra() and is_inside().
  • Added AcousticObject(role="boundary" | "obstacle") and AcousticObject.from_obj(path, materials=None), a list with one object per OBJ group, every one a boundary; groups materials does not cover have none until they are assigned, and AcousticObject.material accepts only an AcousticMaterial or None.
  • Added AcousticObject.assign_roles_by_group(objects, roles) and assign_materials_by_group(objects, materials) to mark OBJ groups as obstacles and give them materials after reading, and AcousticObject.group, the OBJ group an object was read from (not serialised); a group name that none of the objects has raises a ValueError.
  • Added Receiver.up, rotation, frame and Receiver.from_frame(): the orientation sets the axes of ambisonic responses (x facing, y left, z up).
  • Added compas_acoustics.room.diffuse (predicted_rt, mean_absorption, DiffuseFieldWarning): Sabine and Eyring reverberation times.
  • Added JSON serialisation (compas.json_dumps) for Scene, AcousticObject, Source, Receiver, ImpulseResponse, Ray, ScatteredRay and RayField; the IR graph is not serialised.
  • Added Scene.impulse_responses(attribute), Scene.simulation_methods() and attribute= on the IR graph methods: each method stores its responses under its own name.
  • Added ImpulseResponse with peak normalization (denormalized(), normalization_factor, level_db), to_wav(), from_wav(), generation, resampled(), ambisonic_order, normalization, channel_order and direct_sound_index().
  • Added Analyser.stage_support() (ST_early and ST_late, ISO 3382-1 Annex C) and sound_strength() (G, needs pyrato >= 1.0), and G in the analysis tables.
  • Added Analyser accepting an ImpulseResponse, analysed at its raw level and sampling rate.
  • Added Auraliser.format (mono, binaural or ambisonic, that of its impulse responses, which must share one), level (relative, individual, none), headroom_db, and lists of impulse responses and sound files as input.
  • Added RayField, Ray and ScatteredRay for traced rays (RayField.from_scene), with filtering, truncation and viewer display.
  • Added Source.directivity, Source.sound_file (the Auraliser mixes a multichannel file down to mono when it reads it), Receiver.direction, radius and received_spls.
  • Added AcousticMaterial.category and data_origin, MaterialsLibrary.materials_by_category, preset materials, and tools/import_backend_materials.py.
  • Added AudioPlayer for .wav files and numpy arrays, with normalize=True.
  • Added compas_acoustics.regularized_fitness, exported from the package.
  • Added a warning before an image source search that will take long (simulation_methods.image_source_search_seconds()).
  • Added compas_acoustics.worker: runs compas_acoustics in a second Python 3.12, for the Grasshopper plugin and for Rhino 8's Python 3.9: a local HTTP service on a free port with a token per session that takes and returns compas_pb payloads (client(), simulate, analyse, and in worker.api the functions the components call).
  • Added compas_acoustics.pb, a compas_pb plugin: the Data types as protobuf messages (pb/protobuf_defs/.../acoustics.proto), their Python bindings, converters registered through the compas_pb.plugins entry point, SCHEMA (which the plugin checks the worker against) and plain(); compas_pb >= 1.2 is a requirement. invoke generate regenerates the Python and C# bindings.
  • Added anechoic_soundfiles_library (bassoon, female and male speech, viola) and hrtfs_library (KEMAR, free-field compensated, 48 kHz), with READMEs that credit the sources and licences.
  • Added Defaults.HRTF_LIBRARY_PATH, ANECHOIC_SOUNDFILES_LIBRARY_PATH and PARAM_NAMES.
  • Added pyFDN and multislope to the requirements on Python >= 3.11, for the hybrid method (pyFDN brings flamo and PyTorch), and a CI job for it: one pip install compas_acoustics, no extra.
  • Added the Grasshopper plugin CompasAcoustics.gha (C#, src/compas_acoustics_ghcsharp/CompasAcoustics.sln, invoke build-gha): typed parameters for the compas_acoustics data, value lists and buttons next to unconnected inputs, and the worker as the only Python it runs, so STI, G and the hybrid method are always available. It needs Rhino 8 and the .NET 8 SDK to build.
  • Added the components Simulate Binaural and Simulate Ambisonics (with a run button, their last input), Simulate Mono (was Simulate Ray Tracing), Material Library categories, and the inputs level (Auralise), normalize (Audio Player, Simulate), method (Deconstruct IR Graph), direction and plane (Receiver, which outputs the receiver only and keeps it upright: +z up, no roll).
  • Added the input role (a value list: boundary or obstacle) to the Grasshopper Acoustic Object component; a wrong role or material is an error on the component instead of a traceback.
  • Added the IR graph of the Grasshopper plugin: Simulate Mono, Simulate Binaural and Simulate Ambisonics output the responses of their method (pyroomacoustics, binaural, ambisonics), and Deconstruct IR Graph reads them, so it has no method input.
  • Changed the Grasshopper Check Channel component to count the channels of any sound: an audio file path, an impulse response, a Source with a sound file, or an array of samples such as the signals of Auralise (the input is now sound).
  • Changed the Grasshopper Auralise component to also show the warnings of building the auraliser (impulse responses resampled to a common rate, sounds mixed down to mono), and the Audio Player to remark at which rate it plays an array that comes without one.
  • Changed the image source order input of the Grasshopper Simulate Mono (max_order), Simulate Binaural and Simulate Ambisonics (image_source_order) components to ism_order; the Simulate Mono description gave the default as 6, the method's default is 3.
  • Changed the order of the inputs of the Grasshopper Auralise component to irs, anechoic_sounds, level, mix_sources, save_file_path, save_file_name, save.
  • Changed the Grasshopper Auralise component to keep the format of its impulse responses, without the inputs output_format and hrtf: mono responses (Simulate Mono) give mono signals, binaural ones binaural, ambisonic ones the undecoded ACN/SN3D signal, and the file saved has that many channels. Binaural signals come from Simulate Binaural and its hrtf. With mix_sources, responses of different formats at one receiver are an error. It convolves in Rhino's Python, without the worker.
  • Changed the Grasshopper Audio Player component to warn for a signal of more than two channels (an ambisonic one) instead of an error, and AudioPlayer.play_array to play a mono signal on both channels.
  • Renamed the input sound of the Grasshopper Audio Player component to audio and ordered its inputs audio, sampling_rate, normalize, play, stop.
  • Renamed the inputs signals and multiconvolve of the Grasshopper Auralise component to anechoic_sounds and mix_sources: mix_sources sums the sources at each receiver into one signal.
  • Changed the order of the inputs of the Grasshopper Source component to position, name, direction, directivity, sound_file.
  • Changed AudioPlayer to read the audio devices again before it plays an array, so that headphones connected after Rhino started are the output.
  • Added the input select (a value list: all or one of EDT, T20, T30, C50, C80, D50, DRR, Ts, STI, G) to the Grasshopper Deconstruct Parameter component: a selected parameter comes out as plain lists, all as one branch per parameter. Its parameters input takes the list.
  • Renamed the Grasshopper Simulate Pyroomacoustics component to Simulate Mono, after the method it runs, Mono (pyroomacoustics; graph attribute and worker method 'mono', 'data' as before) (the same GUID: saved definitions load it) and its output ir_graph to mono_ir_graph, after the format of its responses, as Simulate Binaural's binaural_ir_graph and Simulate Ambisonics' ambisonic_ir_graph.
  • Added the input source_idx to the Grasshopper Deconstruct IR Graph component (inputs ir_graph, source_idx, receiver_idx): with receiver_idx it picks source-receiver combinations, e.g. a single impulse response for Analyse Impulse Response. Sources are indexed in the order of the scene's sources; an index out of range is a warning.
  • Added compas_acoustics.analyser.time_response (level in dB re the peak of all channels, or amplitude) and frequency_response (fractional-octave smoothed, in dB re the peak), per channel; levels are floored 100 dB below the peak.
  • Added the outputs time and frequency and the input time_scale (a value list: db or linear) to the Grasshopper Analyse Impulse Response component: the impulse response and its 1/3-octave smoothed frequency response from 20 Hz to 20 kHz, of the W channel of an ambisonic response and both ears of a binaural one. Plot 2D draws them as a curve per channel, on a time axis or a log frequency axis with ticks at 50, 100, 200, 500 ... 20000 Hz, reduced to the minimum and maximum of 1000 bins; a level in dB runs from its floor on the x axis to its peak, an amplitude is symmetric about zero with the x axis at zero. The time output in linear ends 5 % after its Schroeder curve falls 60 dB, in db it is the whole response, (time_response(..., trim_db=...)); the parameters and the frequency output use the whole response. AcousticParameter.from_time_response and from_frequency_response replace from_ir, which Plot 2D could not draw.
  • Renamed the output params of the Grasshopper Analyse Impulse Response component to parameters, and the input param of Deconstruct Parameter to parameters.
  • Renamed the input param of the Grasshopper Plot 2D component to data: it takes a parameter, or the time or frequency output of Analyse Impulse Response.
  • Changed the hrtf input of the Grasshopper Simulate Binaural component to get no value list: empty is KEMAR, the HRTF... button on the body chooses the user's own SOFA set (right-click "Use KEMAR" goes back), and a File Path connected to it wins; the component's message names the set in use.
  • Changed the seed input of the Grasshopper Simulate Mono, Simulate Binaural and Simulate Ambisonics components: left empty, each run draws a new random seed (shown in the component's message) instead of the methods' fixed default 42; a number gives repeatable responses. Mono, Binaural and Ambisonics themselves still default to 42.
  • Added a warning to the Grasshopper Simulate Binaural and Simulate Ambisonics components when tail is reference and nothing is connected to reference; it shows before Run, and Run does not start the simulation.
  • Added the output parameter to the Grasshopper Deconstruct Parameter component: the selected AcousticParameter, which Plot 2D plots and a panel prints (AcousticParameter.__str__ gives the name, unit, value per band and overall value).
  • Added STI and G to the Grasshopper Analyse IR component, computed in the worker (they need pyrato >= 1.0, which Rhino 8's Python 3.9 cannot install); G only for a simulated response.
  • Changed the Grasshopper Plot 2D chart to leave half a band's spacing between the y axis and the first frequency band.
  • Changed compas_acoustics.worker.simulate to store every format of a run on the scene's graph under its attribute and return the first, as Scene.simulate does; it used to store whatever came back under the method's name.
  • Added a status to the Grasshopper Simulate Binaural and Simulate Ambisonics components for when they are not running: it says done with the tail (and the ambisonic order) of the last result on the graph, settings changed, press Run when its inputs differ from that result, and press Run only when there is none.
  • Added the Grasshopper installer for Rhino 8 (installer/compas_acoustics_installer.gh), invoke installer-bundle, and compas-dev/compas-actions v1 workflows (build, release, prepare release), with tests on Python 3.9.
  • Changed the example scripts' names: demo.py is shoebox.py, demo_obj.py is obj.py, demo_rays.py is rays.py, demo_scattered_rays.py is scattered_rays.py; added specular_reflections.py.
  • Changed the example script shoebox.py to simple_simulation_shoebox.py; its Grasshopper twin is examples/simple_simulation_shoebox.ghx.
  • Added the documentation for the first public release: concepts, installation, a Python and a Grasshopper track, a Grasshopper component reference generated from the plugin's specs (docs/_ext/ghcomponents, also run by invoke docs-components), and build checks that fail when the committed images in docs/_images/ are out of date.
  • Added a getting-started section to the README, and Examples sections that invoke testdocs can run in the docstrings of Scene, Mono, Binaural, Ambisonics, Analyser and Auraliser.
  • Added SpecularReflections.from_scene(scene, max_order=3), the image-source paths from each source to each receiver for inspection, with filter(source, ray_index, receiver_index, order). It is a RayField of SpecularPath rays, so it plays in a billiard (particles) and displays like traced rays.
  • Added source directivity to RayField.from_scene, and so to Visualise Rays and Visualise Billiard: each source emits its rays with the density of its radiated energy, (p + (1 - p) cos theta)^2 about its direction (a deterministic lattice; a cardioid sends nothing straight back, a figure-of-eight nothing sideways); within a spotlight cone the cone's lattice is thinned to that density. An omnidirectional source emits the same rays as before.
  • Added RayField.particles(time_ms) and RayField.duration_ms, the rays' positions at a time, for a billiard view.
  • Added a directional ray-tracing histogram to the hybrid's late envelope (late_envelope=True of Binaural and Ambisonics, the default with a reference or ray-tracing tail): pyroomacoustics keeps one histogram per receiver direction (raytracing.directional_histograms, one bin per delay line, binned by the direction a ray arrives from; Lentz, Schroeder and Vorlaender 2007; Schroeder 2011, Sec. 5.3.4), and after the level per band follows the reference, each delay line is scaled by the square root of its direction's share of the rays' energy over its share of the lines' energy (hybrid.envelope.directional_shares, shape_late_directional), both pooled over neighbouring lines with the weights of Kirsch et al. (2023, Eq. 4), so the total is kept and a direction facing an absorbing wall decays as the rays say it does; the shares are held once the rays have decayed 60 dB. The histogram is traced by the method itself, also with a given reference: one more ray-tracing run per scene. The omnidirectional level and decay are unchanged.
  • Added the Grasshopper Visualise Reflections component: image-source paths of a source-receiver pair by order, output as reflections, which Visualise Billiard plays.
  • Added the Grasshopper Visualise Rays component: rays traced from one source (source_idx) or all, for display and Visualise Billiard.
  • Added the Grasshopper Visualise Billiard component: the rays of a ray field as particles (points only) at a time, played with a button or scrubbed with a slider.
  • Added colours by reflection order to Visualise Reflections, Visualise Rays and Visualise Billiard: each component draws its paths, ray segments or particles itself (round particles, order 0 black, one colour per order); RayField.particles returns each particle's reflection order too.
  • Added controls on the Grasshopper component bodies: switches and dropdowns under a title naming their input (Tail, Role), showing the chosen value; Simulate Binaural and Simulate Ambisonics tail, diffraction and run and Visualise Billiard play keep inputs, hidden on a new component and shown by "Show control inputs" in the right-click menu (a wired one always shows) for parameter studies; Auralise's level, Acoustic Object's role, Deconstruct Parameter's select and Source's directivity are chosen on the body only; Analyse IR's time scale on its time output's right-click menu; buttons (Run, Cancel; Play, reading Pause while playing (Play goes on from the paused frame), and Rewind on Visualise Billiard and Visualise Rays, whose rays are drawn only as Play makes them grow with the sound; a Preview toggle on the three Visualise components, which draw themselves and not their outputs). A wired value wins and greys the control; every choice is saved with the document; a control's tooltip says what its input means and its default.
  • Added body controls for the remaining choices of the Grasshopper components, each hidden behind "Show control inputs" like the others and starting at its input's default: the ISM order slider (whole orders 0 to 10, set when the knob is let go) on Simulate Mono, Simulate Binaural and Simulate Ambisonics; toggles and buttons set as far from the control above as a title is; on/off toggles sharing a row, Ray tracing, Air and Normalize on Simulate Mono, Late envelope and Normalize on the hybrid ones; the SH order dropdown (1 to 7, default 1) on Simulate Ambisonics, a choice of the component without an input; the sampling rate dropdown (44.1, 48, 96 kHz, default 48) on Acoustic Scene, a choice of the component without an input; Mix sources on Auralise; the Sound file... button on Source (its file's name on the button, right-click "Clear sound file"); the category dropdown on Construct Material; Source and Receiver dropdowns (All, or each by its index and name, from the wired scene or graph) on Visualise Rays, Visualise Reflections, Visualise Energy Map and Deconstruct IR Graph; Scattering and a slider of the drawn reflection order on Visualise Rays; the reflection orders 0 to 4 on Visualise Reflections, several lit at once; the time slider (it follows playback, and the particles follow the knob while it is dragged) and the Speed dropdown on Visualise Billiard; Play/Stop and Rewind side by side as icon buttons on Visualise Rays and Visualise Billiard; the Frequency bands dropdown (Full (A-weighted), or 125 Hz to 8 kHz) on Visualise Energy Map, its input renamed frequency_bands (was band). Controls on integer, number and bool inputs keep their values hidden and in the file, as text ones did.
  • Added the Audio Player's waveform, and impulse responses as its input (e.g. the ir output of Deconstruct IR Graph): the transport and volume right of the icon and below the inputs a lane per channel across the whole component, at one height, the component growing down with the channels, a Play/Pause and a Stop button with their usual glyphs, a playhead, Play, Pause, Stop, seeking by a click on the waveform, a volume slider, and a body that resizes.
  • Added runs that do not block the canvas: Simulate Mono, Simulate Binaural, Simulate Ambisonics, Analyse IR, Auralise, Visualise Rays and Visualise Reflections call the worker on a background thread, keep their last outputs meanwhile, show running m:ss and the worker's stage, and offer Cancel; a change of inputs cancels the run and starts another (Simulate Binaural and Simulate Ambisonics wait for Run).
  • Added viewport previews of sources (a loudspeaker: a cube turned with the source and a driver on its front face, the direction, the directivity lobe), receivers (a head: a sphere with its ears, the facing), acoustic objects and scenes (their edges only, as Rhino draws a surface's: outlines and folds, not the triangulation; no surfaces over the Rhino geometry they come from; obstacles with orange edges). IR graphs draw nothing. The Acoustic Object's message gives its total area, the Acoustic Scene's the room's volume and total area on two lines, its tooltip also the objects, sources, receivers and sampling rate. Sources and receivers are drawn only by the Source and Receiver components, under their preview toggles: not again by a scene, by Deconstruct IR Graph (which previews nothing, its source-to-receiver edges included), or by a Merge or Data they pass through. Components preview only their outputs, not the geometry, points or planes wired into them; Acoustic Object and Acoustic Scene start with their preview off, their surfaces being in Rhino already. Every component is drawn light and offers Preview, also those with nothing to draw (Grasshopper would draw them in the dark preview-off grey); those draw nothing: every output has a type (text, number, integer, point), none the generic one that draws whatever passes through it.
  • Added defaults and named values on the Grasshopper ports (ism_order 3, n_rays 20000 and 500, max_order 10, energy_thres 1e-7, k 1.0, sampling_rate 48000), and the scene's sources and receivers as named values of source_idx and receiver_idx.
  • Added the material picker (search, Custom above the categories, Name, Category and Origin, a chart of absorption and scattering on which a click or drag sets a band, and a box per band for each to type a value, of a library material or of a custom one whose name and category are editable), opened from the Material Library's strip or by a double-click on it; its inputs absorption and scattering replace the chosen coefficients; its strip, sized as Deconstruct Parameter's, draws absorption and scattering as the picker's paired bars over the bands, 0 and 1 beside; its message says when scattering is the 0.05 default; a custom material left without absorption warns. A material's tooltip gives its mean absorption.
  • Added Plot 2D on the canvas: a chart per parameter, a dot per band joined by a line or a line per channel, resizable at a fixed aspect ratio, STI on a 0 to 1 axis and the reverberation times (EDT, T20, T30) from 0, with the values under the mouse in the tooltip and a Save button that writes the charts as a PNG; Deconstruct Parameter shows its bands as a small line chart, the bands below and the lowest, middle and highest value beside.
  • Added the Worker component (state, Python, version and schema, Restart, Open log), "Open worker log" in every component's menu, the worker's log file, and message strips of at most 40 characters with the whole text in the tooltip; Acoustic Scene's message counts objects, sources, receivers, the rate and the volume.
  • Added one generated icon set for the components (tools/icons.py).
  • Added the worker routes POST /cancel/<call id> and GET /progress/<call id> and the request header X-Call-Id (compas_acoustics.worker.jobs); a cancelled call answers 499.
  • Added LF and LFC (ISO 3382-1 early lateral energy fractions) to Analyser.analyse(), Analyse IR and Deconstruct Parameter, from the first-order channels of an ambisonic response.
  • Added Source.spl as the source's level (SPL on axis at 1 m, per band; one value or seven) and SPL per band with its A-weighted total to Analyser (sound_pressure_level, analyse) and Analyse IR.
  • Added the spl input to the Grasshopper Source component (the source's level at 1 m per band, or one value).
  • Added EnergyMap (compas_acoustics.EnergyMap): the SPL of a scene's sources over many points, per band, A-weighted and over time, from pyroomacoustics' ray tracer with a microphone per point plus first-order image sources, and the worker's energy_map.
  • Added the Visualise Energy Map component: a surface (a mesh, a surface or a Brep, meshed into faces no larger than spacing, default 0.25 m; the rays keep a 1 m grid) coloured by the full A-weighted or one band's SPL at its face centres (the Frequency bands dropdown), drawn a centimetre above the surface so that the two do not flicker. Its settings are fixed: image sources up to order 1, seed 42, and the rays of energy_map.map_rays, Vorlaender's (2008) Eq. 11.12 over the response length for 16000 hits per point (20000 to 250000 rays). EnergyMap.from_scene(n_rays=None) uses the same count. The rays are traced to one receiver per 1 m cell of the map (ray_spacing=1.0, the cell's point nearest its centre) and their energy is interpolated to the other points by inverse squared distance; the image sources stay exact at every point. pyroomacoustics checks every receiver for every ray segment, so fewer receivers trace faster, and none lies partly outside the room next to a wall. The worker's energy_map takes no n_rays, image_source_order or seed, returns the rays traced as n_rays, and returns no frames of the energy arriving; EnergyMap.frames() gives them.

Changed

  • Changed the worker's playback API: play(sound, normalize=True, gain_db=0.0, start=0.0) returns the duration; pause, resume, stop, playback_status and sound_samples are new.
  • Changed how the hybrid's early renderer and late envelope compute, not what: early.render_images and render_transfers accumulate the paths' spectra as matrix products over chunks of paths instead of one outer product per path, and envelope.octave_band_split applies pyfar's reconstructing bank as one FFT per channel and one inverse FFT per band (zero-padded past the filter length, the linear convolution pyfar computes) with the bank designed once per sampling rate. Responses are unchanged to rounding.
  • Changed Analyser.speech_transmission_index to reduce the modulation transfer function it already has with pyrato's _sti_calc (IEC 60268-16:2020 Annex A.2.1) instead of calling speech_transmission_index_indirect, which computes the same MTF again; without _sti_calc the indirect method is called.
  • Changed the ray tracer's defaults: energy_thres 1e-7 (pyroomacoustics' own), since libroom stops a ray on the product of its wall reflections while the receiver logs that energy over the squared distance; and duration 1.5 times the longest Eyring reverberation time within 0.5 to 12 s. The runs Binaural and Ambisonics make themselves, a ray-traced reference and the directional histogram, take the same default. A reference shorter than 2/3 of its reverberation time warns, since pyFDN's decay fit shrinks its range to what the response has. A ray-traced response that ends before 2/3 of its own longest T30 warns with the length to rerun with: a room whose absorption is concentrated on a few surfaces can decay far slower than Eyring predicts. A room whose Eyring prediction hits the 12 s cap also warns (raytracing.default_duration): its rays live for hundreds of reflections, usually from a material with no low-frequency absorption on a large surface.
  • Changed the JSON data type of every compas_acoustics class to compas_acoustics/<ClassName> (was compas_acoustics.<module>/<ClassName>), so that saved data no longer depends on the module a class lives in; the materials library is stored that way. Files saved before do not load.
  • Changed the modules: ray and specular are one module, rays; acoustic_material and materials_library are materials; geometry is acoustic_object; numerical.fitness is fitness; room_geometry, air, diffuse, pra_converter, raytracing and _pyfar_constants are the subpackage room (room.geometry, room.air, room.diffuse, room.pra, room.raytracing, room._pyfar_constants), which exports RoomGeometry and AirConditions; room.raytracing holds both pyroomacoustics runs (image_source_rirs, ray_traced_rirs) and the directional histograms, the backend of Mono and of the binaural and ambisonics methods; binaural is hybrid.binaural. The classes are exported from compas_acoustics as before.
  • Changed Defaults().DEFAULT_SPEED_OF_SOUND to the speed of sound of AirConditions() (343.98 m/s at 20 C and 50 %, was 343), the air every simulation runs in without one given. Ray, ScatteredRay and RayField no longer take or store a sound_speed (it was 343 on its own and was serialised with every ray): their travel times are at this default; ray fields saved with it still load.
  • Changed the hybrid engine to use 96 delay lines up to third ambisonic order, and always for Binaural (hybrid.late.MIN_DELAYS): the count Kirsch, Wendt and van de Par (2023) run their network with whatever the number of rendered directions, so that the tail's character does not change with the count, and the one they name for offline simulation. In the binaural response each line is heard through its own HRIR. The methods take no num_delays or drive_gain: the count follows from sh_order (Ambisonics) or is 96 (Binaural), and the input gain sqrt(num_delays) offsets the output's 1 / sqrt(num_delays), so the late level does not depend on the count; hybrid.synthesis.prepare keeps both. The delay-line grid depends on the count alone (fdn_directions(num_delays)); whether it carries an ambisonic order is ambisonic_output_matrix's check.
  • Changed how the last rendered image-source order drives the hybrid's network: each image enters the delay lines of the hemisphere opposite its arrival, weighted by the cosine to each line (hybrid.scattering.scattering_weights(..., opposite=True)), the mapping of Ewert et al. (2025, Sec. 3.1.2).
  • Changed the spelling of identifiers to normalization and normalized everywhere, including ImpulseResponse.normalization (the ambisonic convention) and its JSON metadata key.
  • Changed AudioPlayer to raise errors and warnings.warn instead of printing them; the Audio Player component shows them as messages. An unsupported sound type raises a TypeError.
  • Changed seed of Mono, Binaural and Ambisonics (was 0) to True by default, which seeds with 42 (DEFAULT_SEED); an int seeds with that value; False (or None) leaves the run unseeded, a new draw each run, recorded as generation["seed"] = None. Anything else raises a ValueError. RayField.from_scene(random_seed=) follows the same rule.
  • Changed the hybrid engine to drive the network with the last rendered image source order, as Wendt et al. (2014) and Ewert et al. (2025) do. The search stops at image_source_order.
  • Changed Binaural and Ambisonics without a reference to run Mono(ray_tracing=True, image_source_order=3, n_rays=100000, seed=42) and fit the decay to it.
  • Changed the hybrid's 35 Hz high-pass to a causal filter, which removes pre-arrival ringing in the diffracted paths.
  • Changed every simulation to run in AirConditions() (20 C, 50 %, 343.98 m/s) unless air= is given; before, the methods used different air tables.
  • Changed simulated impulse responses to 48000 Hz (were always 16000 Hz) and to the package scale (free-field direct sound 1 / (4 pi r)).
  • Changed AcousticObject.__init__ to take one material; AcousticObjectsLibrary.shoebox_room() returns a list of six single-material objects and takes materials.
  • Changed Source.directional to Source.directivity, and Source.direction and Receiver.direction to compas.geometry.Vector.
  • Changed sample_rate to sampling_rate everywhere: ImpulseResponse, Auraliser, AudioPlayer.play, receivers_to_pra_mic_array, HrtfSet and the Grasshopper components.
  • Changed the Analyser methods to name the quantity: reverberation_times, clarity_definition, direct_to_reverberant_ratio, speech_transmission_index, stage_support, sound_strength.
  • Changed the analyser to apply octave filters zero-phase or after cutting the time windows, which removes a delay of 18 ms at 250 Hz and 35 ms at 125 Hz from EDT, Ts, C50, C80, D50 and DRR.
  • Changed direct_to_reverberant_ratio to 5 ms of direct sound, DRR_overall from a 700-4000 Hz band.
  • Changed reverberation_times to Schroeder integration without noise compensation; truncate a measured response with a noise floor before analysing it.
  • Changed the direct sound detection to the start of ISO 3382-1: the last sample more than 20 dB below the maximum before it.
  • Changed Auraliser.convolve() to return the signal in the format of its impulse responses, without decoding, summing the impulse responses of one sound first.
  • Changed Auraliser to auralise at the raw level, export peak at -1 dBFS, and use an impulse response's own channel_order and normalization.
  • Changed Auraliser to read and resample with pyfar (polyphase) and to default to the rate of the first impulse response.
  • Changed Analyser, Auraliser and MaterialsLibrary to plain classes; they are services, not data.
  • Changed the default HRTF to KEMAR_FreeFieldComp_48kHz.sofa.
  • Changed a material without scattering coefficients to scatter 0.05 in every band in every pyroomacoustics simulation.
  • Changed AcousticMaterial so that a coefficient of 0 is a value, and material names may have spaces and capitals; library files are named by AcousticMaterial.slug and carry no backend suffix.
  • Changed sources and receivers outside the room to raise a UserWarning, and surfaces with role="obstacle" to reflect from both sides.
  • Changed Scene.acoustic_materials to read-only; assigning it raises an AttributeError that says how materials are assigned.
  • Changed the hybrid engine to accept diffraction without obstacles, and to report non-closed obstacle components in one warning per call.
  • Changed the package to run on Python 3.9 (Rhino 8) without Rhino, Grasshopper or .NET; the Grasshopper plugin is in src/compas_acoustics_ghcsharp/.
  • Changed pyrato >= 1.0 to be required from Python 3.11 (was 3.12), and removed the upper bounds on pyroomacoustics and pyFDN.
  • Changed the Analyse IR component to leave out STI and G, which need pyrato >= 1.0; they stay in Analyser where pyrato allows.
  • Changed matplotlib to be imported by the Analyser plot methods only when they run.
  • Changed the Grasshopper installer to copy the plugin (the bundle's gha/ folder) into Grasshopper's Libraries folder, remove the CPython components of earlier versions, and always set up the worker; its worker input is not used.
  • Changed librosa out of the dependencies and sounddevice into them; spharpy is required on Python >= 3.11.
  • Changed RoomGeometry to weld vertices at 1 mm (WELD_TOLERANCE, was 1 micrometre), as Rhino's tolerance in a metre model: a wall corner 10 micrometres off no longer leaves the room open. A vertex still lies on an edge only within 1e-6 of the model's extent. Faces with a corner more than 10 micrometres off their plane are triangulated (pyroomacoustics raised a RuntimeError on them), faces that collapse in welding are dropped with a warning, a model more than 500 units across warns that coordinates are read as metres, and a wall pyroomacoustics refuses is a ValueError naming the face.
  • Changed AcousticObject.from_obj to read OBJ files with its own parser: o (object) lines are the groups of a file without g lines, faces may use i/j/k and negative indices, vertices are no longer rounded (RoomGeometry welds them), and faces with fewer than three distinct vertices are skipped.
  • Changed the default Source direction to +x (was +z), the direction the Grasshopper Source component already defaults to and the receiver's facing direction.
  • Changed AcousticObjectsLibrary.shoebox_room so that left_wall is the surface at y = width and right_wall the one at y = 0, matching the receiver frame (x facing, y left); the two names were swapped. The surfaces come in the order floor, right, front, left and back wall, ceiling.
  • Changed the error of Receiver for a name with spaces or capitals to say Receiver (it said Source).
  • Changed RoomGeometry.surfaces, surface_area, area_normals and volume to count the air side only: a closed obstacle counts one side and its volume is subtracted, and obstacle faces lying on the boundary are left out, so Eyring and Sabine times, the default ray-tracing duration and the hybrid's delays and scattering decay time move in scenes with closed obstacles or obstacles on the boundary; a free panel still counts both sides.
  • Changed AcousticMaterial to validate coefficients on creation: any iterable (including Grasshopper's List[Double]) and scalars become lists of float, a single value (a scalar or a one-element list) applies to every band, an empty list means no coefficients, and another length than 7 or a value outside [0, 1] raises ValueError.
  • Changed the hybrid's binaural late field to be rendered per delay line, each line / sqrt(N) through the HRIR measured nearest its direction, so a binaural response does not need sh_order >= 1.
  • Changed hybrid.late.render to return the delay lines' outputs, (n, num_delays), after the directional absorption and slope weights; the ambisonic late field is that times C.T.
  • Changed the format of a response to come from its channels, ImpulseResponse.format ('mono', 'binaural' for two channels without an ambisonic order, 'ambisonic'), not a generation["output"] flag; the Auraliser, the analysis plots and the Grasshopper Audio Player read it.
  • Filled in the package metadata, README and documentation for the first public release, and removed the tests that compared against external toolboxes, saved outputs or a running worker.
  • Changed the component specs (Specs/<CA_name>/metadata.json) to list their ports under ports.

Fixed

  • Fixed raytracing.directional_histograms failing with "all input arrays must have the same shape" when the run is longer than pyroomacoustics' default 10 s and rays are still alive then: libroom sizes every direction's histogram for 10 s and doubles only the directions hit later. The hybrid's late envelope failed this way on a reverberant room whose Eyring buffer reached 12 s.
  • Fixed the Grasshopper Acoustic Scene component failing with 'NoneType' object has no attribute 'sources' when an object is empty or not an acoustic object (an upstream Acoustic Object that failed): such items are left out with a warning.
  • Fixed RoomGeometry taking a vertex up to 1 mm off an edge for a T-junction, which left rooms with sliver faces open: the edge test is 1e-6 of the model's extent again.
  • Fixed the single-number values of C50, C80, D50 and Ts averaging 500, 1000 and 2000 Hz: they average 500 and 1000 Hz, as ISO 3382-1:2009 Table A.1 has it for G, EDT, C80, D50 and Ts (C50 follows D50), like EDT, T20 and T30 already did.
  • Fixed responses simulated in the worker (the hybrid in Rhino) always coming back as peak-normalized views: they keep the method's normalize. Their raw level was already kept.
  • Fixed the Grasshopper Material Library component forgetting its material when the file is reopened: the choice is kept in the component's nickname, which Grasshopper saves.
  • Fixed the Grasshopper Material Library component failing with No module named 'compas_acoustics': its script pinned an empty Rhino script environment, compas_acoustics_rhino, instead of the one the installer fills.
  • Fixed the worker setup from the Grasshopper installer failing at its last check: the worker's Python 3.12 inherited the PYTHONPATH of Rhino's 3.9 packages and could not import numpy.
  • Fixed a simulation failing on a Source whose sound_file is not a RIFF WAV (an AIFF or FLAC file; the library's bassoon.wav is an AIFF): the file is read with soundfile.
  • Fixed the hybrid engine counting an image source twice when its path passes through an edge (+6 dB on that path); hybrid.early.image_sources now keeps one image per position, as ShoeBox does.
  • Fixed low-frequency bias in EDT, C50, C80, D50, DRR and Ts caused by the delay of causal octave filters.
  • Fixed speech_transmission_index returning NaN for impulse responses shorter than 1.6 s.
  • Fixed wheels that were missing the materials library, the HRTFs, the sound files and the Grasshopper components.
  • Fixed Auraliser modifying the caller's dicts, ignoring 1D responses and multichannel sounds, and wrapping energy around when resampling.
  • Fixed simulations above 16 kHz with air absorption, which failed on a 7 against 8 octave band mismatch.
  • Fixed Receiver and Source losing name, orientation, directivity and settings in JSON and copy().
  • Fixed NaN responses for a source at a receiver's position (now a ValueError), and unrelated errors for points inside a closed obstacle, scenes of obstacles only, or too few walls.
  • Fixed the hybrid failing on a fully absorbing surface, and counting a wall hit on a diffraction apex as a reflection.
  • Fixed display_in_viewer guards that treated compas.is_rhino() as a boolean, and the default HRTF path that pointed at a missing file.
  • Fixed the Grasshopper Auralise and Analyse IR components using a fixed sample rate.
  • Fixed AcousticObject.from_obj reading OBJ files in the platform's encoding: a UTF-8 byte order mark dropped the first vertex, and non-ASCII group names were garbled on Windows; files are read as UTF-8.
  • Fixed obstacle faces lying on a boundary face being counted when the scene is not aligned with the axes: the test used the distance along each axis instead of the distance to the face's plane.

Removed

  • Removed Plot 2D Settings and Plot 2D's Rhino geometry (texts, points, polylines, grid).
  • Removed the worker's compas.rpc transport (proxy()) and its temporary WAV folders.
  • Removed Simulator; use Scene.simulate(method) and RayField.from_scene.
  • Removed simulation_platform from Scene and ImpulseResponse; generation names the method.
  • Removed Mono(n_jobs=) and the parallel ray tracer, which wrote private Room state.
  • Removed AcousticObjectsGroup, AcousticObject.surface_groups, Scene.iter_acoustic_objects, Scene.scene_surface_groups and their display and print methods; see scene_materials and print_scene_materials.
  • Removed compas_acoustics.stage_acoustics and tools.Tools; use Analyser.stage_support().
  • Removed pra_converter.material_to_pra, mesh_to_pra_wall, acoustic_object_to_pra_walls and room_octave_bands; see RoomGeometry.to_pra.
  • Removed Auraliser.multiconvolution(), decode_stereo_simple(), resample_signal() and FuMa channel order.
  • Removed compas_acoustics.orchestra_layouts and the Orchestra Layout component.
  • Removed the two MIT KEMAR HRTF sets and speech.wav.
  • Removed Hybrid and Hybrid.binaural(); use Binaural or Ambisonics.
  • Removed the ambisonic decoders of Auraliser (mono from W, stereo cardioids and binaural: output_format, decode, decode_mono, decode_stereo, decode_binaural, hrtf_path, binaural_filters, ambisonics_upmix_downmix), binaural.magls_filters, worker.convolve and the installer's worker option binaural. The hybrid renders binaural responses with an HRTF per path and per delay line; an ambisonic response or signal is saved as ACN/SN3D for a decoder elsewhere. Impulse responses of different formats in one Auraliser raise a ValueError.