<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>TITAN Haptics | Antoine Weill--Duflos</title>
    <link>https://antoine.weill-duflos.fr/en/tag/titan-haptics/</link>
      <atom:link href="https://antoine.weill-duflos.fr/en/tag/titan-haptics/index.xml" rel="self" type="application/rss+xml" />
    <description>TITAN Haptics</description>
    <generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Sat, 30 May 2026 00:00:00 +0000</lastBuildDate>
    <image>
      <url>https://antoine.weill-duflos.fr/media/icon_hu_d686267daab28486.png</url>
      <title>TITAN Haptics</title>
      <link>https://antoine.weill-duflos.fr/en/tag/titan-haptics/</link>
    </image>
    
    <item>
      <title>A Pocket Rolling Stone, Part 1: A 2006 Haptic Illusion, an ESP32-C6, and an I2S Amp</title>
      <link>https://antoine.weill-duflos.fr/en/post/esp32-rolling-stone/</link>
      <pubDate>Sat, 30 May 2026 00:00:00 +0000</pubDate>
      <guid>https://antoine.weill-duflos.fr/en/post/esp32-rolling-stone/</guid>
      <description>&lt;p&gt;















&lt;figure  id=&#34;figure-the-part-that-makes-this-build-feel-real-titan-haptics-actuators-this-first-build-renders-the-virtual-ball-as-audio-and-drives-one-of-these-more-on-them-and-on-my-friend-ashley-huffman-who-got-them-to-me-further-down&#34;&gt;
  &lt;div class=&#34;d-flex justify-content-center&#34;&gt;
    &lt;div class=&#34;w-100&#34; &gt;&lt;img alt=&#34;A clear plastic case held in one hand, with four small black cylindrical TITAN Haptics actuators seated in foam cutouts, each with thin red and black lead wires&#34; srcset=&#34;
               /en/post/esp32-rolling-stone/featured_hu_5845204be01c0e90.webp 400w,
               /en/post/esp32-rolling-stone/featured_hu_d1077f3167290f3a.webp 760w,
               /en/post/esp32-rolling-stone/featured_hu_cb24e0e4817501a0.webp 1200w&#34;
               src=&#34;https://antoine.weill-duflos.fr/en/post/esp32-rolling-stone/featured_hu_5845204be01c0e90.webp&#34;
               width=&#34;573&#34;
               height=&#34;760&#34;
               loading=&#34;lazy&#34; data-zoomable /&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;figcaption&gt;
      The part that makes this build feel real: TITAN Haptics actuators. This first build renders the virtual ball as audio and drives one of these. More on them, and on my friend Ashley Huffman who got them to me, further down.
    &lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;This one has been with me for several years, not a weekend. The idea is simple to state and surprisingly hard to stop tuning: build a small handheld object that lets you &lt;strong&gt;feel a ball rolling and sliding inside a tube that does not physically exist&lt;/strong&gt;. You tilt the device, and a virtual marble runs from one end to the other, bumps the wall, rolls back. There is no moving mass inside. The whole sensation is synthesized.&lt;/p&gt;
