From 6496e19f680bc3e4e1dafb0ff5dd194b8e1055b7 Mon Sep 17 00:00:00 2001 From: yenatch Date: Thu, 31 Oct 2013 01:35:27 -0400 Subject: spruce up music command classes plus BigEndianParam --- pokemontools/crystal.py | 79 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 22 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index c95b705..b6f17ee 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -2889,41 +2889,65 @@ def create_command_classes(debug=False): command_classes = create_command_classes() +class BigEndianParam: + """big-endian word""" + size = 2 + should_be_decimal = False + + def __init__(self, *args, **kwargs): + self.prefix = '$' + for (key, value) in kwargs.items(): + setattr(self, key, value) + self.parse() -music_commands_new = { - 0xD0: ["octave8"], - 0xD1: ["octave7"], - 0xD2: ["octave6"], - 0xD3: ["octave5"], - 0xD4: ["octave4"], - 0xD5: ["octave3"], - 0xD6: ["octave2"], - 0xD7: ["octave1"], - 0xD8: ["notetype", ["note_length", SingleByteParam], ["intensity", SingleByteParam]], # only 1 param on ch3 + def parse(self): + self.bytes = rom_interval(self.address, 2, strings=False) + self.parsed_number = self.bytes[0] * 0x100 + self.bytes[1] + + def to_asm(self): + if not self.should_be_decimal: + return self.prefix+"".join([("%.2x")%x for x in self.bytes]) + elif self.should_be_decimal: + decimal = int("0x"+"".join([("%.2x")%x for x in self.bytes]), 16) + return str(decimal) + +class DecimalBigEndianParam(BigEndianParam): + should_be_decimal = True + +music_commands = { + 0xD0: ["octave 8"], + 0xD1: ["octave 7"], + 0xD2: ["octave 6"], + 0xD3: ["octave 5"], + 0xD4: ["octave 4"], + 0xD5: ["octave 3"], + 0xD6: ["octave 2"], + 0xD7: ["octave 1"], + 0xD8: ["notetype", ["note_length", SingleByteParam], ["intensity", SingleByteParam]], # no intensity on channel 4/8 0xD9: ["forceoctave", ["octave", SingleByteParam]], - 0xDA: ["tempo", ["tempo", MultiByteParam]], + 0xDA: ["tempo", ["tempo", DecimalBigEndianParam]], 0xDB: ["dutycycle", ["duty_cycle", SingleByteParam]], 0xDC: ["intensity", ["intensity", SingleByteParam]], 0xDD: ["soundinput", ["input", SingleByteParam]], - 0xDE: ["unknownmusic0xde", ["unknown", SingleByteParam]], # also updates duty cycle + 0xDE: ["unknownmusic0xde", ["unknown", SingleByteParam]], 0xDF: ["unknownmusic0xdf"], 0xE0: ["unknownmusic0xe0", ["unknown", SingleByteParam], ["unknown", SingleByteParam]], 0xE1: ["vibrato", ["delay", SingleByteParam], ["extent", SingleByteParam]], 0xE2: ["unknownmusic0xe2", ["unknown", SingleByteParam]], - 0xE3: ["togglenoise", ["id", SingleByteParam]], # this can have 0-1 params! + 0xE3: ["togglenoise", ["id", SingleByteParam]], # no parameters on toggle off 0xE4: ["panning", ["tracks", SingleByteParam]], 0xE5: ["volume", ["volume", SingleByteParam]], - 0xE6: ["tone", ["tone", MultiByteParam]], # big endian + 0xE6: ["tone", ["tone", BigEndianParam]], 0xE7: ["unknownmusic0xe7", ["unknown", SingleByteParam]], 0xE8: ["unknownmusic0xe8", ["unknown", SingleByteParam]], - 0xE9: ["globaltempo", ["value", MultiByteParam]], + 0xE9: ["globaltempo", ["value", DecimalBigEndianParam]], 0xEA: ["restartchannel", ["address", PointerLabelParam]], - 0xEB: ["newsong", ["id", MultiByteParam]], + 0xEB: ["newsong", ["id", DecimalBigEndianParam]], 0xEC: ["sfxpriorityon"], 0xED: ["sfxpriorityoff"], 0xEE: ["unknownmusic0xee", ["address", PointerLabelParam]], 0xEF: ["stereopanning", ["tracks", SingleByteParam]], - 0xF0: ["sfxtogglenoise", ["id", SingleByteParam]], # 0-1 params + 0xF0: ["sfxtogglenoise", ["id", SingleByteParam]], # no parameters on toggle off 0xF1: ["music0xf1"], # nothing 0xF2: ["music0xf2"], # nothing 0xF3: ["music0xf3"], # nothing @@ -2936,19 +2960,29 @@ music_commands_new = { 0xFA: ["setcondition", ["condition", SingleByteParam]], 0xFB: ["jumpif", ["condition", SingleByteParam], ["address", PointerLabelParam]], 0xFC: ["jumpchannel", ["address", PointerLabelParam]], - 0xFD: ["loopchannel", ["count", SingleByteParam], ["address", PointerLabelParam]], + 0xFD: ["loopchannel", ["count", DecimalParam], ["address", PointerLabelParam]], 0xFE: ["callchannel", ["address", PointerLabelParam]], 0xFF: ["endchannel"], } -music_command_enders = [0xEA, 0xEB, 0xEE, 0xFC, 0xFF,] -# special case for 0xFD (if loopchannel.count = 0, break) +music_command_enders = [ + "restartchannel", + "newsong", + "unknownmusic0xee", + "jumpchannel", + "endchannel", +] def create_music_command_classes(debug=False): klasses = [] - for (byte, cmd) in music_commands_new.items(): + for (byte, cmd) in music_commands.items(): cmd_name = cmd[0].replace(" ", "_") - params = {"id": byte, "size": 1, "end": byte in music_command_enders, "macro_name": cmd_name} + params = { + "id": byte, + "size": 1, + "end": cmd[0] in music_command_enders, + "macro_name": cmd[0] + } params["param_types"] = {} if len(cmd) > 1: param_types = cmd[1:] @@ -2968,6 +3002,7 @@ def create_music_command_classes(debug=False): klasses.append(klass) # later an individual klass will be instantiated to handle something return klasses + music_classes = create_music_command_classes() -- cgit v1.2.3 From e11e406fa4e6428e6aecf93f598d343ccbe908c7 Mon Sep 17 00:00:00 2001 From: yenatch Date: Thu, 31 Oct 2013 01:38:30 -0400 Subject: crystal: sfx and song names --- pokemontools/sfx_names.py | 211 +++++++++++++++++++++++++++++++++++++++++++++ pokemontools/song_names.py | 107 +++++++++++++++++++++++ 2 files changed, 318 insertions(+) create mode 100644 pokemontools/sfx_names.py create mode 100644 pokemontools/song_names.py diff --git a/pokemontools/sfx_names.py b/pokemontools/sfx_names.py new file mode 100644 index 0000000..f2d6408 --- /dev/null +++ b/pokemontools/sfx_names.py @@ -0,0 +1,211 @@ +# coding: utf-8 + +sfx_names = [ + 'DexFanfare5079', + 'Item', + 'CaughtMon', + 'PokeballsPlacedOnTable', + 'Potion', + 'FullHeal', + 'Menu', + 'ReadText', + 'ReadText2', + 'DexFanfare2049', + 'DexFanfare80109', + 'Poison', + 'GotSafariBalls', + 'BootPc', + 'ShutDownPc', + 'ChoosePcOption', + 'EscapeRope', + 'PushButton', + 'SecondPartOfItemfinder', + 'WarpTo', + 'WarpFrom', + 'ChangeDexMode', + 'JumpOverLedge', + 'GrassRustle', + 'Fly', + 'Wrong', + 'Squeak', + 'Strength', + 'Boat', + 'WallOpen', + 'PlacePuzzlePieceDown', + 'EnterDoor', + 'SwitchPokemon', + 'Tally', + 'Transaction', + 'ExitBuilding', + 'Bump', + 'Save', + 'Pokeflute', + 'ElevatorEnd', + 'ThrowBall', + 'BallPoof', + 'Unknown3A', + 'Run', + 'SlotMachineStart', + 'Fanfare', + 'Peck', + 'Kinesis', + 'Lick', + 'Pound', + 'MovePuzzlePiece', + 'CometPunch', + 'MegaPunch', + 'Scratch', + 'Vicegrip', + 'RazorWind', + 'Cut', + 'WingAttack', + 'Whirlwind', + 'Bind', + 'VineWhip', + 'DoubleKick', + 'MegaKick', + 'Headbutt', + 'HornAttack', + 'Tackle', + 'PoisonSting', + 'Powder', + 'Doubleslap', + 'Bite', + 'JumpKick', + 'Stomp', + 'TailWhip', + 'KarateChop', + 'Submission', + 'WaterGun', + 'SwordsDance', + 'Thunder', + 'Supersonic', + 'Leer', + 'Ember', + 'Bubblebeam', + 'HydroPump', + 'Surf', + 'Psybeam', + 'Charge', + 'Thundershock', + 'Psychic', + 'Screech', + 'BoneClub', + 'Sharpen', + 'EggBomb', + 'Sing', + 'HyperBeam', + 'Shine', + 'Unknown5F', + 'Unknown60', + 'Unknown61', + 'Unknown62', + 'Unknown63', + 'Burn', + 'TitleScreenEntrance', + 'Unknown66', + 'GetCoinFromSlots', + 'PayDay', + 'Metronome', + 'Call', + 'HangUp', + 'NoSignal', + 'Sandstorm', + 'Elevator', + 'Protect', + 'Sketch', + 'RainDance', + 'Aeroblast', + 'Spark', + 'Curse', + 'Rage', + 'Thief', + 'Thief2', + 'SpiderWeb', + 'MindReader', + 'Nightmare', + 'Snore', + 'SweetKiss', + 'SweetKiss2', + 'BellyDrum', + 'Unknown7F', + 'SludgeBomb', + 'Foresight', + 'Spite', + 'Outrage', + 'PerishSong', + 'GigaDrain', + 'Attract', + 'Kinesis2', + 'ZapCannon', + 'MeanLook', + 'HealBell', + 'Return', + 'ExpBar', + 'MilkDrink', + 'Present', + 'MorningSun', + 'LevelUp', + 'KeyItem', + 'Fanfare2', + 'RegisterPhone#', + '3RdPlace', + 'GetEggFromDaycareMan', + 'GetEggFromDaycareLady', + 'MoveDeleted', + '2NdPlace', + '1StPlace', + 'ChooseACard', + 'GetTm', + 'GetBadge', + 'QuitSlots', + 'EggCrack', + 'DexFanfareLessThan20', + 'DexFanfare140169', + 'DexFanfare170199', + 'DexFanfare200229', + 'DexFanfare230Plus', + 'Evolved', + 'MasterBall', + 'EggHatch', + 'GsIntroCharizardFireball', + 'GsIntroPokemonAppears', + 'Flash', + 'GameFreakLogoGs', + 'NotVeryEffective', + 'Damage', + 'SuperEffective', + 'BallBounce', + 'Moonlight', + 'Encore', + 'BeatUp', + 'BatonPass', + 'BallWiggle', + 'SweetScent', + 'SweetScent2', + 'HitEndOfExpBar', + 'GiveTrademon', + 'GetTrademon', + 'TrainArrived', + 'StopSlot', + '2Boops', + 'GlassTing', + 'GlassTing2', + 'IntroUnown1', + 'IntroUnown2', + 'IntroUnown3', + 'DittoPopUp', + 'DittoTransform', + 'IntroSuicune1', + 'IntroPichu', + 'IntroSuicune2', + 'IntroSuicune3', + 'DittoBounce', + 'IntroSuicune4', + 'GameFreakPresents', + 'Tingle', + 'UnknownCb', + 'TwoPcBeeps', + '4NoteDitty', + 'Twinkle', +] diff --git a/pokemontools/song_names.py b/pokemontools/song_names.py new file mode 100644 index 0000000..1e48ca7 --- /dev/null +++ b/pokemontools/song_names.py @@ -0,0 +1,107 @@ +# coding: utf-8 + +song_names = [ + 'Nothing', + 'TitleScreen', + 'Route1', + 'Route3', + 'Route12', + 'MagnetTrain', + 'KantoGymBattle', + 'KantoTrainerBattle', + 'KantoWildBattle', + 'PokemonCenter', + 'LookHiker', + 'LookLass', + 'LookOfficer', + 'HealPokemon', + 'LavenderTown', + 'Route2', + 'MtMoon', + 'ShowMeAround', + 'GameCorner', + 'Bicycle', + 'HallOfFame', + 'ViridianCity', + 'CeladonCity', + 'TrainerVictory', + 'WildPokemonVictory', + 'GymLeaderVictory', + 'MtMoonSquare', + 'Gym', + 'PalletTown', + 'ProfOaksPokemonTalk', + 'ProfOak', + 'LookRival', + 'AfterTheRivalFight', + 'Surf', + 'Evolution', + 'NationalPark', + 'Credits', + 'AzaleaTown', + 'CherrygroveCity', + 'LookKimonoGirl', + 'UnionCave', + 'JohtoWildBattle', + 'JohtoTrainerBattle', + 'Route30', + 'EcruteakCity', + 'VioletCity', + 'JohtoGymBattle', + 'ChampionBattle', + 'RivalBattle', + 'RocketBattle', + 'ElmsLab', + 'DarkCave', + 'Route29', + 'Route36', + 'SSAqua', + 'LookYoungster', + 'LookBeauty', + 'LookRocket', + 'LookPokemaniac', + 'LookSage', + 'NewBarkTown', + 'GoldenrodCity', + 'VermilionCity', + 'PokemonChannel', + 'PokeFluteChannel', + 'TinTower', + 'SproutTower', + 'BurnedTower', + 'Lighthouse', + 'LakeOfRage', + 'IndigoPlateau', + 'Route37', + 'RocketHideout', + 'DragonsDen', + 'JohtoWildBattleNight', + 'RuinsOfAlphRadio', + 'SuccessfulCapture', + 'Route26', + 'Mom', + 'VictoryRoad', + 'PokemonLullaby', + 'PokemonMarch', + 'GoldSilverOpening', + 'GoldSilverOpening2', + 'MainMenu', + 'RuinsOfAlphInterior', + 'RocketTheme', + 'DancingHall', + 'ContestResults', + 'BugCatchingContest', + 'LakeOfRageRocketRadio', + 'Printer', + 'PostCredits', + 'Clair', + 'MobileAdapterMenu', + 'MobileAdapter', + 'BuenasPassword', + 'LookMysticalMan', + 'CrystalOpening', + 'BattleTowerTheme', + 'SuicuneBattle', + 'BattleTowerLobby', + 'MobileCenter', +] -- cgit v1.2.3 From fc34ecde0aa20c288c5a9eefef00955b30a4894c Mon Sep 17 00:00:00 2001 From: yenatch Date: Thu, 31 Oct 2013 01:40:59 -0400 Subject: crystal: sound data dumper todo: samples, drumkits --- pokemontools/audio.py | 278 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 pokemontools/audio.py diff --git a/pokemontools/audio.py b/pokemontools/audio.py new file mode 100644 index 0000000..92bd3e3 --- /dev/null +++ b/pokemontools/audio.py @@ -0,0 +1,278 @@ +# coding: utf-8 + +import os + +from math import ceil + +from gbz80disasm import get_global_address, get_local_address + +import crystal +from crystal import music_classes as sound_classes +from crystal import Command + +from crystal import load_rom +rom = load_rom() +rom = bytearray(rom) + +import config +conf = config.Config() + + +class NybbleParam: + size = 0.5 + byte_type = 'dn' + which = None + + def __init__(self, address, name): + if self.which == None: + self.which = {0.0: 'lo', 0.5: 'hi'}[address % 1] + self.address = int(address) + self.name = name + self.parse() + + def parse(self): + self.nybble = (rom[self.address] >> {'lo': 0, 'hi': 4}[self.which]) & 0xf + +class HiNybbleParam(NybbleParam): + which = 'hi' + def to_asm(self): + return '%d' % self.nybble + +class LoNybbleParam(NybbleParam): + which = 'lo' + def to_asm(self): + return '%d' % self.nybble + +class PitchParam(HiNybbleParam): + def to_asm(self): + if self.nybble == 0: + pitch = 'Rst' + else: + pitch = 'CDEFGAB'[(self.nybble - 1) / 2] + if not self.nybble & 1: + pitch += '#' + return pitch + + +class Note(Command): + macro_name = "note" + size = 0 + end = False + param_types = { + 0: {"name": "pitch", "class": PitchParam}, + 1: {"name": "duration", "class": LoNybbleParam}, + } + allowed_lengths = [2] + + def parse(self): + self.params = [] + byte = rom[self.address] + current_address = self.address + for (key, param_type) in self.param_types.items(): + name = param_type["name"] + class_ = param_type["class"] + + # by making an instance, obj.parse() is called + obj = class_(address=int(current_address), name=name) + self.params += [obj] + + current_address += obj.size + self.size += obj.size + + self.params = dict(enumerate(self.params)) + + # obj sizes were 0.5, but were working with ints + current_address = int(ceil(current_address)) + self.size = int(ceil(self.size)) + + self.last_address = current_address + return True + + + +class Channel: + """A sound channel data parser.""" + + def __init__(self, address, channel=1, base_label='Sound'): + self.start_address = address + self.address = address + self.channel = channel + self.base_label = base_label + self.output = [] + self.parse() + + def parse(self): + noise = False + done = False + while not done: + cmd = rom[self.address] + + class_ = self.get_sound_class(cmd)(address=self.address) + + # notetype loses the intensity param on channel 4 + if class_.macro_name == 'notetype': + if self.channel in [4, 8]: + class_.size -= 1 + class_.params = dict(class_.params.items()[:-1]) + + # togglenoise only has a param when toggled on + elif class_.macro_name in ['togglenoise', 'sfxtogglenoise']: + if noise: + class_.size -= 1 + class_.params = dict(class_.params.items()[:-1]) + noise = not noise + + asm = class_.to_asm() + + # label any jumps or calls + for key, param in class_.param_types.items(): + if param['class'] == crystal.PointerLabelParam: + label_address = class_.params[key].parsed_address + label = '%s_branch_%x' % ( + self.base_label, + label_address + ) + label_output = ( + label_address, + '%s: ; %x' % (label, label_address) + ) + if label_output not in self.output: + self.output += [label_output] + asm = asm.replace( + '$%x' % (get_local_address(label_address)), + label + ) + + self.output += [(self.address, '\t' + asm)] + self.address += class_.size + + done = class_.end + # infinite loops are enders + if class_.macro_name == 'loopchannel': + if class_.params[0].byte == 0: + done = True + + # keep going past enders if theres more to parse + if any(self.address <= address for address, asm in self.output): + if done: + self.output += [(self.address, '; %x' % self.address)] + done = False + + # dumb safety checks + if ( + self.address >= len(rom) or + self.address / 0x4000 != self.start_address / 0x4000 + ) and not done: + done = True + raise Exception, 'reached the end of the bank without finishing!' + + def to_asm(self): + self.output = sorted( + self.output, + # comment then label then asm + key=lambda (x, y):(x, not y.startswith(';'), ':' not in y) + ) + text = '' + for i, (address, asm) in enumerate(self.output): + if ':' in asm: + # dont print labels for empty chunks + for (x, y) in self.output[i:]: + if ':' not in y: + text += '\n' + asm + '\n' + break + else: + text += asm + '\n' + text += '; %x' % (address + 1) + '\n' + return text + + def get_sound_class(self, i): + for class_ in sound_classes: + if class_.id == i: + return class_ + return Note + + +class Sound: + """Interprets a sound data header.""" + + def __init__(self, address, name=''): + self.start_address = address + self.bank = address / 0x4000 + self.address = address + + self.name = name + self.base_label = 'Sound_%x' % self.start_address + if self.name != '': + self.base_label = self.name + + self.output = [] + self.parse() + + def parse(self): + self.num_channels = (rom[self.address] >> 6) + 1 + self.channels = [] + for ch in xrange(self.num_channels): + current_channel = (rom[self.address] & 0xf) + 1 + self.address += 1 + address = rom[self.address] + rom[self.address + 1] * 0x100 + address = self.bank * 0x4000 + address % 0x4000 + self.address += 2 + channel = Channel(address, current_channel, self.base_label) + self.channels += [(current_channel, channel)] + + def to_asm(self): + asms = {} + + text = '' + text += '%s: ; %x' % (self.base_label, self.start_address) + '\n' + for num, channel in self.channels: + text += '\tchannel %d, %s_Ch%d' % (num, self.base_label, num) + '\n' + text += '; %x' % self.address + '\n' + asms[self.start_address] = text + + text = '' + for ch, (num, channel) in enumerate(self.channels): + text += '%s_Ch%d: ; %x' % ( + self.base_label, + num, + channel.start_address + ) + '\n' + # stack labels at the same address + if ch < len(self.channels) - 1: + next_channel = self.channels[ch + 1][1] + if next_channel.start_address == channel.start_address: + continue + text += channel.to_asm() + asms[channel.start_address] = text + text = '' + + return '\n'.join(asm for address, asm in sorted(asms.items())) + + +def dump_sounds(origin, names, path, base_label='Sound_'): + """Dump sound data from a pointer table.""" + for i, name in enumerate(names): + addr = origin + i * 3 + bank, address = rom[addr], rom[addr+1] + rom[addr+2] * 0x100 + sound_at = get_global_address(address, bank) + + sound = Sound(sound_at, base_label + name) + output = sound.to_asm() + + filename = name.lower() + '.asm' + with open(os.path.join(path, filename), 'w') as out: + out.write(output) + +def dump_crystal_music(): + from song_names import song_names + dump_sounds(0xe906e, song_names, os.path.join(conf.path, 'audio', 'music'), 'Music_') + +def dump_crystal_sfx(): + from sfx_names import sfx_names + dump_sounds(0xe927c, sfx_names, os.path.join(conf.path, 'audio', 'sfx'), 'Sfx_') + + +if __name__ == '__main__': + dump_crystal_music() + dump_crystal_sfx() + -- cgit v1.2.3 From f790933680e3a91706b323c87ec9cbd1cb984869 Mon Sep 17 00:00:00 2001 From: yenatch Date: Tue, 5 Nov 2013 13:54:59 -0500 Subject: audio.py: pointer table generation --- pokemontools/audio.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pokemontools/audio.py b/pokemontools/audio.py index 92bd3e3..25c41c7 100644 --- a/pokemontools/audio.py +++ b/pokemontools/audio.py @@ -267,10 +267,18 @@ def dump_crystal_music(): from song_names import song_names dump_sounds(0xe906e, song_names, os.path.join(conf.path, 'audio', 'music'), 'Music_') +def generate_crystal_music_pointers(): + from song_names import song_names + return '\n'.join('\tdbw BANK({0}), {0}'.format('Music_' + label) for label in song_names) + def dump_crystal_sfx(): from sfx_names import sfx_names dump_sounds(0xe927c, sfx_names, os.path.join(conf.path, 'audio', 'sfx'), 'Sfx_') +def generate_crystal_sfx_pointers(): + from sfx_names import sfx_names + return '\n'.join('\tdbw BANK({0}), {0}'.format('Sfx_' + label) for label in sfx_names) + if __name__ == '__main__': dump_crystal_music() -- cgit v1.2.3 From 4a7373d8e79d17f10ebafa3ccef7b822a5b139af Mon Sep 17 00:00:00 2001 From: yenatch Date: Tue, 5 Nov 2013 14:10:25 -0500 Subject: audio.py: cleaner key deletion --- pokemontools/audio.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pokemontools/audio.py b/pokemontools/audio.py index 25c41c7..5318ebe 100644 --- a/pokemontools/audio.py +++ b/pokemontools/audio.py @@ -113,13 +113,13 @@ class Channel: if class_.macro_name == 'notetype': if self.channel in [4, 8]: class_.size -= 1 - class_.params = dict(class_.params.items()[:-1]) + del class_.params[class_.size - 1] # togglenoise only has a param when toggled on elif class_.macro_name in ['togglenoise', 'sfxtogglenoise']: if noise: class_.size -= 1 - class_.params = dict(class_.params.items()[:-1]) + del class_.params[class_.size - 1] noise = not noise asm = class_.to_asm() -- cgit v1.2.3 From d88513d2cdf8cfa0569b713401c4ff2474895f3f Mon Sep 17 00:00:00 2001 From: yenatch Date: Thu, 7 Nov 2013 00:27:22 -0500 Subject: dont use ambiguous characters in sfx_names --- pokemontools/sfx_names.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemontools/sfx_names.py b/pokemontools/sfx_names.py index f2d6408..f7da967 100644 --- a/pokemontools/sfx_names.py +++ b/pokemontools/sfx_names.py @@ -148,7 +148,7 @@ sfx_names = [ 'LevelUp', 'KeyItem', 'Fanfare2', - 'RegisterPhone#', + 'RegisterPhoneNumber', '3RdPlace', 'GetEggFromDaycareMan', 'GetEggFromDaycareLady', -- cgit v1.2.3 From ec52d627003ee42ba37c644a3b58acf870abc45b Mon Sep 17 00:00:00 2001 From: yenatch Date: Thu, 7 Nov 2013 00:29:37 -0500 Subject: sound parsing actually works now + sorted out output semantics + cross-song label references + incbins --- pokemontools/audio.py | 139 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 93 insertions(+), 46 deletions(-) diff --git a/pokemontools/audio.py b/pokemontools/audio.py index 5318ebe..247ef87 100644 --- a/pokemontools/audio.py +++ b/pokemontools/audio.py @@ -18,6 +18,11 @@ import config conf = config.Config() +def sort_asms(asms): + """sort and remove duplicates from a list of tuples + format (address, asm, last_address)""" + return sorted(set(asms), key=lambda (x,y,z):(x,z,not y.startswith(';'), ':' not in y)) + class NybbleParam: size = 0.5 byte_type = 'dn' @@ -45,24 +50,29 @@ class LoNybbleParam(NybbleParam): class PitchParam(HiNybbleParam): def to_asm(self): + """E and B cant be sharp""" if self.nybble == 0: - pitch = 'Rst' + pitch = '__' else: - pitch = 'CDEFGAB'[(self.nybble - 1) / 2] - if not self.nybble & 1: + pitch = 'CCDDEFFGGAAB'[(self.nybble - 1)] + if self.nybble in [2, 4, 7, 9, 11]: pitch += '#' + else: + pitch += '_' return pitch class Note(Command): macro_name = "note" - size = 0 + size = 1 end = False param_types = { 0: {"name": "pitch", "class": PitchParam}, 1: {"name": "duration", "class": LoNybbleParam}, } allowed_lengths = [2] + override_byte_check = True + is_rgbasm_macro = True def parse(self): self.params = [] @@ -77,7 +87,6 @@ class Note(Command): self.params += [obj] current_address += obj.size - self.size += obj.size self.params = dict(enumerate(self.params)) @@ -99,6 +108,7 @@ class Channel: self.channel = channel self.base_label = base_label self.output = [] + self.labels = [] self.parse() def parse(self): @@ -134,16 +144,16 @@ class Channel: ) label_output = ( label_address, - '%s: ; %x' % (label, label_address) + '\n%s: ; %x' % (label, label_address), + label_address ) - if label_output not in self.output: - self.output += [label_output] + self.labels += [label_output] asm = asm.replace( '$%x' % (get_local_address(label_address)), label ) - self.output += [(self.address, '\t' + asm)] + self.output += [(self.address, '\t' + asm, self.address + class_.size)] self.address += class_.size done = class_.end @@ -153,9 +163,9 @@ class Channel: done = True # keep going past enders if theres more to parse - if any(self.address <= address for address, asm in self.output): + if any(self.address <= address for address, asm, last_address in self.output + self.labels): if done: - self.output += [(self.address, '; %x' % self.address)] + self.output += [(self.address, '; %x' % self.address, self.address)] done = False # dumb safety checks @@ -167,22 +177,18 @@ class Channel: raise Exception, 'reached the end of the bank without finishing!' def to_asm(self): - self.output = sorted( - self.output, - # comment then label then asm - key=lambda (x, y):(x, not y.startswith(';'), ':' not in y) - ) + output = sort_asms(self.output + self.labels) text = '' - for i, (address, asm) in enumerate(self.output): + for i, (address, asm, last_address) in enumerate(output): if ':' in asm: # dont print labels for empty chunks - for (x, y) in self.output[i:]: + for (x, y, z) in output[i:]: if ':' not in y: text += '\n' + asm + '\n' break else: text += asm + '\n' - text += '; %x' % (address + 1) + '\n' + text += '; %x' % (last_address) + '\n' return text def get_sound_class(self, i): @@ -206,6 +212,8 @@ class Sound: self.base_label = self.name self.output = [] + self.labels = [] + self.asms = [] self.parse() def parse(self): @@ -220,44 +228,83 @@ class Sound: channel = Channel(address, current_channel, self.base_label) self.channels += [(current_channel, channel)] - def to_asm(self): - asms = {} + self.labels += channel.labels - text = '' - text += '%s: ; %x' % (self.base_label, self.start_address) + '\n' - for num, channel in self.channels: - text += '\tchannel %d, %s_Ch%d' % (num, self.base_label, num) + '\n' - text += '; %x' % self.address + '\n' - asms[self.start_address] = text - - text = '' - for ch, (num, channel) in enumerate(self.channels): - text += '%s_Ch%d: ; %x' % ( + label_text = '\n%s_Ch%d: ; %x' % ( self.base_label, - num, + current_channel, channel.start_address - ) + '\n' - # stack labels at the same address - if ch < len(self.channels) - 1: - next_channel = self.channels[ch + 1][1] - if next_channel.start_address == channel.start_address: - continue - text += channel.to_asm() - asms[channel.start_address] = text - text = '' + ) + label_output = (channel.start_address, label_text, channel.start_address) + self.labels += [label_output] + + asms = [] - return '\n'.join(asm for address, asm in sorted(asms.items())) + text = '%s: ; %x' % (self.base_label, self.start_address) + '\n' + for i, (num, channel) in enumerate(self.channels): + channel_id = num - 1 + if i == 0: + channel_id += (len(self.channels) - 1) << 6 + text += '\tdbw $%.2x, %s_Ch%d' % (channel_id, self.base_label, num) + '\n' + text += '; %x\n' % self.address + asms += [(self.start_address, text, self.start_address + len(self.channels) * 3)] + + for num, channel in self.channels: + asms += channel.output + + asms = sort_asms(asms) + self.last_address = asms[-1][2] + asms += [(self.last_address,'; %x' % self.last_address, self.last_address)] + + self.asms += asms + + def to_asm(self, labels=[]): + """insert outside labels here""" + asms = self.asms + + # incbins dont really count as parsed data + incbins = [] + for i, (address, asm, last_address) in enumerate(asms): + if i + 1 < len(asms): + next_address = asms[i + 1][0] + if last_address != next_address: + incbins += [(last_address, 'INCBIN "baserom.gbc", $%x, $%x - $%x' % (last_address, next_address, last_address), next_address)] + asms += incbins + for label in self.labels + labels: + if self.start_address <= label[0] < self.last_address: + asms += [label] + + return '\n'.join(asm for address, asm, last_address in sort_asms(asms)) def dump_sounds(origin, names, path, base_label='Sound_'): """Dump sound data from a pointer table.""" - for i, name in enumerate(names): - addr = origin + i * 3 + def get_sound_at(addr): bank, address = rom[addr], rom[addr+1] + rom[addr+2] * 0x100 - sound_at = get_global_address(address, bank) + return get_global_address(address, bank) + # first pass to grab labels and boundaries + labels = [] + addresses = [] + for i, name in enumerate(names): + sound_at = get_sound_at(origin + i * 3) + sound = Sound(sound_at, base_label + name) + labels += sound.labels + addresses += [(sound.start_address, sound.last_address)] + addresses = sorted(addresses) + + for i, name in enumerate(names): + sound_at = get_sound_at(origin + i * 3) sound = Sound(sound_at, base_label + name) - output = sound.to_asm() + output = sound.to_asm(labels) + '\n' + + # incbin trailing commands that didnt get picked up + index = addresses.index((sound.start_address, sound.last_address)) + if index + 1 < len(addresses): + next_address = addresses[index + 1][0] + if 5 > next_address - sound.last_address > 0: + if next_address / 0x4000 == sound.last_address / 0x4000: + output += '\nINCBIN "baserom.gbc", $%x, $%x - $%x\n' % (sound.last_address, next_address, sound.last_address) filename = name.lower() + '.asm' with open(os.path.join(path, filename), 'w') as out: -- cgit v1.2.3 From ea28149195b9fa75803253837eaf1917c97b4759 Mon Sep 17 00:00:00 2001 From: yenatch Date: Thu, 7 Nov 2013 00:41:44 -0500 Subject: preprocessor: from_asm method and is_rgbasm_macro in command classes enjoy this half-assed implementation --- pokemontools/crystal.py | 16 ++++++++++++++++ pokemontools/preprocessor.py | 18 +++++++++++++----- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index dd2461a..e5ffd80 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -764,6 +764,10 @@ class SingleByteParam(): else: return str(self.byte) + @staticmethod + def from_asm(value): + return value + class DollarSignByte(SingleByteParam): def to_asm(self): return hex(self.byte).replace("0x", "$") @@ -822,6 +826,10 @@ class MultiByteParam(): decimal = int("0x"+"".join([("%.2x")%x for x in reversed(self.bytes)]), 16) return str(decimal) + @staticmethod + def from_asm(value): + return value + class PointerLabelParam(MultiByteParam): # default size is 2 bytes @@ -1000,6 +1008,7 @@ class RAMAddressParam(MultiByteParam): class MoneyByteParam(MultiByteParam): size = 3 + byte_type = "db" max_value = 0x0F423F should_be_decimal = True def parse(self): @@ -1236,6 +1245,7 @@ class Command: # use this when the "byte id" doesn't matter # .. for example, a non-script command doesn't use the "byte id" override_byte_check = False + is_rgbasm_macro = False base_label = "UnseenLabel_" def __init__(self, address=None, *pargs, **kwargs): @@ -1723,6 +1733,7 @@ class TextCommand(Command): #def get_dependencies(self, recompute=False, global_dependencies=set()): # return [] + # this is a regular command in a TextScript for writing text # but unlike other macros that preprocessor.py handles, # the preprocessor-parser is custom and MainText is not @@ -2376,6 +2387,7 @@ class BigEndianParam: """big-endian word""" size = 2 should_be_decimal = False + byte_type = "bigdw" def __init__(self, *args, **kwargs): self.prefix = '$' @@ -2394,6 +2406,10 @@ class BigEndianParam: decimal = int("0x"+"".join([("%.2x")%x for x in self.bytes]), 16) return str(decimal) + @staticmethod + def from_asm(value): + return value + class DecimalBigEndianParam(BigEndianParam): should_be_decimal = True diff --git a/pokemontools/preprocessor.py b/pokemontools/preprocessor.py index 5fe0851..f4e92b6 100644 --- a/pokemontools/preprocessor.py +++ b/pokemontools/preprocessor.py @@ -556,8 +556,6 @@ class Preprocessor(object): # remove trailing newline if line[-1] == "\n": line = line[:-1] - else: - original_line += "\n" # remove first tab has_tab = False @@ -589,6 +587,12 @@ class Preprocessor(object): sys.stdout.write(original_line) return + # rgbasm can handle other macros too + if "is_rgbasm_macro" in dir(macro): + if macro.is_rgbasm_macro: + sys.stdout.write(original_line) + return + # certain macros don't need an initial byte written # do: all scripting macros # don't: signpost, warp_def, person_event, xy_trigger @@ -608,7 +612,7 @@ class Preprocessor(object): index = 0 while index < len(params): param_type = macro.param_types[index - correction] - description = param_type["name"] + description = param_type["name"].strip() param_klass = param_type["class"] byte_type = param_klass.byte_type # db or dw size = param_klass.size @@ -638,7 +642,7 @@ class Preprocessor(object): index += 2 correction += 1 elif size == 3 and "from_asm" in dir(param_klass): - output += ("db " + param_klass.from_asm(param) + "\n") + output += ("\t" + byte_type + " " + param_klass.from_asm(param) + "\n") index += 1 else: raise exceptions.MacroException( @@ -649,9 +653,13 @@ class Preprocessor(object): ) ) + elif "from_asm" in dir(param_klass): + output += ("\t" + byte_type + " " + param_klass.from_asm(param) + " ; " + description + "\n") + index += 1 + # or just print out the byte else: - output += (byte_type + " " + param + " ; " + description + "\n") + output += ("\t" + byte_type + " " + param + " ; " + description + "\n") index += 1 -- cgit v1.2.3 From 2d30d0bca7905b1e95c8af0eb43208642b3df0a5 Mon Sep 17 00:00:00 2001 From: yenatch Date: Thu, 7 Nov 2013 00:48:43 -0500 Subject: crystal: use rom.interval instead of rom_interval rom_interval got broken when globals got nuked it's probably a bad idea to keep the function around but it's supposed to work eventually --- pokemontools/crystal.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index e5ffd80..d8f254a 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -174,7 +174,7 @@ def how_many_until(byte, starting, rom): def load_map_group_offsets(map_group_pointer_table, map_group_count, rom=None): """reads the map group table for the list of pointers""" map_group_offsets = [] # otherwise this method can only be used once - data = rom_interval(map_group_pointer_table, map_group_count*2, strings=False, rom=rom) + data = rom.interval(map_group_pointer_table, map_group_count*2, strings=False, rom=rom) data = helpers.grouper(data) for pointer_parts in data: pointer = pointer_parts[0] + (pointer_parts[1] << 8) @@ -548,7 +548,7 @@ def parse_text_from_bytes(bytes, debug=True, japanese=False): def parse_text_at(address, count=10, debug=True): """returns a list of bytes from an address see parse_text_at2 for pretty printing""" - return parse_text_from_bytes(rom_interval(address, count, strings=False), debug=debug) + return parse_text_from_bytes(rom.interval(address, count, strings=False), debug=debug) def parse_text_at2(address, count=10, debug=True, japanese=False): """returns a string of text from an address @@ -569,7 +569,7 @@ def parse_text_at3(address, map_group=None, map_id=None, debug=False): def rom_text_at(address, count=10): """prints out raw text from the ROM like for 0x112110""" - return "".join([chr(x) for x in rom_interval(address, count, strings=False)]) + return "".join([chr(x) for x in rom.interval(address, count, strings=False)]) def get_map_constant_label(map_group=None, map_id=None, map_internal_ids=None): """returns PALLET_TOWN for some map group/id pair""" @@ -807,7 +807,7 @@ class MultiByteParam(): self.parse() def parse(self): - self.bytes = rom_interval(self.address, self.size, strings=False) + self.bytes = rom.interval(self.address, self.size, strings=False) self.parsed_number = self.bytes[0] + (self.bytes[1] << 8) if hasattr(self, "bank"): self.parsed_address = calculate_pointer_from_bytes_at(self.address, bank=self.bank) @@ -1780,7 +1780,7 @@ class MainText(TextCommand): # read the text bytes into a structure # skip the first offset byte because that's the command byte - self.bytes = rom_interval(offset, jump, strings=False) + self.bytes = rom.interval(offset, jump, strings=False) # include the original command in the size calculation self.size = jump @@ -2396,7 +2396,7 @@ class BigEndianParam: self.parse() def parse(self): - self.bytes = rom_interval(self.address, 2, strings=False) + self.bytes = rom.interval(self.address, 2, strings=False) self.parsed_number = self.bytes[0] * 0x100 + self.bytes[1] def to_asm(self): @@ -4323,7 +4323,7 @@ class Signpost(Command): address = self.address bank = self.bank self.last_address = self.address + self.size - bytes = rom_interval(self.address, self.size) #, signpost_byte_size) + bytes = rom.interval(self.address, self.size) #, signpost_byte_size) self.y = int(bytes[0], 16) self.x = int(bytes[1], 16) @@ -4577,7 +4577,7 @@ class SecondMapHeader: def parse(self): address = self.address - bytes = rom_interval(address, second_map_header_byte_size, strings=False) + bytes = rom.interval(address, second_map_header_byte_size, strings=False) size = second_map_header_byte_size # for later @@ -5390,7 +5390,7 @@ class MapBlockData: os.mkdir(self.maps_path) if not os.path.exists(map_path): # dump to file - #bytes = rom_interval(self.address, self.width.byte*self.height.byte, strings=True) + #bytes = rom.interval(self.address, self.width.byte*self.height.byte, strings=True) bytes = rom[self.address : self.address + self.width.byte*self.height.byte] file_handler = open(map_path, "w") file_handler.write(bytes) @@ -5458,7 +5458,7 @@ class MapEventHeader: # signposts signpost_count = ord(rom[after_triggers]) signpost_byte_count = signpost_byte_size * signpost_count - # signposts = rom_interval(after_triggers+1, signpost_byte_count) + # signposts = rom.interval(after_triggers+1, signpost_byte_count) signposts = parse_signposts(after_triggers+1, signpost_count, bank=bank, map_group=map_group, map_id=map_id, debug=debug) after_signposts = after_triggers + 1 + signpost_byte_count self.signpost_count = signpost_count @@ -5467,7 +5467,7 @@ class MapEventHeader: # people events people_event_count = ord(rom[after_signposts]) people_event_byte_count = people_event_byte_size * people_event_count - # people_events_bytes = rom_interval(after_signposts+1, people_event_byte_count) + # people_events_bytes = rom.interval(after_signposts+1, people_event_byte_count) # people_events = parse_people_event_bytes(people_events_bytes, address=after_signposts+1, map_group=map_group, map_id=map_id) people_events = parse_people_events(after_signposts+1, people_event_count, bank=pointers.calculate_bank(after_signposts+2), map_group=map_group, map_id=map_id, debug=debug) self.people_event_count = people_event_count @@ -5628,7 +5628,7 @@ class MapScriptHeader: self.trigger_count = ord(rom[address]) self.triggers = [] ptr_line_size = 4 - groups = helpers.grouper(rom_interval(address+1, self.trigger_count * ptr_line_size, strings=False), count=ptr_line_size) + groups = helpers.grouper(rom.interval(address+1, self.trigger_count * ptr_line_size, strings=False), count=ptr_line_size) current_address = address+1 for (index, trigger_bytes) in enumerate(groups): logging.debug( -- cgit v1.2.3 From 5612a982abb7b5de72868ca7ccaa89641c6e3d22 Mon Sep 17 00:00:00 2001 From: yenatch Date: Thu, 7 Nov 2013 00:58:58 -0500 Subject: add some extra music command classes --- pokemontools/crystal.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index d8f254a..e4238ba 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -2411,7 +2411,7 @@ class BigEndianParam: return value class DecimalBigEndianParam(BigEndianParam): - should_be_decimal = True + should_be_decimal = True music_commands = { 0xD0: ["octave 8"], @@ -2504,6 +2504,31 @@ def create_music_command_classes(debug=False): music_classes = create_music_command_classes() +class OctaveParam(DecimalParam): + @staticmethod + def from_asm(value): + value = int(value) + return hex(0xd8 - value).replace("0x", "$") + +class OctaveCommand(Command): + macro_name = "octave" + size = 0 + end = False + param_types = { + 0: {"name": "octave", "class": OctaveParam}, + } + allowed_lengths = [1] + override_byte_check = True + +class ChannelCommand(Command): + macro_name = "channel" + size = 3 + override_byte_check = True + param_types = { + 0: {"name": "id", "class": DecimalParam}, + 1: {"name": "address", "class": PointerLabelParam}, + } + class callchannel(Command): id = 0xFD macro_name = "callchannel" -- cgit v1.2.3 From a9aae368887caac706def81f0b991c7e778ad341 Mon Sep 17 00:00:00 2001 From: yenatch Date: Thu, 7 Nov 2013 01:00:14 -0500 Subject: fix spacing for pokered music command classes --- pokemontools/crystal.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index e4238ba..cdab01f 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -2529,22 +2529,26 @@ class ChannelCommand(Command): 1: {"name": "address", "class": PointerLabelParam}, } + +# pokered + class callchannel(Command): - id = 0xFD - macro_name = "callchannel" - size = 3 - param_types = { - 0: {"name": "address", "class": PointerLabelParam}, - } + id = 0xFD + macro_name = "callchannel" + size = 3 + param_types = { + 0: {"name": "address", "class": PointerLabelParam}, + } class loopchannel(Command): - id = 0xFE - macro_name = "loopchannel" - size = 4 - param_types = { - 0: {"name": "count", "class": SingleByteParam}, - 1: {"name": "address", "class": PointerLabelParam}, - } + id = 0xFE + macro_name = "loopchannel" + size = 4 + param_types = { + 0: {"name": "count", "class": SingleByteParam}, + 1: {"name": "address", "class": PointerLabelParam}, + } + effect_commands = { 0x1: ['checkturn'], -- cgit v1.2.3 From 6b434d223046af3cb93cc23a34c96aaace38ba35 Mon Sep 17 00:00:00 2001 From: yenatch Date: Thu, 7 Nov 2013 02:11:00 -0500 Subject: oops forgot the cries --- pokemontools/audio.py | 8 ++++++++ pokemontools/cry_names.py | 4 ++++ 2 files changed, 12 insertions(+) create mode 100644 pokemontools/cry_names.py diff --git a/pokemontools/audio.py b/pokemontools/audio.py index 247ef87..00d3fdc 100644 --- a/pokemontools/audio.py +++ b/pokemontools/audio.py @@ -326,6 +326,14 @@ def generate_crystal_sfx_pointers(): from sfx_names import sfx_names return '\n'.join('\tdbw BANK({0}), {0}'.format('Sfx_' + label) for label in sfx_names) +def dump_crystal_cries(): + from cry_names import cry_names + dump_sounds(0xe91b0, cry_names, os.path.join(conf.path, 'audio', 'cries'), 'Cry_') + +def generate_crystal_cry_pointers(): + from cry_names import cry_names + return '\n'.join('\tdbw BANK({0}), {0}'.format('Cry_' + label) for label in cry_names) + if __name__ == '__main__': dump_crystal_music() diff --git a/pokemontools/cry_names.py b/pokemontools/cry_names.py new file mode 100644 index 0000000..bee335f --- /dev/null +++ b/pokemontools/cry_names.py @@ -0,0 +1,4 @@ +# coding: utf-8 + +cry_names = ['%X' % x for x in xrange(0x44)] + -- cgit v1.2.3 From 85fa5a06c012270cea3347ac19b052f7f8678975 Mon Sep 17 00:00:00 2001 From: yenatch Date: Fri, 8 Nov 2013 22:43:46 -0500 Subject: audio: sfx and cries are contiguous songs for the most part are independent sfx/cry headers are grouped together and most sound data is shared --- pokemontools/audio.py | 47 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/pokemontools/audio.py b/pokemontools/audio.py index 00d3fdc..8217cfb 100644 --- a/pokemontools/audio.py +++ b/pokemontools/audio.py @@ -277,24 +277,27 @@ class Sound: return '\n'.join(asm for address, asm, last_address in sort_asms(asms)) -def dump_sounds(origin, names, path, base_label='Sound_'): +def read_bank_address_pointer(addr): + bank, address = rom[addr], rom[addr+1] + rom[addr+2] * 0x100 + return get_global_address(address, bank) + + +def dump_sounds(origin, names, base_label='Sound_'): """Dump sound data from a pointer table.""" - def get_sound_at(addr): - bank, address = rom[addr], rom[addr+1] + rom[addr+2] * 0x100 - return get_global_address(address, bank) # first pass to grab labels and boundaries labels = [] addresses = [] for i, name in enumerate(names): - sound_at = get_sound_at(origin + i * 3) + sound_at = read_bank_address_pointer(origin + i * 3) sound = Sound(sound_at, base_label + name) labels += sound.labels addresses += [(sound.start_address, sound.last_address)] addresses = sorted(addresses) + outputs = [] for i, name in enumerate(names): - sound_at = get_sound_at(origin + i * 3) + sound_at = read_bank_address_pointer(origin + i * 3) sound = Sound(sound_at, base_label + name) output = sound.to_asm(labels) + '\n' @@ -307,12 +310,38 @@ def dump_sounds(origin, names, path, base_label='Sound_'): output += '\nINCBIN "baserom.gbc", $%x, $%x - $%x\n' % (sound.last_address, next_address, sound.last_address) filename = name.lower() + '.asm' + outputs += [(filename, output)] + return outputs + + +def export_sounds(origin, names, path, base_label='Sound_'): + for filename, output in dump_sounds(origin, names, base_label): with open(os.path.join(path, filename), 'w') as out: out.write(output) + +def dump_sound_clump(origin, names, base_label='Sound_'): + """some sounds are grouped together and/or share most components. + these can't reasonably be split into files for each sound.""" + + output = [] + for i, name in enumerate(names): + sound_at = read_bank_address_pointer(origin + i * 3) + sound = Sound(sound_at, base_label + name) + output += sound.asms + sound.labels + output = sort_asms(output) + return output + + +def export_sound_clump(origin, names, path, base_label='Sound_'): + output = dump_sound_clump(origin, names, base_label) + with open(path, 'w') as out: + out.write('\n'.join(y for x, y, z in output)) + + def dump_crystal_music(): from song_names import song_names - dump_sounds(0xe906e, song_names, os.path.join(conf.path, 'audio', 'music'), 'Music_') + export_sounds(0xe906e, song_names, os.path.join(conf.path, 'audio', 'music'), 'Music_') def generate_crystal_music_pointers(): from song_names import song_names @@ -320,7 +349,7 @@ def generate_crystal_music_pointers(): def dump_crystal_sfx(): from sfx_names import sfx_names - dump_sounds(0xe927c, sfx_names, os.path.join(conf.path, 'audio', 'sfx'), 'Sfx_') + export_sound_clump(0xe927c, sfx_names, os.path.join(conf.path, 'audio', 'sfx.asm'), 'Sfx_') def generate_crystal_sfx_pointers(): from sfx_names import sfx_names @@ -328,7 +357,7 @@ def generate_crystal_sfx_pointers(): def dump_crystal_cries(): from cry_names import cry_names - dump_sounds(0xe91b0, cry_names, os.path.join(conf.path, 'audio', 'cries'), 'Cry_') + export_sound_clump(0xe91b0, cry_names, os.path.join(conf.path, 'audio', 'cries.asm'), 'Cry_') def generate_crystal_cry_pointers(): from cry_names import cry_names -- cgit v1.2.3 From e7385d6f04b20aa4faa5c068238bd3a227164bc4 Mon Sep 17 00:00:00 2001 From: yenatch Date: Fri, 8 Nov 2013 22:49:25 -0500 Subject: audio: less ambiguous tuple unpacking --- pokemontools/audio.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pokemontools/audio.py b/pokemontools/audio.py index 8217cfb..e427ec8 100644 --- a/pokemontools/audio.py +++ b/pokemontools/audio.py @@ -182,8 +182,8 @@ class Channel: for i, (address, asm, last_address) in enumerate(output): if ':' in asm: # dont print labels for empty chunks - for (x, y, z) in output[i:]: - if ':' not in y: + for (address_, asm_, last_address_) in output[i:]: + if ':' not in asm_: text += '\n' + asm + '\n' break else: @@ -336,7 +336,7 @@ def dump_sound_clump(origin, names, base_label='Sound_'): def export_sound_clump(origin, names, path, base_label='Sound_'): output = dump_sound_clump(origin, names, base_label) with open(path, 'w') as out: - out.write('\n'.join(y for x, y, z in output)) + out.write('\n'.join(asm for address, asm, last_address in output)) def dump_crystal_music(): -- cgit v1.2.3 From 3013246d69fb83b282a93ee45beebab852fcb9ab Mon Sep 17 00:00:00 2001 From: yenatch Date: Fri, 8 Nov 2013 22:49:47 -0500 Subject: cry_names: zfill(2) --- pokemontools/cry_names.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemontools/cry_names.py b/pokemontools/cry_names.py index bee335f..af08fe1 100644 --- a/pokemontools/cry_names.py +++ b/pokemontools/cry_names.py @@ -1,4 +1,4 @@ # coding: utf-8 -cry_names = ['%X' % x for x in xrange(0x44)] +cry_names = ['%.2X' % x for x in xrange(0x44)] -- cgit v1.2.3 From 7c4f12dec9e2e5049a6f6ade4e3d51356e2ffeca Mon Sep 17 00:00:00 2001 From: yenatch Date: Wed, 13 Nov 2013 12:45:28 -0500 Subject: audio: noise command --- pokemontools/audio.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pokemontools/audio.py b/pokemontools/audio.py index e427ec8..1ace4b1 100644 --- a/pokemontools/audio.py +++ b/pokemontools/audio.py @@ -98,6 +98,20 @@ class Note(Command): return True +class Noise(Note): + macro_name = "noise" + size = 0 + end = False + param_types = { + 0: {"name": "duration", "class": LoNybbleParam}, + 1: {"name": "intensity", "class": SingleByteParam}, + 2: {"name": "frequency", "class": MultiByteParam}, + } + allowed_lengths = [2,3] + override_byte_check = True + is_rgbasm_macro = False + + class Channel: """A sound channel data parser.""" @@ -195,6 +209,7 @@ class Channel: for class_ in sound_classes: if class_.id == i: return class_ + if self.channel in [4. 8]: return Noise return Note -- cgit v1.2.3 From f5ad14d634973ff6b340853df8b61bbdf61e0060 Mon Sep 17 00:00:00 2001 From: yenatch Date: Wed, 13 Nov 2013 12:46:18 -0500 Subject: audio: pass in a channel id to sound commands --- pokemontools/audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemontools/audio.py b/pokemontools/audio.py index 1ace4b1..1cce1fe 100644 --- a/pokemontools/audio.py +++ b/pokemontools/audio.py @@ -131,7 +131,7 @@ class Channel: while not done: cmd = rom[self.address] - class_ = self.get_sound_class(cmd)(address=self.address) + class_ = self.get_sound_class(cmd)(address=self.address, channel=self.channel) # notetype loses the intensity param on channel 4 if class_.macro_name == 'notetype': -- cgit v1.2.3