Skip to content

Commit

Permalink
feat: new audio-specific fade timings
Browse files Browse the repository at this point in the history
Also, better audio docs
  • Loading branch information
liana-p committed Jul 25, 2023
1 parent fe2be9f commit e43f1f4
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 22 deletions.
74 changes: 74 additions & 0 deletions docs/features/audio.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,77 @@ wait 100
talk character idle "Suddenly, something happened!"
resume music # resume the music
```

## Title Screen Music

A music can play on the title screen, by adding the `defaultMusic` option in the audio config:

```yaml
files:
music:
loop: true
src: music/music.mp3
options:
defaultMusic: music
volume: 0.5
musicFadeInTime: 0.5
musicFadeInDelay: 0.5
musicFadeOutTime: 0.5
```

## Audio-specific fade timings

Individual audio files can have fade in/out and fade delays configured, which will override the default ones from the audio option when that specific audio plays:

```yaml
files:
music:
loop: true
src: music/music.mp3
fadeInTime: 2
fadeOutTime: 2
fadeInDelay: 2
```

## Audio triggers

Audio triggers allow specifying a sound effect in the config that will be played when a specific event happens. There are a few audio triggers available in narrat.

Simply add the ones you want to use to the config. For example:

```yaml
files:
click:
src: audio/click.ogg
game_start:
volume: 0.9
src: audio/game_start.ogg
failure:
src: audio/failure.ogg
success:
src: audio/success.wav
audioTriggers:
onPlayerAnswered: "click",
onPressStart: "game_start",
onSkillCheckFailure: "failure",
onSkillCheckSuccess: "success"
onButtonClicked: click
onSpriteClicked: click
onItemUsed: click
```

## Audio Volume mixing

All volumes are between 0 and 1.

The volume a specific audio file is playing at is calculated by multiplying the following volumes together:

- Master Volume (The one in `options.volume` in the audio config file, which players can also edit in the system menu)
- Channel Volume (Starts at 1, players can edit them in the system menu)
- Audio file volume: The volume specified in the audio config file for that specific audio file, if it exists. Otherwise it's 1.

So for example:

If I set master volume to 1, and music volume has been set to 0.5, and a specific sound effect has its volume set at 0.5, then the sound effect will play at 0.25 volume.
3 changes: 3 additions & 0 deletions packages/narrat/examples/games/default/data/audio.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ files:
calm:
loop: true
src: music/music.mp3
fadeInTime: 2
fadeOutTime: 2
volume: 0.3
battle:
loop: true
src: music/battle.mp3
Expand Down
18 changes: 9 additions & 9 deletions packages/narrat/src/components/StartMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,12 @@ import { CustomStartMenuButton } from '@/exports/plugins';
import ModalWindow from './utils/modal-window.vue';
import { InputListener, useInputs } from '@/stores/inputs-store';
import { useNavigation } from '@/inputs/useNavigation';
import { getAudio, stopHowlerById } from '@/utils/audio-loader';
import {
getAudio,
stopHowlerById,
standalonePlayMusic,
standaloneStopMusic,
} from '@/utils/audio-loader';
const inputListener = ref<InputListener | null>(
useInputs().registerInputListener('start-menu', {}),
Expand Down Expand Up @@ -204,10 +209,6 @@ function chosenSave({ slotId }: ChosenSlot) {
}
onMounted(() => {
const config = getConfig();
if (config.audio.options.defaultMusic) {
useAudio().playChannel('music', config.audio.options.defaultMusic, 0);
}
const save = getSaveFile();
if (save.slots.some((slot) => slot.saveData)) {
hasSave.value = true;
Expand All @@ -233,8 +234,7 @@ onMounted(() => {
navigation.select(0);
});
if (audioConfig().options.defaultMusic) {
const music = getAudio(audioConfig().options.defaultMusic!)!;
musicId.value = music.play();
musicId.value = standalonePlayMusic(audioConfig().options.defaultMusic!);
}
});
Expand Down Expand Up @@ -312,8 +312,8 @@ onUnmounted(() => {
inputEvents.off('debouncedKeydown', listener.value as any);
}
if (typeof musicId.value === 'number') {
const defaultMusic = audioConfig().options.defaultMusic!;
stopHowlerById(defaultMusic, musicId.value);
const music = audioConfig().options.defaultMusic!;
standaloneStopMusic(music, musicId.value);
}
});
Expand Down
15 changes: 15 additions & 0 deletions packages/narrat/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,21 @@ export function audioFileConfig(key: string) {
}
return res;
}
export function getAudioFadeTimings(audio: string) {
const audioConf = audioFileConfig(audio);
const fadeInDelay =
(audioConf?.fadeInDelay ?? audioConfig().options.musicFadeInDelay) * 1000;
const fadeInTime =
(audioConf?.fadeInTime ?? audioConfig().options.musicFadeInTime) * 1000;
const fadeOutTime =
(audioConf?.fadeOutTime ?? audioConfig().options.musicFadeOutTime) * 1000;
return {
fadeInDelay,
fadeInTime,
fadeOutTime,
};
}

export function skillsConfig() {
return getConfig().skills;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ main:
// jump test_new_inputs
// jump test_arrays
// set_screen map 0 fade 1000
jump test_audio
// jump test_audio
set_screen map
// set_screen video
talk player idle "Hello"
Expand Down
23 changes: 12 additions & 11 deletions packages/narrat/src/stores/audio-store.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { audioConfig } from '@/config';
import { audioConfig, audioFileConfig, getAudioFadeTimings } from '@/config';
import { getAudio, stopHowlerById } from '@/utils/audio-loader';
import { deepCopy } from '@/utils/data-helpers';
import { error } from '@/utils/error-handling';
Expand Down Expand Up @@ -76,18 +76,19 @@ export const useAudio = defineStore('audio', {
if (!audioChannel) {
return;
}
const { fadeOutTime } = getAudioFadeTimings(audioChannel.audio);
this.setAudioChannel(mode, channelIndex, null);
if (audioConfig().options.musicFadeOutTime) {
const audio = getAudio(audioChannel.audio);
if (audio) {
audio.fade(
audio.volume(audioChannel.howlerId) as number,
0,
audioConfig().options.musicFadeOutTime * 1000,
fadeOutTime,
audioChannel.howlerId,
);
}
await timeout(audioConfig().options.musicFadeOutTime * 1000);
await timeout(fadeOutTime);
}
this.actuallyStopChannel(audioChannel);
},
Expand Down Expand Up @@ -170,6 +171,8 @@ export const useAudio = defineStore('audio', {
error(`Could not find audio ${audio}`);
return;
}
const { fadeInTime, fadeInDelay } = getAudioFadeTimings(audio);
const volume = this.audioVolume(mode, audio);
if (mode !== 'sound') {
const newId = newAudio.play();
newAudio.volume(0, newId);
Expand All @@ -178,22 +181,17 @@ export const useAudio = defineStore('audio', {
audio,
howlerId: newId,
});
await timeout(audioConfig().options.musicFadeInDelay * 1000);
await timeout(fadeInDelay);
// We're checking the the audio hasn't been changed again in the background before playing the audio
const currentValue = this.getAudioChannel(mode, channelIndex);
if (currentValue !== null && currentValue.audio === audio) {
newAudio.play(newId);
// newAudio.volume(0.5, newId);
newAudio.fade(
0,
this.modeVolume(mode) * newAudio.volume(),
audioConfig().options.musicFadeInTime * 1000,
newId,
);
newAudio.fade(0, volume, fadeInTime, newId);
}
} else if (mode === 'sound') {
const newId = newAudio.play();
newAudio.volume(this.modeVolume(mode) * newAudio.volume(), newId);
newAudio.volume(volume, newId);
this.setAudioChannel(mode, channelIndex, {
audio,
howlerId: newId,
Expand Down Expand Up @@ -268,6 +266,9 @@ export const useAudio = defineStore('audio', {
modeVolume(mode: AudioModeKey): number {
return this.masterVolume * this.modes.get(mode)!.options.volume;
},
audioVolume(mode: AudioModeKey, audio: string): number {
return this.modeVolume(mode) * (audioFileConfig(audio)?.volume ?? 1);
},
},
});

Expand Down
32 changes: 31 additions & 1 deletion packages/narrat/src/utils/audio-loader.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { getAssetUrl } from '../config';
import {
audioConfig,
audioFileConfig,
getAssetUrl,
getAudioFadeTimings,
} from '../config';
import { Howl, Howler } from 'howler';
import { warning } from './error-handling';
import { logger } from './logger';
import { useAudio } from '@/stores/audio-store';
import { AudioConfig, AudioFileConfig } from '@/config/audio-config';
import { timeout } from './promises';

export const howlerMap: {
[key: string]: Howl;
Expand Down Expand Up @@ -76,3 +82,27 @@ export function stopHowlerById(musicKey: string, howlerId: number) {
export function getAudio(key: string): Howl | undefined {
return howlerMap[key];
}

export function calculateAudioVolume(
channelVolume: number,
audioVolume: number,
) {
return channelVolume * audioVolume;
}
export function standalonePlayMusic(key: string) {
const music = getAudio(key)!;
const { fadeInTime } = getAudioFadeTimings(key);
const id = music.play();
const audioStore = useAudio();
const volume = audioStore.audioVolume('music', key);
music.fade(0, volume, fadeInTime, id);
return id;
}

export async function standaloneStopMusic(key: string, id: number) {
const audio = getAudio(key)!;
const { fadeOutTime } = getAudioFadeTimings(key);
audio.fade(audio.volume(id) as number, 0, fadeOutTime, id);
await timeout(fadeOutTime);
audio.stop(id);
}

0 comments on commit e43f1f4

Please sign in to comment.