Sooo, I tried both methods (Max's and llamazing's). I thought Max's idea was pretty bright but I was getting all kinds of audio buffer errors. It also had a choppy sound. Code was basically this:
function item:_play_sound()
local speed = 390 / self._power
sol.audio.play_sound("alex/vacuum_2")
self._sound_timers[self._power] = sol.timer.start(self, speed, function()
self._sound_timers[self._power] = nil
self:_play_sound()
end)
end
Maybe there was a way to make this work right, but I'm not sure. Anyway I ended up breaking it up into 3 different sounds and writing this monstrous code:
-- Handle repeating sfx
-- FIXME: Make this code not horrible!
-- See: https://gitlab.com/solarus-games/solarus/issues/1289
function item:_play_sound()
if self._power == 1 and not self._sound_timers[1] then
log("sfx start: Vacuum 1")
sol.audio.play_sound("vacuum_1")
self._sound_timers[1] = sol.timer.start(self, 180, function()
self._sound_timers[1] = nil
item:_play_sound()
end)
elseif self._power == 2 and not self._sound_timers[2] then
log("sfx start: Vacuum 2")
sol.audio.play_sound("vacuum_2")
self._sound_timers[2] = sol.timer.start(self, 125, function()
self._sound_timers[2] = nil
item:_play_sound()
end)
elseif self._power == 3 and not self._sound_timers[3] then
log("sfx start: Vacuum 3")
sol.audio.play_sound("vacuum_3")
self._sound_timers[3] = sol.timer.start(self, 125, function()
self._sound_timers[3] = nil
item:_play_sound()
end)
end
end
I may go back and encapsulate the repeating parts into a function, but this was bad code anyway.

I also had to do this when the item command was released, which I don't totally understand.
-- Set all sound timers to nil
-- FIXME: I don't understand why looping through them didn't work
self._sound_timers[1] = nil
self._sound_timers[2] = nil
self._sound_timers[3] = nil
Ultimately, _play_sound() is called whenever the vacuum's power level is changed, so that's nice. It syncs the sound up to the vacuum quite nicely. Each clip is also <200ms so it feels like a pretty immediate stop when you release the button.
Here's the full commit if anyone is interested:
https://gitlab.com/voadi/voadi/commit/497699088b4db58860213ca3acc3577acd1dbd09