Skip to content

Commit

Permalink
Document bard music
Browse files Browse the repository at this point in the history
  • Loading branch information
GriffinRichards committed Oct 25, 2024
1 parent 5d8c4ac commit 882af85
Show file tree
Hide file tree
Showing 31 changed files with 5,937 additions and 5,826 deletions.
4 changes: 2 additions & 2 deletions data/scripts/mauville_man.inc
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ MauvilleCity_PokemonCenter_1F_EventScript_Bard::
end

MauvilleCity_PokemonCenter_1F_EventScript_PlaySong::
setvar VAR_0x8004, 0
setvar VAR_0x8004, FALSE @ Play his current song
special PlayBardSong
delay 60
special HasBardSongBeenChanged
Expand All @@ -45,7 +45,7 @@ MauvilleCity_PokemonCenter_1F_EventScript_WriteLyrics::
faceplayer
goto_if_eq VAR_RESULT, 0, MauvilleCity_PokemonCenter_1F_EventScript_DeclineWritingLyrics
msgbox MauvilleCity_PokemonCenter_1F_Text_LetMeSingItForYou, MSGBOX_DEFAULT
setvar VAR_0x8004, 1
setvar VAR_0x8004, TRUE @ Play the new song
special PlayBardSong
delay 60
msgbox MauvilleCity_PokemonCenter_1F_Text_ThatHowYouWantedSongToGo, MSGBOX_YESNO
Expand Down
54 changes: 31 additions & 23 deletions include/bard_music.h
Original file line number Diff line number Diff line change
@@ -1,41 +1,49 @@
#ifndef GUARD_BARD_MUSIC_H
#define GUARD_BARD_MUSIC_H

#define BARD_SOUND_MAX_LENGTH 6
// The maximum number of BardSoundTemplates/BardSounds there can be for each easy chat word.
#define MAX_BARD_SOUNDS_PER_WORD 6

struct BardSound
// The number of pitch tables there are for each pitch table size (see sPitchTables).
#define NUM_BARD_PITCH_TABLES_PER_SIZE 5

// This struct describes which phoneme song to play for the sound, and whether to
// make any adjustments to its length or volume. Very few sounds make any adjustments.
struct BardSoundTemplate
{
/*0x00*/ u8 songLengthId;
/*0x01*/ s8 songLengthOffset;
/*0x02*/ u16 unused;
/*0x04*/ s16 volume;
/*0x06*/ u16 unused2;
u8 songId;
s8 lengthAdjustment;
u16 unused; // Only set on EC_WORD_WAAAH, and never read.
s16 volume;
};

struct BardPhoneme
// This is the length and pitch to play the phoneme song at.
// These will be calculated in 'CalcWordSounds'.
struct BardSound
{
/*0x00*/ u16 length;
/*0x02*/ u16 pitch;
u16 length;
u16 pitch;
};

struct BardSong
{
/*0x00*/ u8 currWord;
/*0x01*/ u8 currPhoneme;
/*0x02*/ u8 phonemeTimer;
/*0x03*/ u8 state;
/*0x04*/ s16 length;
/*0x06*/ u16 volume;
/*0x08*/ s16 pitch;
/*0x0A*/ s16 voiceInflection;
/*0x0C*/ u16 lyrics[NUM_BARD_SONG_WORDS];
/*0x18*/ struct BardPhoneme phonemes[BARD_SOUND_MAX_LENGTH];
/*0x30*/ const struct BardSound *sound;
u8 lyricsIndex;
u8 soundIndex;
u8 timer;
u8 state;
s16 length; // Length of the sound for the word currently being sung (i.e. the sum of 'length' in all the current word's phonemes).
u16 volume;
s16 pitch;
s16 voiceInflection;
u16 lyrics[NUM_BARD_SONG_WORDS];
struct BardSound sounds[MAX_BARD_SOUNDS_PER_WORD];
const struct BardSoundTemplate *soundTemplates;
};