&lt;p&gt;It started at the &lt;a href=&#34;https://eurohaptics.org/events/workshop-in-memoriam-of-vincent-hayward/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;workshop held in memoriam of Vincent Hayward&lt;/a&gt;, one of the two authors of the paper this is based on. Sitting there, it struck me that of this lovely demonstration there was, as far as I could tell, essentially one working set left in the world. That bothered me. I was fairly sure I knew a good actuator that could also produce a crisp impact, and that very minimal hardware would be enough to bring the illusion back to life. The piece I was missing, for years, was time: between a day job and parent duty, the build kept not happening. What finally unlocked it was using Claude Code to move fast in the small windows I do have, which is how the firmware, the two hardware paths, and the companion app actually came together.&lt;/p&gt;
&lt;p&gt;It is still very much ongoing. I have two different hardware paths working in firmware, the physics keeps getting refined, and the device is small, meant to stay a handheld object. This post is Part 1: the idea, the physics, the firmware, and the first of the two builds, the one that renders the ball as &lt;strong&gt;sound through an I2S amplifier&lt;/strong&gt;. A small honesty note up front: this audio build I validated on the bench, with the boards loose, but never closed up into a finished enclosure. The unit I actually assembled and carry around is the H-bridge build in &lt;a href=&#34;../esp32-rolling-stone-hbridge/&#34;&gt;Part 2&lt;/a&gt;, which drives a motor directly. Both paths run the exact same simulation.&lt;/p&gt;
&lt;h2 id=&#34;the-illusion-this-is-built-on&#34;&gt;The illusion this is built on&lt;/h2&gt;
&lt;p&gt;The whole thing is a reproduction of a lovely little paper: Hsin-Yun Yao and Vincent Hayward, &lt;em&gt;An Experiment on Length Perception with a Virtual Rolling Stone&lt;/em&gt;, Eurohaptics 2006, pages 325 to 330. The &lt;a href=&#34;https://cim.mcgill.ca/~haptic/pub/HY-VH-EH-06.pdf&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;PDF lives on the McGill haptics lab page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The setup in the paper is elegant. They give people a handheld tube with a single vibrotactile actuator inside, and they synthesize the feeling of a ball rolling along the inside of the tube as you tilt it. No real ball. The actuator plays back a vibration whose pitch tracks how fast the virtual ball is moving, plus a sharp transient every time the ball hits an end wall. Then they ask: can people estimate the &lt;strong&gt;length&lt;/strong&gt; of the virtual tube just from this feeling? The answer is yes, better than chance, and the really interesting part is that people seem to use an internal model of &lt;strong&gt;gravity&lt;/strong&gt; to do it. They are not just timing a sound, they are mentally rolling a ball under gravity and reading off how far it went.&lt;/p&gt;
&lt;p&gt;That is a haptic illusion in the purest sense: a perception of something physical (a ball, a tube, a length) created entirely from a one-dimensional vibration signal. It sits right next to a theme I have written about before, that &lt;a href=&#34;../haptic-illusions/&#34;&gt;we have museums for optical illusions but almost nothing for touch&lt;/a&gt;, and the related &lt;a href=&#34;../../project/3dprintedillusions/&#34;&gt;3D printed haptic illusions&lt;/a&gt; project from my lab days, where the whole point was that you can build a haptic illusion cheaply and put it in someone&amp;rsquo;s hand. This project is the powered, programmable cousin of those: instead of a clever piece of geometry, the illusion lives in firmware.&lt;/p&gt;
&lt;h2 id=&#34;what-the-device-actually-does&#34;&gt;What the device actually does&lt;/h2&gt;
&lt;p&gt;The hardware is deliberately minimal:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;ESP32-C6&lt;/strong&gt; (RISC-V, 160 MHz, single core). It is cheap, it has BLE, and it is more than fast enough.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;BNO085&lt;/strong&gt; 9-DOF IMU to sense how the device is tilted.&lt;/li&gt;
&lt;li&gt;One haptic output. This is where the two builds diverge: an &lt;strong&gt;I2S audio amplifier driving a TITAN Haptics actuator&lt;/strong&gt; (this post), or an &lt;strong&gt;H-bridge driving a motor&lt;/strong&gt; (&lt;a href=&#34;../esp32-rolling-stone-hbridge/&#34;&gt;Part 2&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The firmware reads the tilt, runs a small physics simulation of the ball, and turns the ball&amp;rsquo;s motion into a vibration in real time. Tilt the device and the ball accelerates downhill. Level it out and the ball coasts, slowing with friction. Tip it the other way and the ball reverses, runs to the far wall, and hits it with a thump whose strength depends on how fast it was going.&lt;/p&gt;
&lt;p&gt;There are actually three simulation modes in the firmware now, selectable at runtime:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rolling&lt;/strong&gt;: the ball rolls like a solid sphere.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sliding&lt;/strong&gt;: the ball slides with Coulomb friction, so it can stick on shallow slopes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Marbles&lt;/strong&gt;: a 3D box with three marbles of different mass and size bouncing off each other and the walls. This one drifted in while I was playing, and it is genuinely fun.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;the-physics-briefly&#34;&gt;The physics, briefly&lt;/h2&gt;
&lt;p&gt;The rolling and sliding modes are one-dimensional. The only input from the real world is &lt;code&gt;sin(α)&lt;/code&gt;, the sine of the tube&amp;rsquo;s tilt angle, which I read straight off the IMU&amp;rsquo;s gravity vector. From there it is textbook mechanics.&lt;/p&gt;
&lt;p&gt;For a solid sphere rolling without slipping, the moment of inertia steals some of the acceleration, so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ẍ = (g / 1.4) · sin(α)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;1.4&lt;/code&gt; is the &lt;code&gt;1 + 2/5&lt;/code&gt; factor for a solid sphere. In the firmware this collapses to a single constant &lt;code&gt;G_FACTOR = 7.0&lt;/code&gt; m/s².&lt;/p&gt;
&lt;p&gt;For the sliding mode I add Coulomb friction, with a dead zone where the slope is too shallow to overcome static friction and the ball just stays put:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ẍ = g · sin(α) − g · µ · sgn(sin(α)) · cos(α)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I integrate this with a trapezoidal step at 1 kHz, keeping the previous acceleration around so the integration stays stable. The ball is constrained to &lt;code&gt;[0, cavity]&lt;/code&gt;. When it reaches a wall and is still moving into it, that is an &lt;strong&gt;impact&lt;/strong&gt;: I record the speed, bounce the velocity with a restitution coefficient of 0.5, and flag the event for the haptic layer. A small piece of edge-detection state stops the impact from re-firing while the ball is resting against a wall under gravity, which would otherwise produce an ugly buzz.&lt;/p&gt;
&lt;p&gt;The marble box is the same spirit in 3D: each marble gets box acceleration and gravity in the non-inertial frame of the device, collides with the six walls, and collides with the other marbles using a proper unequal-mass impulse along the contact normal. The haptic signal is driven by the largest collision impulse on each tick, so a heavy steel marble slamming the wall feels different from the light plastic one.&lt;/p&gt;
&lt;h2 id=&#34;the-firmware-shape&#34;&gt;The firmware shape&lt;/h2&gt;
&lt;p&gt;The ESP32-C6 is single core, so I lean on FreeRTOS to keep the timing clean. Three tasks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;taskIMU&lt;/code&gt;&lt;/strong&gt; (priority 3) reads the BNO085 as fast as I2C allows and updates a set of volatile globals. I2C is the slow part, so it gets its own task and is decoupled from everything else.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;taskPhysics&lt;/code&gt;&lt;/strong&gt; (priority 5) runs at exactly 1 kHz using &lt;code&gt;vTaskDelayUntil&lt;/code&gt;. Each tick it grabs the latest tilt, steps the physics, calls the haptic layer, and publishes a telemetry snapshot.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;taskComms&lt;/code&gt;&lt;/strong&gt; (priority 1) handles serial and BLE commands and streams telemetry out at about 58 Hz, with a one-line status dump once a second.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One small but necessary detail: on the C6 the Arduino &lt;code&gt;loopTask&lt;/code&gt; is watched by the task watchdog, and my &lt;code&gt;loop()&lt;/code&gt; just parks on &lt;code&gt;vTaskDelay(portMAX_DELAY)&lt;/code&gt; forever. So I delete the loop task from the watchdog in &lt;code&gt;setup()&lt;/code&gt;, otherwise the board resets itself every ten seconds. Embedded life.&lt;/p&gt;
&lt;p&gt;A single &lt;code&gt;#define HBRIDGE&lt;/code&gt; build flag, set per environment in &lt;code&gt;platformio.ini&lt;/code&gt;, switches all the hardware-specific code. The physics is byte-for-byte identical between the two builds. That was a deliberate goal: I wanted the &lt;strong&gt;same ball&lt;/strong&gt; in both devices, so any difference I feel is the output stage, not the simulation.&lt;/p&gt;
&lt;h2 id=&#34;build-one-rendering-the-ball-as-audio&#34;&gt;Build one: rendering the ball as audio&lt;/h2&gt;
&lt;p&gt;The first build treats the ball as a &lt;strong&gt;sound&lt;/strong&gt;, which turns out to be a very natural fit for haptics. A voice-coil haptic actuator and a small speaker are mechanically the same animal: a coil pushing a mass. If you can synthesize a believable rolling sound, you can feel it.&lt;/p&gt;
&lt;p&gt;















&lt;figure  id=&#34;figure-the-i2s-build-before-assembly-the-blue-board-is-an-adafruit-max98357a-i2s-class-d-amplifier-the-black-board-is-an-adafruit-esp32-c6-feather-the-amplifier-output-goes-to-the-titan-haptics-actuator-instead-of-a-speaker-this-is-as-far-as-this-build-got-physically-validated-on-the-bench-never-boxed-into-an-enclosure&#34;&gt;
  &lt;div class=&#34;d-flex justify-content-center&#34;&gt;
    &lt;div class=&#34;w-100&#34; &gt;&lt;img alt=&#34;On a wooden desk, a small blue Adafruit MAX98357A I2S amplifier breakout with a green screw terminal on the left, and a larger black Adafruit ESP32-C6 Feather board on the right, with two loose header strips above them&#34; srcset=&#34;
               /en/post/esp32-rolling-stone/feather-max98357a_hu_8cec9694ee62d2f.webp 400w,
               /en/post/esp32-rolling-stone/feather-max98357a_hu_3144dfb912ec271.webp 760w,
               /en/post/esp32-rolling-stone/feather-max98357a_hu_97b93c80f4c067db.webp 1200w&#34;
               src=&#34;https://antoine.weill-duflos.fr/en/post/esp32-rolling-stone/feather-max98357a_hu_8cec9694ee62d2f.webp&#34;
               width=&#34;760&#34;
               height=&#34;573&#34;
               loading=&#34;lazy&#34; data-zoomable /&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;figcaption&gt;
      The I2S build, before assembly. The blue board is an Adafruit MAX98357A I2S Class-D amplifier. The black board is an Adafruit ESP32-C6 Feather. The amplifier output goes to the TITAN Haptics actuator instead of a speaker. This is as far as this build got physically: validated on the bench, never boxed into an enclosure.
    &lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;The board is an &lt;strong&gt;Adafruit ESP32-C6 Feather&lt;/strong&gt; and the amplifier is an &lt;strong&gt;Adafruit MAX98357A&lt;/strong&gt; I2S Class-D amp. The BNO085 hangs off the STEMMA QT connector, so there is no soldering for the sensor. The amp&amp;rsquo;s three I2S pins (bit clock, word select, data) come off the Feather&amp;rsquo;s SPI header pins, and the output, normally going to a speaker, goes instead to the actuator.&lt;/p&gt;
&lt;h3 id=&#34;bill-of-materials-i2s-build&#34;&gt;Bill of materials (I2S build)&lt;/h3&gt;
&lt;p&gt;Everything here is off-the-shelf. Prices are rough and from early 2026.&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Part&lt;/th&gt;
          &lt;th&gt;Full reference&lt;/th&gt;
          &lt;th&gt;Where to buy&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;MCU board&lt;/td&gt;
          &lt;td&gt;Adafruit ESP32-C6 Feather, STEMMA QT. Adafruit PID 5933, module ESP32-C6-MINI-1&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://www.adafruit.com/product/5933&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;adafruit.com/product/5933&lt;/a&gt;, about 15 USD. Also stocked by Digikey and Mouser under Adafruit 5933&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;I2S amplifier&lt;/td&gt;
          &lt;td&gt;Adafruit MAX98357A I2S 3W Class-D amplifier breakout. Adafruit PID 3006, chip Analog Devices MAX98357A&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://www.adafruit.com/product/3006&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;adafruit.com/product/3006&lt;/a&gt;, about 6 USD. Bare chip is MAX98357AETE+T at Digikey and RS&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;IMU&lt;/td&gt;
          &lt;td&gt;Adafruit BNO085 9-DOF IMU, STEMMA QT. Adafruit PID 4754, sensor CEVA Hillcrest BNO085&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://www.adafruit.com/product/4754&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;adafruit.com/product/4754&lt;/a&gt;, about 20 USD&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;Haptic actuator&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;TITAN Haptics TacHammer Drake LFi&lt;/strong&gt; (the impact-tuned variant of their wideband LMR voice-coil actuator; this is the part that makes the whole thing feel real, see the shoutout below)&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://titanhaptics.com/drake/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;titanhaptics.com/drake&lt;/a&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;STEMMA QT cable&lt;/td&gt;
          &lt;td&gt;JST-SH 4-pin Qwiic / STEMMA QT cable, 50 mm. Adafruit PID 4399&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://www.adafruit.com/product/4399&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;adafruit.com/product/4399&lt;/a&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Power&lt;/td&gt;
          &lt;td&gt;3.7 V LiPo with JST-PH 2-pin (the Feather charges it onboard), plus a USB-C cable&lt;/td&gt;
          &lt;td&gt;any LiPo distributor&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Enclosure&lt;/td&gt;
          &lt;td&gt;3D-printed shell, a strip of acoustic foam, kapton tape&lt;/td&gt;
          &lt;td&gt;self-sourced&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The Feather has onboard LiPo charging and a STEMMA QT port, so the IMU needs no soldering and the only wiring is the three I2S lines plus power and ground to the amplifier.&lt;/p&gt;
&lt;h3 id=&#34;synthesizing-the-rolling-sound&#34;&gt;Synthesizing the rolling sound&lt;/h3&gt;
&lt;p&gt;The rolling vibration is a &lt;strong&gt;position-indexed wavetable&lt;/strong&gt;. The table is one period of a negative sine arch, 30 samples long, and I index into it with the ball&amp;rsquo;s position in millimetres modulo 30. That sounds like a strange way to make a sound, but it has a beautiful property: because the table is indexed by &lt;em&gt;position&lt;/em&gt;, not by time, the pitch you hear automatically rises with the ball&amp;rsquo;s speed. Move fast, sweep through the table fast, higher pitch. Slow down, lower pitch. It falls out of the geometry for free, and it matches the pitch-tracks-velocity behaviour from the original paper. The amplitude scales with speed too, and below a small speed threshold I silence it entirely so a resting ball is silent.&lt;/p&gt;
&lt;p&gt;The impact is separate: a short rectangular burst whose amplitude scales with the impact speed and whose duration stretches from 2 ms for a tap up to 9 ms for a hard slam.&lt;/p&gt;
&lt;h3 id=&#34;feeding-i2s-at-exactly-22050-hz-from-a-1-khz-loop&#34;&gt;Feeding I2S at exactly 22050 Hz from a 1 kHz loop&lt;/h3&gt;
&lt;p&gt;Here is the part I am quietly proud of. The physics runs at 1 kHz, but the audio needs to come out at 22050 Hz. That is 22.05 samples per physics tick, which is not an integer. If I just wrote 22 samples every tick I would be running the audio slightly slow (22000 Hz), and the pitch would be subtly wrong forever.&lt;/p&gt;
&lt;p&gt;So I keep a fractional accumulator. Every tick I add 0.05 to it, and write 22 samples, except that whenever the accumulator crosses 1.0 I write 23 samples instead and subtract 1.0. Over 1000 ticks that is 950 ticks of 22 plus 50 ticks of 23, which is exactly 22050 samples per second. The long-run sample rate is dead on, with no drift, and the &lt;code&gt;i2s_channel_write&lt;/code&gt; call into the DMA buffer naturally paces the loop. Cheap, exact, and it just works.&lt;/p&gt;
&lt;p&gt;The Feather build also has a NeoPixel that doubles as a status light: dim white while booting, red and stuck if the IMU is not found, green once it is running.&lt;/p&gt;
&lt;h2 id=&#34;the-titan-haptics-actuator-or-what-made-this-so-simple-to-build&#34;&gt;The TITAN Haptics actuator, or what made this so simple to build&lt;/h2&gt;
&lt;p&gt;A speaker will let you &lt;em&gt;hear&lt;/em&gt; the ball, but to &lt;em&gt;feel&lt;/em&gt; it properly you want a purpose-built haptic actuator. This is exactly where the original work had it hard: in 2006, Yao and Hayward had to &lt;strong&gt;build their own custom actuator&lt;/strong&gt; to get a vibration with a clean enough impact for the illusion to land. Twenty years later, I did not have to. I got in touch with &lt;strong&gt;TITAN Haptics&lt;/strong&gt;, and it turns out they make exactly the right thing: the &lt;strong&gt;TacHammer Drake LFi&lt;/strong&gt;, the impact-tuned variant of their wideband voice-coil actuator. It is specifically good at rendering &lt;strong&gt;impacts&lt;/strong&gt;, sharp discrete taps rather than just a buzz, which is exactly what the rolling stone needs every time the ball hits a wall, and it is what makes this build so simple and so effective.&lt;/p&gt;
&lt;p&gt;So I am using a &lt;strong&gt;TacHammer Drake LFi&lt;/strong&gt; (pictured at the top of this post), driven straight off the I2S amplifier output. It is an LMR voice-coil actuator with an ultrawide operating range (TITAN quotes roughly 5 to 300 Hz and a 19 G peak), so the same signal that would make a sound makes a feeling, and it produces a crisp impact on demand. The lovely part: TITAN&amp;rsquo;s own &lt;a href=&#34;https://titanhaptics.com/drake/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;spec sheet for the Drake&lt;/a&gt; lists both Class-D audio amplifiers and H-bridge motor drivers among the compatible ways to drive it, which is precisely the two builds in this series. This is the single part that takes the project from &amp;ldquo;neat signal-processing demo&amp;rdquo; to &amp;ldquo;wait, I can actually feel the ball,&amp;rdquo; so it has earned its own line in the bill of materials above and its own section here.&lt;/p&gt;
&lt;p&gt;A big, warm shout-out to my friend &lt;strong&gt;Ashley Huffman&lt;/strong&gt; and the team at &lt;strong&gt;TITAN Haptics&lt;/strong&gt;. Ashley is genuinely a force in this field: she hosts the &lt;a href=&#34;https://thehapticsclub.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Haptics Club&lt;/a&gt; podcast and writes the &lt;a href=&#34;https://haptics.substack.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;All Things Haptics&lt;/a&gt; newsletter. She got excited about my idea and helped me get the hardware fast to get this running. Ash, you da queen! It is a real pleasure to build on hardware made by people who genuinely care about touch, and an even bigger pleasure when one of them is a friend.&lt;/p&gt;
&lt;h2 id=&#34;seeing-the-ball-the-companion-app&#34;&gt;Seeing the ball: the companion app&lt;/h2&gt;
&lt;p&gt;Because the whole point is a thing you cannot see, I wrote a Python companion app that connects over USB serial or BLE and draws what the firmware is feeling. It is the tool I actually use to tune the physics.&lt;/p&gt;
&lt;p&gt;















&lt;figure  id=&#34;figure-the-companion-app-in-rolling-mode-live-over-usb-the-tilted-tube-and-ball-on-the-left-position-and-velocity-history-on-the-right-and-the-live-telemetry-panel-at-the-bottom-the-board-reports-a-steady-1000-hz-physics-loop&#34;&gt;
  &lt;div class=&#34;d-flex justify-content-center&#34;&gt;
    &lt;div class=&#34;w-100&#34; &gt;&lt;img alt=&#34;Screenshot of the companion app showing a dark UI titled Virtual Rolling Stone, ESP32-C6, Yao and Hayward Eurohaptics 2006. A grey tube is tilted down to the left at minus 6.9 degrees with an orange ball partway along it and a red flash on the left wall. On the right, a position history trace shows a triangle wave and a velocity trace shows the ball oscillating. A panel reads mode ROLLING, cavity 1000 mm, position 548 mm, velocity plus 1.4 m per s, physics 1000 of 1000 Hz&#34; srcset=&#34;
               /en/post/esp32-rolling-stone/viz-rolling_hu_13e405a38069a73f.webp 400w,
               /en/post/esp32-rolling-stone/viz-rolling_hu_ca995601256eb4c5.webp 760w,
               /en/post/esp32-rolling-stone/viz-rolling_hu_d354f1c1785d795c.webp 1200w&#34;
               src=&#34;https://antoine.weill-duflos.fr/en/post/esp32-rolling-stone/viz-rolling_hu_13e405a38069a73f.webp&#34;
               width=&#34;760&#34;
               height=&#34;485&#34;
               loading=&#34;lazy&#34; data-zoomable /&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;figcaption&gt;
      The companion app in rolling mode, live over USB. The tilted tube and ball on the left, position and velocity history on the right, and the live telemetry panel at the bottom. The board reports a steady 1000 Hz physics loop.
    &lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;It shows the tilted tube with the ball animated at its real position, coloured by speed, flashing the wall on impact. On the right are scrolling position and velocity traces. The bottom panel is the live telemetry: mode, cavity length, position, velocity, measured physics rate. It can also replay the rolling-sound synthesis through your computer speakers using the exact same wavetable model as the firmware, which is a quick way to A/B the feel without flashing the board.&lt;/p&gt;
&lt;p&gt;Switching to marble mode flips the main view to a 3D box:&lt;/p&gt;
&lt;p&gt;















&lt;figure  id=&#34;figure-marble-mode-a-3d-box-with-three-marbles-of-different-mass-and-radius-the-live-gravity-vector-drawn-as-an-arrow-and-the-per-axis-position-history-the-panel-shows-the-raw-accelerometer-in-g-and-the-current-impact-energy&#34;&gt;
  &lt;div class=&#34;d-flex justify-content-center&#34;&gt;
    &lt;div class=&#34;w-100&#34; &gt;&lt;img alt=&#34;Screenshot of the companion app in marbles mode showing a 3D red box tilted in space with a gravity arrow and small marbles inside, labelled heavy 1.5 mm, medium 1.0 mm, light 0.7 mm, with X and Y position history traces on the right and a panel showing accelerometer readings in g and an impact value&#34; srcset=&#34;
               /en/post/esp32-rolling-stone/viz-marbles_hu_4df3ff5384fa7777.webp 400w,
               /en/post/esp32-rolling-stone/viz-marbles_hu_c2181fac9f4bf17.webp 760w,
               /en/post/esp32-rolling-stone/viz-marbles_hu_d9e0560a205920ca.webp 1200w&#34;
               src=&#34;https://antoine.weill-duflos.fr/en/post/esp32-rolling-stone/viz-marbles_hu_4df3ff5384fa7777.webp&#34;
               width=&#34;760&#34;
               height=&#34;485&#34;
               loading=&#34;lazy&#34; data-zoomable /&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;figcaption&gt;
      Marble mode. A 3D box with three marbles of different mass and radius, the live gravity vector drawn as an arrow, and the per-axis position history. The panel shows the raw accelerometer in g and the current impact energy.
    &lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;The 3D box, the gravity arrow tracking how I hold the device, the three marbles of different mass: all of it is live data coming off the board at 58 Hz over the serial link. It is a small thing, but watching the simulation and feeling it in your hand at the same time is what makes the tuning loop fast.&lt;/p&gt;
&lt;h2 id=&#34;where-this-is-at&#34;&gt;Where this is at&lt;/h2&gt;
&lt;p&gt;Working today: the physics, the firmware for both output paths, the companion app, and BLE and serial telemetry. This audio path I validated on the bench, with the Feather, the amplifier, and the TITAN Haptics actuator wired up loose. I never closed it into a finished enclosure. The unit I actually assembled, foam and kapton and all, is the H-bridge build in &lt;a href=&#34;../esp32-rolling-stone-hbridge/&#34;&gt;Part 2&lt;/a&gt;, because the all-in-one board made for a smaller package. So the audio build is a working approach more than a finished object, which is the honest state of it.&lt;/p&gt;
&lt;p&gt;What is next, roughly: a &lt;strong&gt;longer tube&lt;/strong&gt;. The device stays a handheld object, but the original illusion lives in the sense of length, and a longer body gives the virtual ball more room to run and makes the illusion more convincing. After that, an &lt;strong&gt;onboard battery&lt;/strong&gt; so it is untethered, and a small user test in the spirit of the original paper to see whether people can read the virtual tube length from my version of the illusion.&lt;/p&gt;
&lt;p&gt;There is one direction I am especially excited about. At that same Vincent Hayward workshop, one of the threads was &lt;a href=&#34;https://cim.mcgill.ca/~haptic/pub/LM-ET-AL-NAT-18.pdf&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Miller et al., &lt;em&gt;Sensing with tools extends somatosensory processing beyond the body&lt;/em&gt;, Nature 2018&lt;/a&gt;: when you hold a rod and something strikes it, your nervous system can tell &lt;em&gt;where&lt;/em&gt; along the rod the impact landed, purely from the vibration that travels up the tool. The rolling stone already synthesizes an impact when the virtual ball hits a wall. The natural next step is to synthesize a &lt;em&gt;localizable&lt;/em&gt; impact, shaping the transient so you feel not just that the ball hit, but where along the tube it hit. That would turn the device from a length illusion into a position-along-a-tool illusion, which is exactly the kind of thing this line of work was reaching toward.&lt;/p&gt;
&lt;p&gt;The code is on GitHub at &lt;a href=&#34;https://github.com/Leicas/esp32-ball&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;github.com/Leicas/esp32-ball&lt;/a&gt;. &lt;a href=&#34;../esp32-rolling-stone-hbridge/&#34;&gt;Part 2&lt;/a&gt; takes the same ball and drives it through an H-bridge instead, on a board with no amplifier at all, which is a completely different set of trade-offs.&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
