I design interactive systems that translate hidden dynamics into sound, working across electroacoustics, psychoacoustics, and real-time computation. It takes two skills that don't usually sit in the same person: the sound design that decides how something should feel to hear, and the engineering that makes it run in real time.
Max MSP / RNBO
Python
Web Audio
OSC
Real-time
Selected projects
A voice for electric vehicles
EV Engine Sound Sonification · Automotive
Jun 2026
An audio engine that gives electric cars a voice, synthesised in real time from live OBD-II telemetry with a hybrid of wavetable and granular synthesis in Max MSP. The control signal turned out to be the harder problem: telemetry arrives every 96 ms while the DSP needs a value every 1.33 ms. Validated across 18 recorded drives and assessed on the road by a conservatory examination panel.
Turns a live Binance order flow into notes and shifting texture you can monitor without watching a chart. Five microstructure metrics drive three noise-excited synthesis engines authored in RNBO, running as WebAssembly. Rebuilt from zero after a first version that worked but became unbearable over a session.
Electric cars are silent, so drivers lost the feedback that told them how fast they were going and how hard the car was working. This engine rebuilds it in real time from the car's own telemetry.
My role. Research and DSP development. Road testing with Luca Piscanec, who provided the test vehicle and drove every recorded session.
The control signal was the hard problem here, more than the synthesis. Telemetry arrives every 96 ms while the DSP needs a value every 1.33 ms: 72 audio blocks apart, measured across 22,367 recorded frames.
Every decision carries a price. A fourth OBD-II parameter would cost 16% of the frame rate, which is why reverse gear never got its own sound; physical modelling was dropped because the input is known to misbehave.
Validated, then evaluated in context. 18 recorded drives turn the iteration loop reproducible; a conservatory examination panel assessed the system from inside the moving car.
And stated honestly. One vehicle, CPU profiled only on a laptop, no user study. The limits are set out at the end.
Why an EV needs a voice
A combustion engine tells the driver things no instrument does. Its pitch and its roughness are a continuous, ambient report on speed and effort, and drivers act on that report without ever consciously consulting it: shifting, easing off, moderating consumption when the engine audibly strains. An electric vehicle removes that feedback entirely.
The goal is to give it back, without demanding attention. Every synthesis decision follows from that. Speed and effort have to stay legible as separate sensations, so each drives its own perceptual axis. The result then has to be heard as a single source, the voice of the car, which is what the entire FX chain exists to enforce.
Scope. This is an interior, driver-facing sound. It is not a homologated AVAS: UN R138 and FMVSS 141 regulate exterior pedestrian-warning sound, and no claim of compliance with either is made here. I led the research and DSP development; road testing was done with Luca Piscanec, who provided the test vehicle and drove every recorded session.
The constraint: the input is 72 audio blocks slower than the engine
OBD-II is a request/response protocol. The bridge asks for one parameter and waits for the ECU to answer; nothing is pushed. Measured across 22,367 frames and 35.8 minutes of recorded driving:
MEASURED18 RECORDED DRIVES
frame interval96 ms median · 2.5 ms deviation
worst case254 ms · 2.65× the median
blocks between frames72 median · 191 worst
cost per PID15.3 ms of pure protocol round-trip
The DSP needs a value every 1.33 ms. A new one arrives every 96.
The audio engine is roughly seventy times faster than its own input. All of the latency lives in the acquisition path.
That single measurement decided most of what follows. It also set the tolerance: the rare outliers run 2.65× the median, so any interpolation scheme has to survive a frame arriving nearly three times late.
Why only three parameters
Queries are sequential and blocking, so every additional parameter is paid on every frame. Three PIDs measure about 46 ms against a 50 ms target, leaving roughly 4 ms of headroom. A fourth costs 16% of the live frame rate and blows the deadline outright. The parameter budget was therefore fixed before a single mapping was designed.
That ceiling cost a feature. In reverse, speed is reported as a positive number: nothing in the three-value stream distinguishes backing up from moving forward. A dedicated reverse sound needed a fourth PID plus gear-state inference: bandwidth and latency on every frame, for one gear. I dropped the feature. The three values also travel as a single OSC message, so Max never receives a frame in which RPM has advanced but speed has not.
Two signals, opposite failure modes
Speed is a staircase. It arrives quantised to integer km/h and changes on fewer than one frame in five; at steady cruise it held a single value for 48 consecutive frames, 4.6 seconds frozen. Speed drives the granular read position at one second per 10 km/h, so a 1 km/h increment is an instantaneous 0.1 s jump in the buffer, a discontinuity in timbre as much as in level. Applied raw, a cruise would sit perfectly still for seconds and then lurch. That is what justifies ramping the control signals, and why the patch glides between successive frames.
RPM is the opposite problem. It moves on 94.8% of frames and is never still, but jumps as far as 1,959 RPM between one frame and the next. Here smoothing has to absorb the spikes while keeping the response that makes acceleration feel immediate. Same stream, two failure modes, two treatments.
What I tried first
FM was rejected on timbre. It reached inharmonic, metallic territory too readily, and its results were simultaneously predictable and hard to steer, the opposite of the organic, slightly rustic character the project needed. What it lacked was malleability.
Physical modelling was prototyped and abandoned on robustness. A physical model stays stable only while its inputs stay inside the range it was tuned for, and this input is a polled telemetry stream that delivers 1,959 RPM jumps and occasional hard zeros. Wavetable and granular synthesis fail gracefully by comparison: a bad value produces a wrong sound, never an unstable one. The measurements chose the synthesis method.
Two methods run in parallel because neither carries the sound alone. Wavetable gives harmonic identity under precise control: a major-third dyad that opens to a perfect fifth with speed, plus a seventh-chord layer that turns from major to minor past 100 km/h. Each table is instantiated twice and detuned against itself, engine load driving the beat rate from 1 to 8 Hz, so effort arrives as beating, brightness and stereo width at once. Granular supplies the friction: 36 voices reading a 22-second buffer at one second per 10 km/h, warm and harmonic early, dense and mechanical late.
INPUTSOUND MAPPING
RPMwavetable pitch · grain pitch · brightness
engine loadgrain duration · grain density · detune beating
speeddyad ↔ tetrad crossfade · grain scan position
The FX matrix exists to fuse the two. Identical saturation on both layers so the same added harmonics give the ear a cue to group them, a shared resonant filterbank standing in for chassis cavities, and an early-reflection network that places the result in the cabin. If a listener resolves the layers into separate voices the illusion fails: the car stops having a voice and starts having a soundtrack.
36 voices is the lowest count that still produced the intended texture, held at that floor for embedded hardware I have not yet profiled on; on an M4 Pro it costs 2% of the audio deadline. The 22-second buffer spans 220 km/h, headroom sized for the vehicle class (the test car peaked at 122).
A defect I chose not to fix
When a frame is lost the synthesiser receives zero: an assertion that the engine has stopped. Pitch collapses for the duration of that frame and recovers. For a sound whose entire job is tracking engine state it is the worst artefact available, and it is audible in the road test video.
Two unrelated things produce it. On the road it happens at every gear change of the combustion test vehicle, and it stays unpatched on purpose: the target class is single-speed, and an EV with no gearbox never produces it. Guarding against something the deployment vehicle cannot do only defends the prototype. Inside the recordings there is a second, rarer population: 43 zeros, each exactly one frame long, each hitting a single parameter, with every interval between them an exact multiple of 128 frames. A strict 128-frame grid is not driving behaviour. That one is the acquisition stack, it has nothing to do with the gearbox, and it is still open.
What only the road revealed
Every drive was recorded to CSV before any tuning, which turned validation into a reproducible loop: a change to the patch can be judged against the exact same 4.6-second speed freeze without booking a car, a driver and a road. Two things escaped that loop entirely. Standing still was exhausting. With the vehicle stopped the sound continued at full level, and over a session it wore down both of us, so a level reduction at standstill was added. The car's speakers are not monitors. The cabin system flattened exactly the band where engine effort was legible, so a load mapping that read clearly on headphones barely registered at the wheel. The timbre was rebalanced brighter.
Assessed in the car, by a panel
The system was submitted for a conservatory examination in electronic music. Three faculty members of the electronic music department were driven for 15 to 20 minutes with it running live in the cabin, across ordinary mixed driving. The assessment was positive and specifically endorsed the informative intent; the panel reported that the sound conveyed speed and effort as designed, and awarded the highest mark the examination allows: 30 out of 30, with honours.
That is expert evaluation in the actual deployment context, and it is still not a user study. They were passengers, so nobody was performing the driving task the feedback exists to support; there were three of them; there was no task and no control condition; and they were grading the work while they listened.
What this does not prove
One vehicle, and a combustion one standing in for an EV. CPU measured on a laptop, which predicts nothing about an automotive head unit. Only one run of eighteen crosses 100 km/h, so the major-to-minor seventh transition rests on 31 seconds of recording. Public roads cannot legally yield more, and the rest needs a closed circuit. And no user study: the premise that this helps a driver estimate speed and effort rests on informed first-hand report, and remains untested.
Every timing figure above is regenerated from the recordings in the repository by a single script, standard library only. The full engineering documentation is in the README: measurement tables, the analysis of the dropped-frame grid, per-module patch detail and the open items.
My role. Sole author: DSP design in Max and RNBO, the market-metric layer, frontend architecture and UX.
A working architecture discarded, not patched. Version one sounded static, turned irritating well before the end of a session, and could not tell you which way the market was moving. It is documented with its diagnosis, because the rebuild is only legible against what it replaced.
The obvious mapping was rejected twice. Once as continuous pitch, once as avoiding pitch for price altogether, and the second rejection is the one that had cost version one its readability.
Every decision carries its price. The adaptive scale costs absolute comparability, a limit I later ran into myself; the most important cue in the display is exactly cancelled by mono playback.
Measured where it can be, honest where it cannot. 3.2% of one core, flat across engines; ~86 ms of network delay dwarfs the entire software path; the evaluation is a five-listener pilot, and it includes the listener for whom the display meant nothing.
Why listen to a market at all
A chart answers what happened to someone who is looking at it. It answers nothing to someone who is not. Monitoring is a sustained-attention task with a bad ratio: hours of nothing, punctuated by seconds that matter, on a channel that needs the eyes pointed at it the whole time. Audition is the obvious substitute: omnidirectional, no fixation required, and unusually good at detecting change in a stream it has stopped consciously attending to.
It is also why most sonifications fail in practice. Mapping a scalar straight to frequency produces a continuous glissando, which is an alarm, and an alarm cannot be left running. Everything below follows from that one constraint.
Scope. This is a perceptual monitoring instrument, not a trading tool. It issues no signals, executes no orders, holds no credentials, and makes no claim that listening to it improves any trading outcome. It reads Binance's public trade stream and turns it into sound.
The version I threw away
There was a complete, working first version before this one: a wavetable oscillator pair, timbre morphing on the 24-hour trend, a binaural-beat detune on price micro-variation, and pitch assigned to volatility, deliberately kept off price to dodge the price-to-pitch cliché. It failed in three ways, and I abandoned the project for months.
It was static. A wavetable read at a stable rate only changes when the morph position changes, and market data moves it slowly, so the sound had no internal life, and a sound with no internal life becomes furniture within minutes. That is the exact failure mode a peripheral display cannot have. It became irritating. Two detuned oscillators holding a continuous beat frequency is a fine effect for thirty seconds and an unpleasant one for an hour. And market direction was unreadable: displacing pitch onto volatility dodged the cliché and cost me the only auditory metaphor everyone already knows.
Price → pitch is a cliché because the metaphor works. What had to go was its continuity.
Take the discrete derivative of price, quantise it to a scale interval, and the metaphor everybody already understands survives without the glissando that makes it unbearable.
What replaced it
A second decision followed from the first: physical modelling in place of wavetables. A modelled resonator has internal behaviour, where energy enters, circulates, decays and interacts with the excitation. It sounds like something is being played, which is the right metaphor when the market is the performer and the DSP is the instrument.
order flowstereo lean · bearish ← → bullish, glided over 1 s
price Δquantised interval · direction and width
volumenoise excitation amplitude · log curve
densitybrightness · comb gain · detune · reverb damping
volatilityloop gain · reverb tail, both inversely
Volatility runs inversely everywhere, deliberately. The intuitive mapping, turbulence leaving a long reverberant wake, buys an evocative image at the cost of smearing together the events a listener needs to count when the market is moving fastest. Rising volatility lowers the loop gain in every engine and shortens the reverb tail, so the display dries out and becomes more articulate at once.
Intervals, not glissando
Price drives a step through a pre-quantised scale array, taken from its discrete derivative over the last two ticks: a positive delta ascends, and the interval width tracks the size of the move. With eight voices per engine, transients trigger overlapping discrete notes that decay into each other like a harp.
The cost is real. Any price move smaller than the scale step is inaudible: the display quantises away micro-structure that a continuous mapping would have preserved. I took that trade because a display you mute after ten minutes has an effective resolution of zero.
An adaptive scale, and the price of it
The value range is re-derived continuously. A 30-second calibration window captures the range of each metric, then thresholds snap up to any new peak and decay back over the following minute, so the mapping re-ranges itself as regimes change. A fixed scale, calibrated once, fails in both directions: a quiet market is inaudible and a volatile one saturates the top of the range and stays there, so the display goes dead in exactly the two regimes where it should be most informative.
What it costs is absolute comparability. The same pitch, the same brightness, the same reverb tail mean different things at different times. You cannot look away for an hour, come back and read the level. It is a change detector, not a gauge. I confirmed that the hard way by using it myself for hours: I never once knew what the market was actually worth.
The most important cue is the most fragile one
Order-flow imbalance drives the stereo image: sell pressure leans left, buy pressure leans right. The two channel gains run in opposition about 11 dB apart and glide over a second, so both channels always carry signal and the image drifts across the field. A hard pan would park one ear on silence and flick on every alternating trade. Encoding the lean as timbre was rejected too: timbre is decodable, and decoding is exactly what the design is trying to avoid. Anything a listener has to interpret consciously has already spent the attention the display exists to save. Spatial position is pre-attentive, felt before it is parsed.
It is also the weakest link. The cue is destroyed by mono playback, by a single earbud, by any downstream mono-sum: the two gains sum identically in both states, so the information is cancelled exactly. The single most important signal in the display is the one most fragile to how it is listened to, and there is no redundant encoding of it.
Three engines, all running at once
Three switchable voices share one excitation: a glassarmonica of paired inharmonic resonators, a four-operator FM pad, and a Karplus-Strong bowed string, all driven by the same external noise source weighted by trade volume. Nothing is instantiated or torn down when the listener switches. All three banks compute continuously and the selector routes the shared excitation, not the output. Switching the output would cut the outgoing voice mid-ring; switching the excitation lets it decay the way a struck instrument does, so the change costs no click, no mute and no gap. It falls out of the topology, with no crossfade written for the purpose.
That is three engines' worth of DSP for one audible voice, permanently, and it is defensible only because it was measured: 3.2% of one core, with the decision due for revision on hardware where 3% became 30%. Three voices also exist for a reason beyond variety. The listening sessions showed that timbral tolerance is personal, and since the display's value depends entirely on someone being willing to leave it on, "pick the voice you can live with" is a functional requirement.
A decay control that never controlled decay
The waveguide had a fixed-frequency buzz, independent of pitch, that grew louder over time, and turning its "decay" coefficient changed the timbre while leaving the decay time untouched. The root cause: the one-pole loop filter had unity DC gain for any feedback coefficient, so that coefficient set colour only, and at DC the loop degenerated into a perfect integrator, accumulating any DC component of the excitation without bound. The parameter named decay was never a decay parameter.
The fix replaced one implicit stage with three explicit ones: a loop gain strictly below unity that is the actual decay, a first-order DC blocker inside the loop so nothing at DC can circulate, and energy normalisation of the injection so amplitude tracks input level independently of the loop gain. The result is stable by construction. A second bug is documented alongside it: an audible roughness in the FM pad that traced back to the filters, where a single 15 ms follower driving both amplitude and modulation index was phase-modulating the carrier inside the psychoacoustic roughness band.
What the measurements actually licensed
The DSP renders 15 seconds of stereo audio in about 477 ms, 3.2% of one core, and the interesting result is the flatness: switching engine moves it by 0.02 percentage points and muting the output changes nothing at all. That table is the price tag of the always-on design above, stated as a number.
Next to the network, none of it matters. Sampling the live stream, the median delay from event time to local receipt is 86 ms, with a 99th percentile near 400, while the entire software path costs single-digit milliseconds. There is nothing worth optimising on my side of the socket, and any latency claim about this app is really a claim about Binance and the route to it. One property I did not expect: 61.6% of consecutive trade events carry the same millisecond timestamp, which makes the density metric bimodal, far from the smooth rate its name suggests. I documented it and left it in place, since the correct definition is a different metric that would need every mapping it drives re-tuned. And where a figure could not be taken, device output latency being unmeasurable in a headless browser, none is quoted.
What listening showed, including where it failed
Roughly four hours of my own background use, plus one twenty-minute session with five listeners who heard both this version and the abandoned one. Three finance-literate listeners found it useful and a musician found it intuitive. The fifth is the informative one. A non-investor could not correlate pitch movement with price movement at all: to them the display was simply meaningless. The sonification is not self-explanatory. It supplies an acoustic cue and assumes a listener who already knows what a price move means. For a professional monitoring instrument that assumption is probably fine; as a general claim about intuitiveness it fails, and the honest fix is onboarding.
Two shipped features exist because of those sessions: the three switchable engines, and the user-controlled sensitivity curve, added after one listener asked to hear the light movements I had tuned out as noise. There is no correct setting for it. It encodes how much attention the listener is willing to spend, and only they know that.
What this does not prove
Fatigue, which is the central claim of the entire project. Twenty minutes sits inside the window where even the abandoned version was still pleasant, so the session was structurally incapable of testing it; the only exposure long enough to speak to it is my own four hours, which is one maximally biased listener. Every tolerability statement here is design rationale, grounded in the specific failure of the previous version. Beyond that: the pilot was unblinded, with no task and no control condition; the performance figures come from one machine; and an offline render proves throughput, leaving deadline safety under load unmeasured.
The full engineering documentation is in the README: the abandoned architecture with its mapping table, both bug diagnoses with their analytic arguments, the complete measurement method and the per-engine DSP detail.
I build systems that let people listen to data they would otherwise have to watch. My work sits where electroacoustics, psychoacoustics, and real-time computation meet, turning signals that are normally invisible into sound a listener can hold at the edge of awareness.
I'm a composer and audio developer finishing a Master's in Electronic Music at the Conservatorio Tartini in Trieste, where I also took my Bachelor's, on a research track built around interactive and data-driven audio. My practice runs between two poles. One is the engineering needed to capture and process live signals at low latency; the other is the sound design needed to make the result musical, legible, and comfortable to live with. Every mapping decision begins as a perceptual question, what an experience should feel like to hear, and only then becomes a technical one.
That instinct comes from years as a multi-instrumentalist, trained across percussion, saxophone, guitar, and piano. Playing rooted my ear in the physical behaviour of sound long before I approached it through code, and it still shapes how I judge what I make. Alongside my research I keep an active compositional practice in acousmatic and electroacoustic music, and I treat critical listening as its own discipline, not a final polish.
My interests fall under sonification and auditory display, the corner of Sound and Music Computing concerned with making information audible without making it intrusive. My next step is an MSc in that field, and alongside it I am looking for an industry partner to do the applied work with: a thesis project, an internship, or a research collaboration where a composer's ear drives the perceptual side of applied audio. Both projects on this site were designed, built and documented end to end on my own, and that is what I would bring to one.
Tools in use
Max MSP / RNBO · Python
Currently learning
Raspberry Pi · Sensors · Public-facing installations