Skip to content

Instantly share code, notes, and snippets.

@KevinTyrrell
Last active August 20, 2024 01:13
Show Gist options
  • Save KevinTyrrell/8822b2cdede6b955a29452f64b0c4848 to your computer and use it in GitHub Desktop.
Save KevinTyrrell/8822b2cdede6b955a29452f64b0c4848 to your computer and use it in GitHub Desktop.
Information regarding Ocarina of Time & Majora's Mask songs in regards to which button combinations unlock access to which songs. This is relevant in randomizers like the Ocarina of Time+MM Randomizer where you are able to unlock Ocarina note buttons progressively (see: https://ootmm.com/).
== Song Note Breakdown ==
[10] Songs Require [A]: Sonata of Awakening, Goron Lullaby, Bolero of Fire, Serenade of Water, Song of Storms, Oath to Order, Minuet of Forest, Nocturne of Shadow, Song of Time, Requiem of Spirit
[13] Songs Require [LEFT]: Sonata of Awakening, Goron Lullaby, Serenade of Water, Saria's Song, Song of Soaring, Song of Healing, Zelda's Lullaby, Prelude of Light, Epona's Song, New Wave Bossa Nova, Minuet of Forest, Elegy of Emptiness, Nocturne of Shadow
[17] Songs Require [RIGHT]: Sun's Song, Sonata of Awakening, Goron Lullaby, Bolero of Fire, Serenade of Water, Saria's Song, Song of Healing, Zelda's Lullaby, Prelude of Light, Epona's Song, New Wave Bossa Nova, Oath to Order, Minuet of Forest, Elegy of Emptiness, Nocturne of Shadow, Song of Time, Requiem of Spirit
[13] Songs Require [DOWN]: Sun's Song, Bolero of Fire, Serenade of Water, Saria's Song, Song of Soaring, Song of Healing, New Wave Bossa Nova, Song of Storms, Oath to Order, Elegy of Emptiness, Nocturne of Shadow, Song of Time, Requiem of Spirit
[11] Songs Require [UP]: Sun's Song, Sonata of Awakening, Song of Soaring, Zelda's Lullaby, Prelude of Light, Song of Storms, Epona's Song, New Wave Bossa Nova, Minuet of Forest, Elegy of Emptiness, Oath to Order
=-= All Five Note Songs: None =-=
=-= All Cardinal Directions Songs: =-=
New Wave Bossa Nova, Elegy of Emptiness
-- [3] Notes Available: --
Notes: A, LEFT, RIGHT
Songs: Goron Lullaby
Notes: A, RIGHT, DOWN
Songs: Bolero of Fire, Song of Time, Requiem of Spirit
Notes: A, DOWN, UP
Songs: Song of Storms
Notes: LEFT, RIGHT, DOWN
Songs: Song of Healing, Saria's Song
Notes: LEFT, RIGHT, UP
Songs: Zelda's Lullaby, Epona's Song, Prelude of Light
Notes: LEFT, DOWN, UP
Songs: Song of Soaring
Notes: RIGHT, DOWN, UP
Songs: Sun's Song
-- [4] Notes Available: --
Notes: A, LEFT, RIGHT, DOWN
Songs: Goron Lullaby, Bolero of Fire, Serenade of Water, Saria's Song, Song of Healing, Nocturne of Shadow, Song of Time, Requiem of Spirit
Notes: A, LEFT, RIGHT, UP
Songs: Sonata of Awakening, Goron Lullaby, Zelda's Lullaby, Prelude of Light, Epona's Song, Minuet of Forest
Notes: A, LEFT, DOWN, UP
Songs: Song of Soaring, Song of Storms
Notes: A, RIGHT, DOWN, UP
Songs: Sun's Song, Bolero of Fire, Song of Storms, Oath to Order, Song of Time, Requiem of Spirit
Notes: LEFT, RIGHT, DOWN, UP
Songs: Sun's Song, Saria's Song, Song of Soaring, Song of Healing, Zelda's Lullaby, Prelude of Light, Epona's Song, New Wave Bossa Nova, Elegy of Emptiness
-- [5] Notes Available: --
Notes: A, LEFT, RIGHT, DOWN, UP
Songs: Goron Lullaby, Minuet of Forest, Elegy of Emptiness, Serenade of Water, Song of Healing, Oath to Order, Requiem of Spirit, Saria's Song, Song of Soaring, Epona's Song, Sun's Song, Sonata of Awakening, Bolero of Fire, Zelda's Lullaby, Prelude of Light, Song of Storms, New Wave Bossa Nova, Nocturne of Shadow, Song of Time
"""
Copyright (C) 2024 Kevin Tyrrell
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
from collections import defaultdict
from typing import Dict, Tuple, Any
from enum import Enum
from itertools import combinations
class Button(Enum):
A=1
LEFT=2
RIGHT=3
DOWN=4
UP=5
def __repr__(self) -> str:
return self.name
_A, _L, _R, _D, _U = Button.A, Button.LEFT, Button.RIGHT, Button.DOWN, Button.UP
class Song(Enum):
ZELDA_LULLABY = ("Zelda's Lullaby", _L, _U, _R, _L, _U, _R)
EPONA_SONG = ("Epona's Song", _U, _L, _R, _U, _L, _R)
SARIA_SONG = ("Saria's Song", _D, _R, _L, _D, _R, _L)
SUN_SONG = ("Sun's Song", _R, _D, _U, _R, _D, _U)
SONG_OF_TIME = ("Song of Time", _R, _A, _D, _R, _A, _D)
SONG_OF_STORMS = ("Song of Storms", _A, _D, _U, _A, _D, _U)
MINUET_OF_FOREST = ("Minuet of Forest", _A, _U, _L, _R, _L, _R)
BOLERO_OF_FIRE = ("Bolero of Fire", _D, _A, _D, _A, _R, _D, _R, _D)
SERENADE_OF_WATER = ("Serenade of Water", _A, _D, _R, _R, _L)
NOCTURNE_OF_SHADOW = ("Nocturne of Shadow", _L, _R, _R, _A, _L, _R, _D)
REQUIEM_OF_SPIRIT = ("Requiem of Spirit", _A, _D, _A, _R, _D, _A)
PRELUDE_OF_LIGHT = ("Prelude of Light", _U, _R, _U, _R, _L, _U)
# Song of Time ignored as it is the same in both games.
# Inverted Song of Time ignored as it contains identical notes to SoT.
# Song of Double Time ignored as it contains identical notes to SoT.
SONG_OF_HEALING = ("Song of Healing", _L, _R, _D, _L, _R, _D)
# Epona's Song ignored as it is the same in both games.
SONG_OF_SOARING = ("Song of Soaring", _D, _L, _U, _D, _L, _U)
# Song of Storms ignored as it is the same in both games.
SONATA_OF_AWAKENING = ("Sonata of Awakening", _U, _L, _U, _L, _A, _R, _A)
GORON_LULLABY = ("Goron Lullaby", _A, _R, _L, _A, _R, _L, _R, _A)
NEW_WAVE_BOSSA_NOVA = ("New Wave Bossa Nova", _L, _U, _L, _R, _D, _L, _R)
ELEGY_OF_EMPTINESS = ("Elegy of Emptiness", _R, _L, _R, _D, _R, _U, _L)
OATH_TO_ORDER = ("Oath to Order", _R, _D, _A, _D, _R, _U)
def __new__(cls, title: str, *notes: Button):
obj = object.__new__(cls) # Workaround for no enum constructor support
tpl = tuple(notes)
obj._value_ = (title, tpl)
obj.notes = tpl
obj.title = title
return obj
def __init__(self, name: str, *notes: Button):
self.__name: str = name
self.__notes: Tuple[Button, ...] = tuple(notes)
def __repr__(self) -> str:
return self.title
class SongPlayer:
_all_songs = set(Song)
_notes = set(Button)
_songs_by_note = {b: {s for s in Song if b in s.notes} for b in Button}
@classmethod
def get_songs_of_note(cls, b: Button) -> Tuple[Song, ...]:
return tuple(cls._songs_by_note[b])
@classmethod
def get_available_songs(cls, *buttons: Button) -> Tuple[Song, ...]:
missing_notes = cls._notes.difference(*buttons)
playable_songs = cls._all_songs
for note in missing_notes:
playable_songs = playable_songs - cls._songs_by_note[note]
return tuple(playable_songs)
def main(*args: Tuple[Any], **kwargs: Dict[Any, Any]):
print("== Song Note Breakdown ==")
for note in Button:
songs = SongPlayer.get_songs_of_note(note)
print(f"[{len(songs)}] Songs Require [{note.name}]: {', '.join(repr(x) for x in songs)}")
print()
print("=-= All Five Note Songs: None =-=\n")
full_songs = set(SongPlayer.get_songs_of_note(Button.UP))
for b in (Button.LEFT, Button.RIGHT, Button.DOWN):
full_songs = full_songs.intersection(SongPlayer.get_songs_of_note(b))
print(f"=-= All Cardinal Directions Songs: =-=\n\t{', '.join(repr(x) for x in full_songs)}\n")
note_combos = [cmb for i in range(1, len(Button) + 1) for cmb in combinations(Button, i)]
songs_by_cmb = {cmb: SongPlayer.get_available_songs(cmb) for cmb in note_combos}
songs_by_cmb = dict(filter(lambda t: t[-1] != (), songs_by_cmb.items()))
songs_by_note_count = defaultdict(list)
for k, v in songs_by_cmb.items():
songs_by_note_count[len(k)].append((k, v))
songs_by_note_count = dict(sorted(songs_by_note_count.items()))
for count, group in songs_by_note_count.items():
print(f"-- [{count}] Notes Available: --")
for cmb, songs in group:
print(f"\tNotes: {', '.join(repr(x) for x in cmb)}")
print(f"\t\tSongs: {', '.join(repr(x) for x in songs)}")
class _StringToObject:
__number_types, __mappings = [int, float, complex], {
"True": True, "False": False, "None": None }
@classmethod
def parse(cls, value: str) -> Any:
if value in cls.__mappings:
return cls.__mappings[value]
for builtin in cls.__number_types:
try: return builtin(value)
except ValueError: pass
return value
def __main():
from argparse import ArgumentParser
parser, pos_args, key_args = ArgumentParser(), [], {}
parser.add_argument("*", nargs="*")
for token in next(iter(vars(parser.parse_args()).values())):
if "=" in token:
k, v = map(lambda x: _StringToObject.parse(x), token.split("=", 1))
key_args[k] = v
else: pos_args.append(_StringToObject.parse(token))
main(*pos_args, **key_args)
if __name__ == '__main__':
__main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment