diff --git a/Makefile b/Makefile index 5fa87af..82ce300 100644 --- a/Makefile +++ b/Makefile @@ -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 @@ -92,7 +119,7 @@ include $(COMMONRULES) .s.o: $(AS) -Wa,-Iasm -o $@ $< -build/%.o: %.c +build/%.o: %.c @mkdir -p $(@D) $(CC) $(CFLAGS) -MM $^ -MF "$(@:.o=.d)" -MT"$@" $(CC) $(CFLAGS) -c -o $@ $< @@ -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) @@ -508,7 +537,8 @@ $(BASE_TARGET_NAME)_debug.z64: $(CODESEGMENT)_debug.o $(OBJECTS) $(DATA_OBJECTS) clean: rm -rf build rm -rf portal_pak_dir - rm -rf portal_pak_modified + 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 diff --git a/src/levels/cutscene_runner.c b/src/levels/cutscene_runner.c index 2fda2e0..9fa7f5f 100644 --- a/src/levels/cutscene_runner.c +++ b/src/levels/cutscene_runner.c @@ -6,6 +6,7 @@ #include "../levels/levels.h" #include "../util/memory.h" #include "../savefile/checkpoint.h" +#include "../locales/locales.h" #include @@ -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; } diff --git a/src/locales/locales.c b/src/locales/locales.c new file mode 100644 index 0000000..4cd8fc9 --- /dev/null +++ b/src/locales/locales.c @@ -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; +} diff --git a/src/locales/locales.h b/src/locales/locales.h new file mode 100644 index 0000000..b23ef8e --- /dev/null +++ b/src/locales/locales.h @@ -0,0 +1,2 @@ +int getAudioLanguage(); +int mapLocaleSound(int soundId); diff --git a/src/menu/audio_options.c b/src/menu/audio_options.c index 49f70fb..1ae713f 100644 --- a/src/menu/audio_options.c +++ b/src/menu/audio_options.c @@ -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]); } \ No newline at end of file diff --git a/src/menu/audio_options.h b/src/menu/audio_options.h index 45e34c0..b1c46b1 100644 --- a/src/menu/audio_options.h +++ b/src/menu/audio_options.h @@ -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; }; @@ -24,4 +28,4 @@ void audioOptionsInit(struct AudioOptions* audioOptions); enum MenuDirection audioOptionsUpdate(struct AudioOptions* audioOptions); void audioOptionsRender(struct AudioOptions* audioOptions, struct RenderState* renderState, struct GraphicsTask* task); -#endif \ No newline at end of file +#endif diff --git a/src/savefile/savefile.c b/src/savefile/savefile.c index 4b72764..2a97aa5 100755 --- a/src/savefile/savefile.c +++ b/src/savefile/savefile.c @@ -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); } diff --git a/src/savefile/savefile.h b/src/savefile/savefile.h index fa2bdf4..deb2029 100755 --- a/src/savefile/savefile.h +++ b/src/savefile/savefile.h @@ -57,6 +57,7 @@ struct ControlSaveState { struct AudioSettingsSaveState { unsigned char soundVolume; unsigned char musicVolume; + unsigned char audioLanguage; }; #define NO_TEST_CHAMBER 0xFF diff --git a/tools/generate_sound_ids.js b/tools/generate_sound_ids.js index cd47c4c..a7d5e7f 100644 --- a/tools/generate_sound_ids.js +++ b/tools/generate_sound_ids.js @@ -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}`; } @@ -44,4 +59,78 @@ ${soundFilenames.map(formatSoundName).join('\n')} #endif` } -fs.writeFileSync(output, formatFile(output, inputs)); \ No newline at end of file +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());