extern const u16 gNumBardWords_Species;
extern const u16 gNumBardWords_Moves;
const struct BardSound *GetWordSounds(u16 word);
void GetWordPhonemes(struct BardSong *song, u16 word);

const struct BardSoundTemplate *GetWordSoundTemplates(u16 easyChatWord);
void CalcWordSounds(struct BardSong *song, u16 pitchTableIndex);

#endif //GUARD_BARD_MUSIC_H
8 changes: 8 additions & 0 deletions include/constants/songs.h
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,8 @@
#define MUS_RG_SLOW_PALLET 557 // MUS_RG_SLOWMASARA
#define MUS_RG_TEACHY_TV_MENU 558 // MUS_RG_TVNOIZE

// These PH_* constants are phoneme sounds used by the "bard" NPC (see src/bard_music.c and src/mauville_old_man.c).
// Each comes in a triplet of PH_*_BLEND, PH_*_HELD, and PH_*_SOLO, and the name of each triplet incorporates the English phonetic sound it represents.
#define PH_TRAP_BLEND 559
#define PH_TRAP_HELD 560
#define PH_TRAP_SOLO 561
Expand Down Expand Up @@ -545,4 +547,10 @@

#define MUS_NONE 0xFFFF

#define FIRST_PHONEME_SONG PH_TRAP_BLEND
#define LAST_PHONEME_SONG PH_NURSE_SOLO
#define NUM_PHONEME_SONGS (LAST_PHONEME_SONG - FIRST_PHONEME_SONG + 1)
#define PHONEME_ID(song) ((song) - FIRST_PHONEME_SONG)
#define PHONEME_ID_NONE 0xFF

#endif // GUARD_CONSTANTS_SONGS_H
2 changes: 1 addition & 1 deletion include/global.h
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,7 @@ struct MauvilleManBard
/*0x00*/ u8 id;
/*0x01*/ //u8 padding1;
/*0x02*/ u16 songLyrics[NUM_BARD_SONG_WORDS];
/*0x0E*/ u16 temporaryLyrics[NUM_BARD_SONG_WORDS];
/*0x0E*/ u16 newSongLyrics[NUM_BARD_SONG_WORDS];
/*0x1A*/ u8 playerName[PLAYER_NAME_LENGTH + 1];
/*0x22*/ u8 filler_2DB6[0x3];
/*0x25*/ u8 playerTrainerId[TRAINER_ID_LENGTH];
Expand Down
236 changes: 210 additions & 26 deletions src/bard_music.c
Original file line number Diff line number Diff line change
@@ -1,63 +1,247 @@
#include "global.h"
#include "bard_music.h"
#include "easy_chat.h"
#include "constants/songs.h"

#include "data/bard_music/bard_sounds.h"
#include "data/bard_music/word_pitch.h"
#include "data/bard_music/default_sound.h"
#include "data/bard_music/length_table.h"
// Indicates that the previous sound should be held.
#define PREV_BARD_SOUND { .songId = NUM_PHONEME_SONGS }

static s16 CalcWordPitch(int pitchIdx, int songPos)
// Invalid sound, indicates the end of the sounds for the word.
#define NULL_BARD_SOUND { .songId = PHONEME_ID_NONE }

#include "data/bard_music/pokemon.h"
#include "data/bard_music/moves.h"
#include "data/bard_music/trainer.h"
#include "data/bard_music/status.h"
#include "data/bard_music/battle.h"
#include "data/bard_music/greetings.h"
#include "data/bard_music/people.h"
#include "data/bard_music/voices.h"
#include "data/bard_music/speech.h"
#include "data/bard_music/endings.h"
#include "data/bard_music/feelings.h"
#include "data/bard_music/conditions.h"
#include "data/bard_music/actions.h"
#include "data/bard_music/lifestyle.h"
#include "data/bard_music/hobbies.h"
#include "data/bard_music/time.h"
#include "data/bard_music/misc.h"
#include "data/bard_music/adjectives.h"
#include "data/bard_music/events.h"
#include "data/bard_music/trendysaying.h"

