Implement modular multi-audio-language support.

This commit is contained in:
hackgrid 2023-10-14 15:13:13 +02:00
parent 738f3c73fa
commit 44217fb546
9 changed files with 210 additions and 25 deletions

View file

@ -69,21 +69,48 @@ default: english_audio
english_audio: portal_pak_dir
@$(MAKE) buildgame
german_audio: vpk/portal_sound_vo_german_dir.vpk vpk/portal_sound_vo_german_000.vpk portal_pak_dir
vpk -x portal_pak_dir vpk/portal_sound_vo_german_dir.vpk
all_languages: portal_pak_dir german_audio french_audio russian_audio spanish_audio
@$(MAKE) buildgame
german_audio: vpk/portal_sound_vo_german_dir.vpk vpk/portal_sound_vo_german_000.vpk portal_pak_dir
rm -rf portal_pak_dir/locales/de/
vpk -x portal_pak_dir/locales/de/ vpk/portal_sound_vo_german_dir.vpk
cd portal_pak_dir/locales/de/sound/vo/aperture_ai/; ls | xargs -I {} mv {} de_{}
rm -rf assets/locales/de/sound/vo/aperture_ai/
@mkdir -p assets/locales/de/sound/vo/aperture_ai/
cp assets/sound/vo/aperture_ai/*.sox assets/locales/de/sound/vo/aperture_ai/
cd assets/locales/de/sound/vo/aperture_ai/; rm -f ding_off.sox ding_on.sox
cd assets/locales/de/sound/vo/aperture_ai/; ls | xargs -I {} mv {} de_{}
french_audio: vpk/portal_sound_vo_french_dir.vpk vpk/portal_sound_vo_french_000.vpk portal_pak_dir
vpk -x portal_pak_dir vpk/portal_sound_vo_french_dir.vpk
@$(MAKE) buildgame
rm -rf portal_pak_dir/locales/fr/
vpk -x portal_pak_dir/locales/fr/ vpk/portal_sound_vo_french_dir.vpk
cd portal_pak_dir/locales/fr/sound/vo/aperture_ai/; ls | xargs -I {} mv {} fr_{}
rm -rf assets/locales/fr/sound/vo/aperture_ai/
@mkdir -p assets/locales/fr/sound/vo/aperture_ai/
cp assets/sound/vo/aperture_ai/*.sox assets/locales/fr/sound/vo/aperture_ai/
cd assets/locales/fr/sound/vo/aperture_ai/; rm -f ding_off.sox ding_on.sox
cd assets/locales/fr/sound/vo/aperture_ai/; ls | xargs -I {} mv {} fr_{}
russian_audio: vpk/portal_sound_vo_russian_dir.vpk vpk/portal_sound_vo_russian_000.vpk portal_pak_dir
vpk -x portal_pak_dir vpk/portal_sound_vo_russian_dir.vpk
@$(MAKE) buildgame
rm -rf portal_pak_dir/locales/ru/
vpk -x portal_pak_dir/locales/ru/ vpk/portal_sound_vo_russian_dir.vpk
cd portal_pak_dir/locales/ru/sound/vo/aperture_ai/; ls | xargs -I {} mv {} ru_{}
rm -rf assets/locales/ru/sound/vo/aperture_ai/
@mkdir -p assets/locales/ru/sound/vo/aperture_ai/
cp assets/sound/vo/aperture_ai/*.sox assets/locales/ru/sound/vo/aperture_ai/
cd assets/locales/ru/sound/vo/aperture_ai/; rm -f ding_off.sox ding_on.sox
cd assets/locales/ru/sound/vo/aperture_ai/; ls | xargs -I {} mv {} ru_{}
spanish_audio: vpk/portal_sound_vo_spanish_dir.vpk vpk/portal_sound_vo_spanish_000.vpk portal_pak_dir
vpk -x portal_pak_dir vpk/portal_sound_vo_spanish_dir.vpk
@$(MAKE) buildgame
rm -rf portal_pak_dir/locales/es/
vpk -x portal_pak_dir/locales/es/ vpk/portal_sound_vo_spanish_dir.vpk
cd portal_pak_dir/locales/es/sound/vo/aperture_ai/; ls | xargs -I {} mv {} es_{}
rm -rf assets/locales/es/sound/vo/aperture_ai/
@mkdir -p assets/locales/es/sound/vo/aperture_ai/
cp assets/sound/vo/aperture_ai/*.sox assets/locales/es/sound/vo/aperture_ai/
cd assets/locales/es/sound/vo/aperture_ai/; rm -f ding_off.sox ding_on.sox
cd assets/locales/es/sound/vo/aperture_ai/; ls | xargs -I {} mv {} es_{}
buildgame: $(BASE_TARGET_NAME).z64
@ -286,6 +313,7 @@ build/src/decor/decor_object_list.o: build/assets/models/dynamic_model_list.h bu
build/src/effects/effect_definitions.o: build/assets/materials/static.h
build/src/effects/portal_trail.o: build/assets/materials/static.h build/assets/models/portal_gun/ball_trail.h
build/src/levels/level_definition.o: build/src/audio/subtitles.h
build/src/locales/locales.o: build/src/audio/clips.h build/src/audio/languages.h
build/src/menu/controls.o: build/assets/materials/ui.h build/src/audio/clips.h
build/src/menu/game_menu.o: build/src/audio/clips.h build/assets/materials/ui.h build/assets/materials/images.h build/assets/test_chambers/test_chamber_00/test_chamber_00.h
build/src/menu/gameplay_options.o: build/assets/materials/ui.h build/src/audio/clips.h
@ -437,9 +465,9 @@ build/assets/sound/sounds.sounds build/assets/sound/sounds.sounds.tbl: $(SOUND_C
build/asm/sound_data.o: build/assets/sound/sounds.sounds build/assets/sound/sounds.sounds.tbl
build/src/audio/clips.h: tools/generate_sound_ids.js $(SOUND_CLIPS)
build/src/audio/clips.h build/src/audio/languages.h build/src/audio/languages.c: tools/generate_sound_ids.js $(SOUND_CLIPS)
@mkdir -p $(@D)
node tools/generate_sound_ids.js -o $@ -p SOUNDS_ $(SOUND_CLIPS)
node tools/generate_sound_ids.js -o $(@D) -p SOUNDS_ $(SOUND_CLIPS)
build/src/audio/clips.o: build/src/audio/clips.h
build/src/decor/decor_object_list.o: build/src/audio/clips.h
@ -449,7 +477,7 @@ build/src/decor/decor_object_list.o: build/src/audio/clips.h
####################
build/src/audio/subtitles.h build/src/audio/subtitles.c: resource/closecaption_english.txt tools/level_scripts/subtitle_generate.py
@python tools/level_scripts/subtitle_generate.py
@python3 tools/level_scripts/subtitle_generate.py
####################
## Linking
@ -465,7 +493,8 @@ CODEOBJECTS = $(patsubst %.c, build/%.o, $(CODEFILES)) \
build/assets/materials/static_mat.o \
build/assets/materials/ui_mat.o \
build/assets/materials/hud_mat.o \
build/src/audio/subtitles.o
build/src/audio/subtitles.o \
build/src/audio/languages.o
CODEOBJECTS_NO_DEBUG = $(CODEOBJECTS)
@ -509,6 +538,7 @@ clean:
rm -rf build
rm -rf portal_pak_dir
rm -rf portal_pak_modified
rm -rf assets/locales/
clean-src:
rm -rf build/src
@ -521,6 +551,7 @@ clean-src:
clean-assets:
rm -rf build/assets
rm -rf assets/locales/
rm -f $(CODESEGMENT)_debug.o
rm -f $(CODESEGMENT)_no_debug.o
rm -f $(BASE_TARGET_NAME)_debug.elf

View file

@ -6,6 +6,7 @@
#include "../levels/levels.h"
#include "../util/memory.h"
#include "../savefile/checkpoint.h"
#include "../locales/locales.h"
#include <math.h>
@ -142,6 +143,7 @@ void cutsceneRunnerStartStep(struct CutsceneRunner* runner) {
break;
case CutsceneStepTypeQueueSound:
{
step->queueSound.soundId = mapLocaleSound(step->queueSound.soundId);
cutsceneQueueSoundInChannel(step->queueSound.soundId, step->queueSound.volume * (1.0f / 255.0f), step->queueSound.channel, step->queueSound.subtitleId);
break;
}

21
src/locales/locales.c Normal file
View file

@ -0,0 +1,21 @@
#include "locales.h"
#include "../build/src/audio/languages.h"
#include "../savefile/savefile.h"
int getAudioLanguage() {
return (int)gSaveData.audio.audioLanguage;
}
int mapLocaleSound(int soundId) {
int language = getAudioLanguage();
switch(language) {
default:
soundId = AudioLanguageValues[language][soundId];
break;
case AUDIO_LANGUAGE_EN:
break;
}
return soundId;
}

2
src/locales/locales.h Normal file
View file

@ -0,0 +1,2 @@
int getAudioLanguage();
int mapLocaleSound(int soundId);

View file

@ -7,6 +7,7 @@
#include "../build/src/audio/subtitles.h"
#include "../build/assets/materials/ui.h"
#include "../build/src/audio/clips.h"
#include "../build/src/audio/languages.h"
#define GAMEPLAY_Y 54
#define GAMEPLAY_WIDTH 252
@ -15,14 +16,19 @@
#define SCROLL_TICKS (int)maxf(NUM_SUBTITLE_LANGUAGES, 1)
#define SCROLL_INTERVALS (int)maxf((SCROLL_TICKS - 1), 1)
#define SCROLL_CHUNK_SIZE (0x10000 / SCROLL_INTERVALS)
#define SCROLL_TICKS_LANGUAGE (int)maxf(NUM_AUDIO_LANGUAGES, 1)
#define SCROLL_INTERVALS_LANGUAGE (int)maxf((SCROLL_TICKS_LANGUAGE - 1), 1)
#define SCROLL_CHUNK_SIZE_LANGUAGE (0x10000 / SCROLL_INTERVALS_LANGUAGE)
#define FULL_SCROLL_TIME 2.0f
#define SCROLL_MULTIPLIER (int)(0xFFFF * FIXED_DELTA_TIME / (80 * FULL_SCROLL_TIME))
#define SCROLL_CHUNK_SIZE (0x10000 / SCROLL_INTERVALS)
void audioOptionsInit(struct AudioOptions* audioOptions) {
audioOptions->selectedItem = AudioOptionSubtitlesEnabled;
if (NUM_SUBTITLE_LANGUAGES){
if (NUM_SUBTITLE_LANGUAGES) {
audioOptions->subtitlesEnabled = menuBuildCheckbox(&gDejaVuSansFont, "Closed Captions", GAMEPLAY_X + 8, GAMEPLAY_Y + 8);
audioOptions->subtitlesEnabled.checked = (gSaveData.controls.flags & ControlSaveSubtitlesEnabled) != 0;
@ -33,6 +39,15 @@ void audioOptionsInit(struct AudioOptions* audioOptions) {
audioOptions->subtitles_language_temp = (0xFFFF/SCROLL_TICKS)* gSaveData.controls.subtitleLanguage;
audioOptions->subtitlesLanguage.value = (int)gSaveData.controls.subtitleLanguage * (0xFFFF/SCROLL_TICKS);
}
if (NUM_AUDIO_LANGUAGES > 1) {
audioOptions->audioLanguageText = menuBuildText(&gDejaVuSansFont, "Audio Language: ", GAMEPLAY_X + 8, GAMEPLAY_Y + 68);
audioOptions->audioLanguageDynamicText = menuBuildText(&gDejaVuSansFont, AudioLanguages[gSaveData.audio.audioLanguage], GAMEPLAY_X + 150, GAMEPLAY_Y + 68);
audioOptions->audioLanguage= menuBuildSlider(GAMEPLAY_X + 8, GAMEPLAY_Y + 88, 200, SCROLL_TICKS_LANGUAGE);
audioOptions->audio_language_temp = (0xFFFF/SCROLL_TICKS)* gSaveData.audio.audioLanguage;
audioOptions->audioLanguage.value = (int)gSaveData.audio.audioLanguage * (0xFFFF/SCROLL_TICKS_LANGUAGE);
}
}
void audioOptionsHandleSlider(unsigned short* settingValue, float* sliderValue) {
@ -44,14 +59,14 @@ void audioOptionsHandleSlider(unsigned short* settingValue, float* sliderValue)
if (newValue >= 0xFFFF && controllerGetButtonDown(0, A_BUTTON)) {
newValue = 0;
} else {
newValue = newValue + SCROLL_CHUNK_SIZE;
newValue = newValue - (newValue % SCROLL_CHUNK_SIZE);
newValue = newValue + SCROLL_CHUNK_SIZE_LANGUAGE;
newValue = newValue - (newValue % SCROLL_CHUNK_SIZE_LANGUAGE);
}
}
if (controllerGetButtonDown(0, L_JPAD)) {
newValue = newValue - 1;
newValue = newValue - (newValue % SCROLL_CHUNK_SIZE);
newValue = newValue - (newValue % SCROLL_CHUNK_SIZE_LANGUAGE);
}
if (newValue < 0) {
@ -89,7 +104,6 @@ enum MenuDirection audioOptionsUpdate(struct AudioOptions* audioOptions) {
}
}
if (NUM_SUBTITLE_LANGUAGES){
switch (audioOptions->selectedItem) {
case AudioOptionSubtitlesEnabled:
if (controllerGetButtonDown(0, A_BUTTON)) {
@ -110,10 +124,16 @@ enum MenuDirection audioOptionsUpdate(struct AudioOptions* audioOptions) {
gSaveData.controls.subtitleLanguage = temp;
audioOptions->subtitlesLanguageDynamicText = menuBuildText(&gDejaVuSansFont, SubtitleLanguages[gSaveData.controls.subtitleLanguage], GAMEPLAY_X + 150, GAMEPLAY_Y + 28);
break;
}
case AudioOptionAudioLanguage:
audioOptionsHandleSlider(&audioOptions->audio_language_temp, &audioOptions->audioLanguage.value);
int tempAudio = (int)((audioOptions->audio_language_temp * (1.0f/0xFFFF) * NUM_AUDIO_LANGUAGES));
tempAudio = (int)minf(maxf(0.0, tempAudio), NUM_AUDIO_LANGUAGES-1);
gSaveData.audio.audioLanguage = tempAudio;
audioOptions->audioLanguageDynamicText = menuBuildText(&gDejaVuSansFont, AudioLanguages[gSaveData.audio.audioLanguage], GAMEPLAY_X + 150, GAMEPLAY_Y + 68);
break;
}
if (audioOptions->selectedItem == AudioOptionSubtitlesLanguage){
if (audioOptions->selectedItem == AudioOptionSubtitlesLanguage || audioOptions->selectedItem == AudioOptionAudioLanguage){
if ((controllerGetButtonDown(0, L_TRIG) || controllerGetButtonDown(0, Z_TRIG))) {
return MenuDirectionLeft;
}
@ -144,6 +164,11 @@ void audioOptionsRender(struct AudioOptions* audioOptions, struct RenderState* r
renderState->dl = menuSliderRender(&audioOptions->subtitlesLanguage, renderState->dl);
}
if (NUM_AUDIO_LANGUAGES > 1) {
gSPDisplayList(renderState->dl++, audioOptions->audioLanguage.back);
renderState->dl = menuSliderRender(&audioOptions->audioLanguage, renderState->dl);
}
gSPDisplayList(renderState->dl++, ui_material_revert_list[SOLID_ENV_INDEX]);
gSPDisplayList(renderState->dl++, ui_material_list[DEJAVU_SANS_INDEX]);
@ -162,5 +187,14 @@ void audioOptionsRender(struct AudioOptions* audioOptions, struct RenderState* r
gSPDisplayList(renderState->dl++, audioOptions->subtitlesLanguageDynamicText);
}
if (NUM_AUDIO_LANGUAGES > 1) {
gDPPipeSync(renderState->dl++);
menuSetRenderColor(renderState, audioOptions->selectedItem == AudioOptionAudioLanguage, &gSelectionGray, &gColorWhite);
gSPDisplayList(renderState->dl++, audioOptions->audioLanguageText);
gDPPipeSync(renderState->dl++);
menuSetRenderColor(renderState, audioOptions->selectedItem == AudioOptionAudioLanguage, &gSelectionGray, &gColorWhite);
gSPDisplayList(renderState->dl++, audioOptions->audioLanguageDynamicText);
}
gSPDisplayList(renderState->dl++, ui_material_revert_list[DEJAVU_SANS_INDEX]);
}

View file

@ -7,7 +7,7 @@
enum AudioOption {
AudioOptionSubtitlesEnabled,
AudioOptionSubtitlesLanguage,
AudioOptionAudioLanguage,
AudioOptionCount,
};
@ -17,6 +17,10 @@ struct AudioOptions {
Gfx* subtitlesLanguageText;
Gfx* subtitlesLanguageDynamicText;
unsigned short subtitles_language_temp;
struct MenuSlider audioLanguage;
Gfx* audioLanguageText;
Gfx* audioLanguageDynamicText;
unsigned short audio_language_temp;
short selectedItem;
};

View file

@ -99,6 +99,7 @@ void savefileNew() {
gSaveData.audio.soundVolume = 0xFF;
gSaveData.audio.musicVolume = 0xFF;
gSaveData.audio.audioLanguage = 0;
controllerSetDeadzone(gSaveData.controls.deadzone * (1.0f / 0xFFFF) * MAX_DEADZONE);
}

View file

@ -57,6 +57,7 @@ struct ControlSaveState {
struct AudioSettingsSaveState {
unsigned char soundVolume;
unsigned char musicVolume;
unsigned char audioLanguage;
};
#define NO_TEST_CHAMBER 0xFF

View file

@ -7,6 +7,15 @@ let inputs = [];
let definePrefix = '';
let lastCommand = '';
let outputLanguagesSourceFile = '';
let outputLanguagesHeader = '';
let allSounds = [];
let languages = [];
let languages_codes = ["EN", "DE", "FR", "RU", "ES"];
let language_names = {"EN": "English", "DE": "German", "FR": "French", "RU": "Russian", "ES": "Spanish"};
let lookup = [];
languages.push("EN"); // Always included by default
for (let i = 2; i < process.argv.length; ++i) {
const arg = process.argv[i];
if (lastCommand) {
@ -23,6 +32,10 @@ for (let i = 2; i < process.argv.length; ++i) {
}
}
outputLanguagesSourceFile = output + '/languages.c';
outputLanguagesHeader += output + '/languages.h';
output += '/clips.h';
inputs.push('TOTAL_COUNT');
const invalidCharactersRegex = /[^\w\d_]+/gm;
@ -31,6 +44,8 @@ function formatSoundName(soundFilename, index) {
const extension = path.extname(soundFilename);
const lastPart = path.basename(soundFilename, extension);
const defineName = definePrefix + lastPart.replace(invalidCharactersRegex, '_').toUpperCase();
trackSoundLanguages(soundFilename, defineName, index);
return `#define ${defineName} ${index}`;
}
@ -45,3 +60,77 @@ ${soundFilenames.map(formatSoundName).join('\n')}
}
fs.writeFileSync(output, formatFile(output, inputs));
function trackSoundLanguages(soundFilename, defineName, index) {
languages_codes.forEach((language) => {
if (defineName.includes("_" + language + "_") && !languages.includes(language))
languages.push(language);
});
if (soundFilename != "TOTAL_COUNT")
allSounds.push({defineName: defineName, index: index});
}
function fillOverrideLookup() {
for (let language of languages) {
for (let overrideSound of allSounds) {
let baseSound = overrideSound;
let overrideName = overrideSound.defineName;
// check if current sound has base version of default language
if (overrideName.includes('_' + language + '_') && language != "EN") {
baseSound = allSounds.find(element => element.defineName == overrideName.replace('_' + language + '_', '_'));
}
lookup.push({language: language, baseIndex: baseSound.index, index: overrideSound.index, defineName: overrideSound.defineName});
}
}
}
function generateLanguagesHeader() {
let header = `#ifndef __LANGUAGES_H__
#define __LANGUAGES_H__
#define NUM_AUDIO_LANGUAGES ${languages.length}
extern char* AudioLanguages[];
extern int AudioLanguageValues[][${allSounds.length}];
`
header += 'enum AudioLanguagesKey\n{\n';
header += (languages.length > 0 ? '\tAUDIO_LANGUAGE_' + languages.join(',\n\tAUDIO_LANGUAGE_') + ',\n' : '');
header += '};\n';
header += '#endif';
return header;
}
function generateLanguagesSourceFile() {
fillOverrideLookup();
let sourcefile = '#include "languages.h"\n';
sourcefile += '\n';
sourcefile += 'char* AudioLanguages[] = \n{\n';
for (let language of languages) {
sourcefile += '\t"' + language_names[language].toUpperCase() + '",\n';
}
sourcefile += '};\n';
sourcefile += 'int AudioLanguageValues[][' + allSounds.length + '] = \n{\n';
for (let language of languages) {
sourcefile += '\t//' + language + '\n\t{\n';
for (let baseSound of allSounds) {
let overrideSound = lookup.find(lookElement => lookElement.language == language && lookElement.baseIndex == baseSound.index && lookElement.baseIndex != lookElement.index);
if (overrideSound === undefined) overrideSound = baseSound; // no override, use default
sourcefile += '\t' + overrideSound.index + ', // ' + baseSound.defineName + ' ('+baseSound.index+') -> ' + overrideSound.defineName + ' ('+ overrideSound.index+')' + '\n';
}
sourcefile += '\t},\n';
}
sourcefile += '};';
return sourcefile;
}
fs.writeFileSync(outputLanguagesHeader, generateLanguagesHeader());
fs.writeFileSync(outputLanguagesSourceFile, generateLanguagesSourceFile());