Skip to content

Instantly share code, notes, and snippets.

@jrod-disco
Last active October 1, 2024 19:51
Show Gist options
  • Save jrod-disco/26620a54a37d886e489251f23f68d2dd to your computer and use it in GitHub Desktop.
Save jrod-disco/26620a54a37d886e489251f23f68d2dd to your computer and use it in GitHub Desktop.
An attempt at handling sound sprites in Expo
import { Audio } from 'expo-av';
import { AssetLibrary } from '../AssetLibrary';
/**
* Factory function to create sound sprites.
*
* Takes configuration object on init.
*
* Once configured a key can be played like so
* spriteInstance.play('validKeyDefinedInConfig')'
*
* asset - the sound asset
* Eg. AssetLibrary.sounds['nextLevel']
* this sound should contain multiple sounds that will be played
* based on keys defined below
*
* sounds - an object containing one or more sound keys and their start and end time in ms
* Eg. nextLevel:{from:1000,to:1500}
* Note that unlike some other sound sprites we are not sending a duration
* Note that by default the sprite has a 100ms granularity and that will be off by 50%
*
* @param {{asset:sound, sounds{key:{from:number,to:number}}[]}}
*/
export const NewSoundSprite = async (config) => {
let state = {
spritesObject: {},
asset: null,
sndObj: null,
sounds: {
foo: {
start: 0,
duration: 0,
},
},
};
const asset = config.asset;
// If the asset doesn't exist, we out...
if (!asset) {
console.log(
'Sound Sprite: Asset not defined in `AssetLibrary.sounds`',
config.asset
);
return;
}
// Hold some state...
state = { ...state, ...config, asset };
// Create a single sound object for each sound we want to play
// Set its position where we want it
// Store it in state for later use
const { sounds } = config;
for (const key in sounds) {
if (sounds.hasOwnProperty(key)) {
const sound = sounds[key];
const r = await Audio.Sound.createAsync(state.asset, {
shouldPlay: false,
positionMillis: sound.from,
volume: 1,
progressUpdateIntervalMillis: 50,
});
const _onPlaybackStatus = (status) => {
if (status.positionMillis > sound.to || status.didJustFinish) {
r.sound.setPositionAsync(sound.from);
r.sound.stopAsync(); //unloadAsync().catch(() => {});
//console.log('done rewind', status);
} else {
return;
}
};
r.sound.setOnPlaybackStatusUpdate(_onPlaybackStatus);
state.spritesObject[key] = r;
console.log(`${key} loaded`);
}
}
try {
const { sound: soundObject, status } = await Audio.Sound.createAsync(
state.asset,
{
shouldPlay: false,
progressUpdateIntervalMillis: 100,
}
);
state.sndObj = soundObject;
} catch (error) {
console.log('ERROR on CREATE', key);
console.log(error);
}
const play = async (key) => {
if (!SETTINGS.getUseSound()) return; // check to see if we should be playing at all
console.log('sprite play -->', key);
try {
await state.spritesObject[key].sound.setPositionAsync(
state.sounds[key].from
);
} catch (error) {
console.log('ERROR on SETPOSITION', key);
console.log(error);
}
try {
await state.spritesObject[key].sound.playAsync();
} catch (error) {
console.log('ERROR on PLAY', key);
console.log(error);
}
};
// the sound sprite itself has a single play method exposed
// play takes the key (must match key used in config) play('newGameSound')
return { play };
};
import { NewSoundSprite } from '.';
import { AssetLibrary } from '../AssetLibrary';
// Playing a sound sprite we send the name of the sprite and the key for the specific sound we want
// playSoundSprite("yemSprite", "newGame);
export const playSoundSprite = (spriteName, soundName) => {
soundSpritesAsync.then((ssa) => ssa.getAll()[spriteName].play(soundName));
};
export const soundSpritesAsync = (async () => {
const yemSprite = await NewSoundSprite({
asset: AssetLibrary.sounds['yemSprite'],
sounds: {
s10: { from: 0, to: 400 },
s11: { from: 0, to: 400 },
s20: { from: 775, to: 1140 },
s21: { from: 775, to: 1140 },
s30: { from: 1512, to: 1958 },
s31: { from: 1512, to: 1958 },
s40: { from: 2265, to: 2609 },
s41: { from: 2265, to: 2609 },
s50: { from: 3024, to: 3449 },
s51: { from: 3024, to: 3449 },
s60: { from: 3777, to: 4085 },
s61: { from: 3777, to: 4085 },
s70: { from: 4515, to: 4900 },
s71: { from: 4515, to: 4900 },
s80: { from: 5200, to: 5250 },
s81: { from: 5200, to: 5250 },
s90: { from: 6000, to: 6330 },
s91: { from: 6000, to: 6330 },
gameOver: { from: 14850, to: 16900 },
nextLevel: { from: 17950, to: 19500 },
},
});
const funkSprite = await NewSoundSprite({
asset: AssetLibrary.sounds['funkSprite'],
sounds: {
s10: { from: 0, to: 555 },
s11: { from: 0, to: 555 },
s20: { from: 740, to: 1240 },
s21: { from: 740, to: 1240 },
s30: { from: 1490, to: 2025 },
s31: { from: 1490, to: 2025 },
s40: { from: 2040, to: 2800 },
s41: { from: 2040, to: 2800 },
s50: { from: 2990, to: 3540 },
s51: { from: 2990, to: 3540 },
s60: { from: 3790, to: 4330 },
s61: { from: 3790, to: 4330 },
s70: { from: 4490, to: 4990 },
s71: { from: 4490, to: 4990 },
s80: { from: 5240, to: 5750 },
s81: { from: 5240, to: 5750 },
s90: { from: 5990, to: 6490 },
s91: { from: 5990, to: 6490 },
gameOver: { from: 7490, to: 8900 },
nextLevel: { from: 9730, to: 11250 },
},
});
const getAll = () => {
return {
yemSprite,
funkSprite,
};
};
return {
getAll,
};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment