Add playback pitch control and fading
Huns Valen
Similar to https://feedback.secondlife.com/feature-requests/p/change-sound-pitch but with more details that would greatly increase usability.
I would use this for realistic aircraft engine sounds, instead of having to upload 11 distinct engine sounds (throttle 0 through 100, step 10) I could upload one or a few and use this to set the exact correct pitch. A throttle setting of e.g. 35 doesn't have to use the sound for 30, I can use finer gradations at lower throttle settings for higher precision, etc.
This is not just about vehicles. Anything a game designer would expect from UnrealEngine, Unity, etc. should be possible here as long as it doesn't require spatial audio or output chain effects (awareness of obstacles, EQ/reverb, etc.)
ROI:
- I don't have to upload 10 sounds to get 10 engine pitches, and you don't have to pay to store or stream them. The client's CPU does the work instead of your opex.
- You enable MUCH richer audio experiences that can give people a very large number of reasons to come back. Right now, SL is full of beautiful places that there's no reason to visit more than once because they are _not interactive at all._ The more ways you give us to do "game engine"-style stuff relevant in _this_ decade, the more acquisition and retention we can help you drive.
Some use cases would be:
- Vehicles (obviously)
- Musical instruments (you could do a full sequencer with this), whether preprogrammed, manual, or algorithmic/generative
- Interactive games/toys/art installations
- Mutable/frangible objects that respond to stress/tension/compression
- Various user interfaces (HUDs, kiosks, etc.)
- Critters
- Weapons
- Weather (beyond just the wind)
- Industrial machinery (motors, turbines, pumps, etc) that respond to conditions
- Anything else where a creator would want to go beyond "play it or loop it".
API:
- integer handle = llPlaySoundEffect(list attributes); // >= 0 on success, <0 = error code
- integer llAdjustSoundEffect(integer handle, list attributes); // 1 = success, <0 = error code
- integer llStopSoundEffect(integer handle); // 1 = success, 0 = fail
Attributes is a strided list, just like llSetPrimitiveParams() etc. Some particulars were selected to make them easy to integrate with OpenAL.
Basics:
- LINK_NUM, integer: Which prim in the linkset. Optional. Default current.
- SFX_SOUND, string|key: Same as llPlaySound(). Required.
- SFX_VOLUME, float: Same as llPlaySound(). Required.
Time:
- SFX_START_TIME, float: Moves the playhead to this many seconds into the sample at the beginning of playback. Optional, default 0.
- SFX_END_TIME, float: Can be used to shorten the sample at runtime. Optional, default full sample length. If <= SFX_START_TIME, throw an error
- SFX_SET_TIME, float: Immediately moves the playhead to a particular time index in a sound already playing. Optional. Does not account for playback speed. Setting time before SFX_START_TIME or after SFX_END_TIME throws an error. Does not modify playback direction (so if it's in SFX_PINGPONG [below], playback will keep going in whatever direction it was before.)
Speed: (This just makes the sample play faster or slower, inherently causing a pitch change, which is the main point for any game developer. It doesn't Paul-strech or pitch-correct it.)
- SFX_SPEED_START, float: Adjusts playback speed multiplier at start of playback. <1 = slower, >1 = faster. Optional, default = 1.
- SFX_SPEED_END, float: Sets target playback speed multiplier. Optional, default = 1. When SFX_SPEED_END != SFX_SPEED_START, the playback speed will be linear-interpolated (ramped) between SFX_SPEED_START and SFX_SPEED_END.
- SFX_SPEED_ADJUST, float: Adjusts the current playback speed multiplier of a sound already playing. If there's an active ramp (lerp) between SFX_SPEED_START and SFX_SPEED_END, it's cancelled.
- The ramp occurs between SFX_START_TIME (default 0) and SFX_END_TIME (default end of clip).
- The ramp will restart from SFX_SPEED_START if in SFX_LOOP (see below).
- The ramp will reverse direction along with playback direction if in SFX_PINGPONG (see below).
Loop/pingpong (mutually-exclusive, error if you try to set more than one):
- SFX_ONE_SHOT, boolean: Whether the sound should play once, and then stop. Optional. Default TRUE. This is only needed for adjusting a sound that's already playing in LOOP or PINGPONG, and will have the effect of canceling playback as soon as the playhead reaches either the start or end time, depending on whether it's in pingpong/loop.
- SFX_LOOP, boolean: Whether the sound should be played in a loop, like llLoopSound() except that it respects start/end time offsets.
- SFX_PINGPONG, boolean: Whether the sound should ping-pong (play forward from SFX_START_TIME until reaching SFX_END_TIME, then backwards to SFX_START_TIME, then again forwards, etc., until the sound is stopped.
Log In
Nexii Malthus
Note it might be good to come up with a modern SLua API as well as implementation alternative of how it could look like in a modern language with rich data structures instead of the list-based llFunction coding style that we are heavily used to.
Huns Valen
Examples:
// Minimum, just play a sound.
integer handle = llPlaySoundEffect([
SFX_SOUND, "door_click",
SFX_VOLUME, 1.0
]);
// Set up an engine sound.
float throttle = 0.35; // 0–1
integer handle = llPlaySoundEffect([
SFX_SOUND, "jet_engine_loop",
SFX_VOLUME, 1.0,
SFX_LOOP, TRUE,
SFX_SPEED_START, 0.6 + throttle * 0.8
]);
// Modulate engine sound at runtime.
llAdjustSoundEffect(handle, [
SFX_SPEED_ADJUST, 0.6 + throttle * 0.8
]);
// Weapon charge-up sound.
integer handle = llPlaySoundEffect([
SFX_SOUND, "energy_charge",
SFX_VOLUME, 1.0,
SFX_SPEED_START, 0.5,
SFX_SPEED_END, 1.6
]);
// You have a "sound sprite" with a bunch of different hit sounds. You want to play one in particular.
// One asset load => many distinct sounds.
integer handle = llPlaySoundEffect([
SFX_SOUND, "metal_impacts",
SFX_VOLUME, 1.0,
SFX_START_TIME, 1.8,
SFX_END_TIME, 2.6
]);
// Motor sound that ping-pongs back and forth in a loop.
integer handle = llPlaySoundEffect([
SFX_SOUND, "servo_loop",
SFX_VOLUME, 0.7,
SFX_START_TIME, 0.2,
SFX_END_TIME, 1.4,
SFX_PINGPONG, TRUE
]);
// Musical instrument.
float pitch = 1.25;
integer handle = llPlaySoundEffect([
SFX_SOUND, "piano_note",
SFX_VOLUME, 1.0,
SFX_SPEED_START, pitch
]);
// DJ looper lets you jump 3.5 seconds into a loop. (Assumes handle is already playing.)
llAdjustSoundEffect(handle, [
SFX_SET_TIME, 3.5
]);
// Stop the sound.
llStopSoundEffect(handle);