static const struct BardSoundTemplate (*const sBardSoundTemplatesTable[EC_NUM_GROUPS])[MAX_BARD_SOUNDS_PER_WORD] = {
[EC_GROUP_POKEMON] = NULL, // Handled by sBardSoundTemplates_Pokemon
[EC_GROUP_TRAINER] = sBardSoundTemplates_Trainer,
[EC_GROUP_STATUS] = sBardSoundTemplates_Status,
[EC_GROUP_BATTLE] = sBardSoundTemplates_Battle,
[EC_GROUP_GREETINGS] = sBardSoundTemplates_Greetings,
[EC_GROUP_PEOPLE] = sBardSoundTemplates_People,
[EC_GROUP_VOICES] = sBardSoundTemplates_Voices,
[EC_GROUP_SPEECH] = sBardSoundTemplates_Speech,
[EC_GROUP_ENDINGS] = sBardSoundTemplates_Endings,
[EC_GROUP_FEELINGS] = sBardSoundTemplates_Feelings,
[EC_GROUP_CONDITIONS] = sBardSoundTemplates_Conditions,
[EC_GROUP_ACTIONS] = sBardSoundTemplates_Actions,
[EC_GROUP_LIFESTYLE] = sBardSoundTemplates_Lifestyle,
[EC_GROUP_HOBBIES] = sBardSoundTemplates_Hobbies,
[EC_GROUP_TIME] = sBardSoundTemplates_Time,
[EC_GROUP_MISC] = sBardSoundTemplates_Misc,
[EC_GROUP_ADJECTIVES] = sBardSoundTemplates_Adjectives,
[EC_GROUP_EVENTS] = sBardSoundTemplates_Events,
[EC_GROUP_MOVE_1] = NULL, // Handled by sBardSoundTemplates_Moves
[EC_GROUP_MOVE_2] = NULL, // Handled by sBardSoundTemplates_Moves
[EC_GROUP_TRENDY_SAYING] = sBardSoundTemplates_TrendySaying,
[EC_GROUP_POKEMON_NATIONAL] = NULL, // Handled by sBardSoundTemplates_Pokemon
};

// The pitch tables below will be indexed using the number of BardSoundTemplates per word, so a table is selected
// that has at least MAX_BARD_SOUNDS_PER_WORD pitch values. Curiously they select pitch tables whose size is +1
// of the maximum number of sounds per word, so the final pitch value (before PITCH_END) isn't used.
// (i.e., 'NUM_BARD_PITCH_TABLES_PER_SIZE * (MAX_BARD_SOUNDS_PER_WORD-1)' would select a sufficiently large table).
#define BASE_PITCH_TABLE_INDEX (NUM_BARD_PITCH_TABLES_PER_SIZE * MAX_BARD_SOUNDS_PER_WORD)

#define PITCH_END 0x1800

static const s16 sPitch1_0[] = { -0x300, PITCH_END };
static const s16 sPitch1_1[] = { 0x0900, PITCH_END };
static const s16 sPitch1_2[] = { 0x0100, PITCH_END };
static const s16 sPitch1_3[] = { 0x0400, PITCH_END };
static const s16 sPitch1_4[] = { 0x0b00, PITCH_END };

static const s16 sPitch2_0[] = { -0x300, -0x100, PITCH_END };
static const s16 sPitch2_1[] = { -0x300, 0x0200, PITCH_END };
static const s16 sPitch2_2[] = { 0x0200, 0x0400, PITCH_END };
static const s16 sPitch2_3[] = { 0x0600, 0x0800, PITCH_END };
static const s16 sPitch2_4[] = { 0x0900, 0x0800, PITCH_END };

static const s16 sPitch3_0[] = { -0x300, -0x100, -0x300, PITCH_END };
static const s16 sPitch3_1[] = { 0x0400, -0x300, 0x0400, PITCH_END };
static const s16 sPitch3_2[] = { 0x0900, 0x0800, 0x0600, PITCH_END };
static const s16 sPitch3_3[] = { 0x0100, 0x0200, 0x0400, PITCH_END };
static const s16 sPitch3_4[] = { 0x0600, 0x1000, 0x0d00, PITCH_END };

static const s16 sPitch4_0[] = { 0x0400, 0x0900, 0x0400, 0x0900, PITCH_END };
static const s16 sPitch4_1[] = { 0x0900, 0x0400, 0x0d00, 0x0400, PITCH_END };
static const s16 sPitch4_2[] = { 0x0100, 0x0200, 0x0400, 0x0600, PITCH_END };
static const s16 sPitch4_3[] = { 0x0800, 0x0600, 0x0400, 0x0200, PITCH_END };
static const s16 sPitch4_4[] = { 0x0f00, 0x0d00, 0x0b00, 0x0a00, PITCH_END };

static const s16 sPitch5_0[] = { -0x300, -0x100, 0x0100, 0x0200, 0x0400, PITCH_END };
static const s16 sPitch5_1[] = { 0x0900, 0x0800, 0x0600, 0x0400, 0x0200, PITCH_END };
static const s16 sPitch5_2[] = { 0x0100, 0x0400, 0x0900, 0x0400, 0x0100, PITCH_END };
static const s16 sPitch5_3[] = { 0x0900, 0x0400, 0x0900, 0x0400, -0x300, PITCH_END };
static const s16 sPitch5_4[] = { 0x0b00, 0x0800, 0x0400, 0x0400, 0x0600, PITCH_END };

static const s16 sPitch6_0[] = { -0x300, -0x100, 0x0100, 0x0200, 0x0400, 0x0600, PITCH_END };
static const s16 sPitch6_1[] = { 0x0800, 0x0600, 0x0400, 0x0200, 0x0100, -0x100, PITCH_END };
static const s16 sPitch6_2[] = { 0x0100, 0x0200, 0x0400, 0x0100, 0x0200, 0x1000, PITCH_END };
static const s16 sPitch6_3[] = { 0x0400, -0x300, 0x0900, 0x0400, 0x0900, 0x0400, PITCH_END };
static const s16 sPitch6_4[] = { 0x0800, 0x0900, 0x0800, 0x0900, 0x0800, 0x0900, PITCH_END };

static const s16 sPitch7_0[] = { 0x0200, 0x0100, 0x0200, 0x0100, 0x0200, 0x0400, 0x0200, PITCH_END };
static const s16 sPitch7_1[] = { 0x0100, 0x0100, -0x100, -0x100, -0x300, 0x0400, -0x300, PITCH_END };
static const s16 sPitch7_2[] = { 0x0800, 0x0900, 0x0b00, 0x0d00, 0x0e00, 0x0d00, 0x0b00, PITCH_END };
static const s16 sPitch7_3[] = { 0x0800, 0x0600, 0x0400, 0x0200, 0x0d00, 0x0b00, 0x0900, PITCH_END };
static const s16 sPitch7_4[] = { 0x0300, 0x0400, 0x0600, 0x0800, 0x0700, 0x0800, 0x0400, PITCH_END };

// In practice only sPitch7_# below are used below.
// BASE_PITCH_TABLE_INDEX is 30 by default, and this table is always indexed with (x + 30), where x is some value 0 - 4
static const s16 *const sPitchTables[NUM_BARD_PITCH_TABLES_PER_SIZE * 7] = {
sPitch1_0, sPitch1_1, sPitch1_2, sPitch1_3, sPitch1_4,
sPitch2_0, sPitch2_1, sPitch2_2, sPitch2_3, sPitch2_4,
sPitch3_0, sPitch3_1, sPitch3_2, sPitch3_3, sPitch3_4,
sPitch4_0, sPitch4_1, sPitch4_2, sPitch4_3, sPitch4_4,
sPitch5_0, sPitch5_1, sPitch5_2, sPitch5_3, sPitch5_4,
sPitch6_0, sPitch6_1, sPitch6_2, sPitch6_3, sPitch6_4,
sPitch7_0, sPitch7_1, sPitch7_2, sPitch7_3, sPitch7_4
};

// If this fails, CalcWordSounds will likely read out of bounds for sPitchTables.
STATIC_ASSERT(BASE_PITCH_TABLE_INDEX + (NUM_BARD_PITCH_TABLES_PER_SIZE-1) < ARRAY_COUNT(sPitchTables), NotEnoughPitchTablesForBardSounds)

static const struct BardSoundTemplate sEmptyPhonemeTemplate[] = {
NULL_BARD_SOUND,
NULL_BARD_SOUND,
NULL_BARD_SOUND,
NULL_BARD_SOUND,
NULL_BARD_SOUND,
NULL_BARD_SOUND
};

static const int sPhonemeLengths[NUM_PHONEME_SONGS + 1] = {
[PHONEME_ID(PH_TRAP_BLEND)] = 9,
[PHONEME_ID(PH_TRAP_HELD)] = 22,
[PHONEME_ID(PH_TRAP_SOLO)] = 15,
[PHONEME_ID(PH_FACE_BLEND)] = 16,
[PHONEME_ID(PH_FACE_HELD)] = 39,
[PHONEME_ID(PH_FACE_SOLO)] = 21,
[PHONEME_ID(PH_CLOTH_BLEND)] = 9,
[PHONEME_ID(PH_CLOTH_HELD)] = 30,
[PHONEME_ID(PH_CLOTH_SOLO)] = 24,
[PHONEME_ID(PH_DRESS_BLEND)] = 15,
[PHONEME_ID(PH_DRESS_HELD)] = 25,
[PHONEME_ID(PH_DRESS_SOLO)] = 12,
[PHONEME_ID(PH_FLEECE_BLEND)] = 22,
[PHONEME_ID(PH_FLEECE_HELD)] = 45,
[PHONEME_ID(PH_FLEECE_SOLO)] = 24,
[PHONEME_ID(PH_KIT_BLEND)] = 15,
[PHONEME_ID(PH_KIT_HELD)] = 40,
[PHONEME_ID(PH_KIT_SOLO)] = 9,
[PHONEME_ID(PH_PRICE_BLEND)] = 21,
[PHONEME_ID(PH_PRICE_HELD)] = 42,
[PHONEME_ID(PH_PRICE_SOLO)] = 18,
[PHONEME_ID(PH_LOT_BLEND)] = 9,
[PHONEME_ID(PH_LOT_HELD)] = 22,
[PHONEME_ID(PH_LOT_SOLO)] = 15,
[PHONEME_ID(PH_GOAT_BLEND)] = 27,
[PHONEME_ID(PH_GOAT_HELD)] = 48,
[PHONEME_ID(PH_GOAT_SOLO)] = 18,
[PHONEME_ID(PH_THOUGHT_BLEND)] = 27,
[PHONEME_ID(PH_THOUGHT_HELD)] = 33,
[PHONEME_ID(PH_THOUGHT_SOLO)] = 24,
[PHONEME_ID(PH_CHOICE_BLEND)] = 25,
[PHONEME_ID(PH_CHOICE_HELD)] = 39,
[PHONEME_ID(PH_CHOICE_SOLO)] = 19,
[PHONEME_ID(PH_MOUTH_BLEND)] = 16,
[PHONEME_ID(PH_MOUTH_HELD)] = 54,
[PHONEME_ID(PH_MOUTH_SOLO)] = 18,
[PHONEME_ID(PH_FOOT_BLEND)] = 9,
[PHONEME_ID(PH_FOOT_HELD)] = 45,
[PHONEME_ID(PH_FOOT_SOLO)] = 15,
[PHONEME_ID(PH_GOOSE_BLEND)] = 12,
[PHONEME_ID(PH_GOOSE_HELD)] = 39,
[PHONEME_ID(PH_GOOSE_SOLO)] = 23,
[PHONEME_ID(PH_STRUT_BLEND)] = 5,
[PHONEME_ID(PH_STRUT_HELD)] = 45,
[PHONEME_ID(PH_STRUT_SOLO)] = 12,
[PHONEME_ID(PH_CURE_BLEND)] = 21,
[PHONEME_ID(PH_CURE_HELD)] = 48,
[PHONEME_ID(PH_CURE_SOLO)] = 12,
[PHONEME_ID(PH_NURSE_BLEND)] = 21,
[PHONEME_ID(PH_NURSE_HELD)] = 69,
[PHONEME_ID(PH_NURSE_SOLO)] = 18,
[NUM_PHONEME_SONGS] = 15, // This is the length that will be used by PREV_BARD_SOUND to hold the previous phoneme sound.
};

static s16 GetWordPitch(int tableIndex, int pitchIndex)
{
return sBardSoundPitchTables[pitchIdx][songPos];
return sPitchTables[tableIndex][pitchIndex];
}

const struct BardSound *GetWordSounds(u16 word)
const struct BardSoundTemplate *GetWordSoundTemplates(u16 easyChatWord)
{
u32 category;
u32 subword;
const struct BardSound (*ptr)[BARD_SOUND_MAX_LENGTH];
const struct BardSoundTemplate (*ptr)[MAX_BARD_SOUNDS_PER_WORD];

if (IsBardWordInvalid(word))
{
return gBardSound_InvalidWord;
}
category = EC_GROUP(word);
subword = EC_INDEX(word);
if (IsBardWordInvalid(easyChatWord))
return sEmptyPhonemeTemplate;

category = EC_GROUP(easyChatWord);
subword = EC_INDEX(easyChatWord);
switch (category)
{
case EC_GROUP_POKEMON:
case EC_GROUP_POKEMON_NATIONAL:
ptr = gBardSounds_Pokemon;
ptr = sBardSoundTemplates_Pokemon;
break;
case EC_GROUP_MOVE_1:
case EC_GROUP_MOVE_2:
ptr = gBardSounds_Moves;
ptr = sBardSoundTemplates_Moves;
break;
default:
ptr = gBardSoundsTable[category];
ptr = sBardSoundTemplatesTable[category];
break;
}
ptr += subword;
return *ptr;
}

void GetWordPhonemes(struct BardSong *song, u16 word)
// Assumes that 'soundTemplates' has already been loaded with the BardSoundTemplates for the easy chat word to calculate sounds for.
// 'pitchTableIndex' is chosen depending on the easy chat word, but is essentially an arbitrary value 0-4.
void CalcWordSounds(struct BardSong *song, u16 pitchTableIndex)
{
int i;
const struct BardSound *sound;
const struct BardSoundTemplate *template;

song->length = 0;
for (i = 0; i < BARD_SOUND_MAX_LENGTH; i ++)

for (i = 0; i < MAX_BARD_SOUNDS_PER_WORD; i ++)
{
sound = &song->sound[i];
if (sound->songLengthId != 0xFF)
template = &song->soundTemplates[i];
if (template->songId != PHONEME_ID_NONE)
{
song->phonemes[i].length = sound->songLengthOffset + gBardSoundLengthTable[sound->songLengthId];
song->phonemes[i].pitch = CalcWordPitch(word + 30, i);
song->length += song->phonemes[i].length;
// Calculate the length and pitch of each phoneme in this word.
// A phoneme's length is always the same, and depends on the phoneme song and any adjustments in the template.
// Its pitch changes depending on the easy chat word and where in the list of templates the phoneme appears.
song->sounds[i].length = template->lengthAdjustment + sPhonemeLengths[template->songId];
song->sounds[i].pitch = GetWordPitch(pitchTableIndex + BASE_PITCH_TABLE_INDEX, i);

// Add this phoneme's length to the total sound length for this word.
song->length += song->sounds[i].length;
}
}
song->currPhoneme = 0;
song->soundIndex = 0;
song->voiceInflection = 0;
}
Loading

0 comments on commit 882af85

Please sign in to comment.