874b6f20c2
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
168 lines
6.0 KiB
Python
168 lines
6.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Compare the music manager with the vanilla 26.3 client.
|
|
|
|
The oracle runs the real `MusicManager` over the real `SoundEngine` (OpenAL
|
|
Soft "No Output" device, see VanillaSoundRig). The situation
|
|
(`getSituationalMusic`, `getMusicVolume`), the loading screen, pauses, the
|
|
end of a track, `stopPlaying`, `setMinutesBetweenSongs`, the music category
|
|
volume and `showNowPlayingToastIfNeeded` are script inputs; every step prints
|
|
the current track, `nextSongDelay`, `currentGain`, `toastShown`, the toast
|
|
calls, the MUSIC category gain and the music channel's AL source. Horizon's
|
|
`MusicManager` and `SoundEngine` replay the scripts. Tracks use events whose
|
|
variants share one volume, since `forMusic` resolves with an unseeded random.
|
|
"""
|
|
import random
|
|
|
|
try:
|
|
from . import audio_harness as h
|
|
except ImportError:
|
|
import audio_harness as h
|
|
|
|
TRACKS = ['minecraft:music.credits', 'minecraft:music.dragon', 'minecraft:music.end', 'minecraft:music.under_water',
|
|
'minecraft:music.overworld.swamp', 'minecraft:music_disc.cat', 'minecraft:music_disc.13']
|
|
ODD = ['minecraft:music.nether.warped_forest', 'minecraft:not.a.track']
|
|
|
|
|
|
class Script:
|
|
def __init__(self):
|
|
self.lines = []
|
|
|
|
def add(self, *parts):
|
|
self.lines.append(' '.join(map(str, parts)))
|
|
|
|
def situation(self, sound=None, lo=0, hi=0, replace=0, volume=1.0):
|
|
self.add('situation', '-' if sound is None else f'{sound},{lo},{hi},{replace}', h.bits(volume))
|
|
|
|
def tick(self, n=1, loading=0, paused=0):
|
|
for _ in range(n):
|
|
self.add('tick', loading, paused)
|
|
|
|
|
|
def scripted(s):
|
|
end, dragon, water, credits, cat = TRACKS[2], TRACKS[1], TRACKS[3], TRACKS[0], TRACKS[5]
|
|
s.add('start', 1, 'DEFAULT')
|
|
# Menu-like music: 20..600 ticks, replacing.
|
|
s.situation(end, 20, 600, 1)
|
|
s.tick(3, loading=1)
|
|
s.tick(120)
|
|
s.add('finish')
|
|
s.tick(650)
|
|
# Game music does not replace, and the next delay comes from its range.
|
|
s.situation(water, 12000, 24000, 0)
|
|
s.tick(5)
|
|
s.add('finish')
|
|
s.tick(30)
|
|
s.add('freq', 'FREQUENT')
|
|
s.tick(3)
|
|
s.add('freq', 'CONSTANT')
|
|
s.tick(110)
|
|
s.add('freq', 'DEFAULT')
|
|
s.tick(2)
|
|
# A replacing track stops the current one over the following ticks.
|
|
s.situation(dragon, 0, 0, 1)
|
|
s.tick(30)
|
|
s.situation(credits, 0, 0, 1)
|
|
s.tick(30)
|
|
s.situation(None)
|
|
s.tick(5)
|
|
s.add('finish')
|
|
s.tick(5)
|
|
s.situation(end, 6000, 24000, 1)
|
|
s.tick(130)
|
|
# Music volume fades: down to silence, then up from a partial volume.
|
|
s.situation(end, 6000, 24000, 1, volume=0.0)
|
|
s.tick(320)
|
|
s.situation(cat, 0, 0, 1, volume=0.25)
|
|
s.tick(5)
|
|
s.situation(cat, 0, 0, 1, volume=1.0)
|
|
s.tick(400)
|
|
s.situation(cat, 0, 0, 1, volume=0.6)
|
|
s.tick(80)
|
|
# Silent starts and the toast, stops, pauses.
|
|
s.add('volume', 1, h.bits(0.0))
|
|
s.add('stop')
|
|
s.situation(dragon, 0, 0, 1)
|
|
s.tick(105)
|
|
s.add('toast')
|
|
s.add('volume', 1, h.bits(0.5))
|
|
s.add('toast')
|
|
s.tick(2)
|
|
s.add('stopmusic', f'{end},0,0,1')
|
|
s.add('stopmusic', f'{dragon},0,0,1')
|
|
s.tick(105, paused=1)
|
|
s.add('finish')
|
|
s.tick(3, paused=1)
|
|
s.tick(5)
|
|
s.add('stopall')
|
|
s.tick(5)
|
|
# Events that cannot play.
|
|
for event in ODD:
|
|
s.add('start', 7, 'DEFAULT')
|
|
s.situation(event, 0, 40, 1)
|
|
s.tick(150)
|
|
# Real Musics delays over a long session.
|
|
s.add('start', 99, 'DEFAULT')
|
|
s.situation(water, 12000, 24000, 0)
|
|
s.tick(130)
|
|
s.add('finish')
|
|
s.tick(24100)
|
|
|
|
|
|
def fuzz(s, rng, steps):
|
|
s.add('start', rng.getrandbits(63), rng.choice(['DEFAULT', 'FREQUENT', 'CONSTANT']))
|
|
for _ in range(steps):
|
|
op = rng.random()
|
|
if op < 0.08:
|
|
sound = rng.choice(TRACKS + ODD[:1]) if rng.random() < 0.9 else None
|
|
lo = rng.choice([0, 20, 100, 300])
|
|
s.situation(sound, lo, lo + rng.choice([0, 50, 280, 600]), int(rng.random() < 0.5),
|
|
rng.choice([1.0, 1.0, 1.0, 0.0, 0.5, rng.random()]))
|
|
elif op < 0.13:
|
|
s.add('finish')
|
|
elif op < 0.15:
|
|
s.add('stop')
|
|
elif op < 0.16:
|
|
s.add('stopmusic', f'{rng.choice(TRACKS)},0,0,1')
|
|
elif op < 0.17:
|
|
s.add('freq', rng.choice(['DEFAULT', 'FREQUENT', 'CONSTANT']))
|
|
elif op < 0.18:
|
|
s.add('toast')
|
|
elif op < 0.20:
|
|
s.add('volume', rng.choice([0, 1]), h.bits(rng.choice([0.0, 1.0, 0.5])))
|
|
elif op < 0.21:
|
|
s.add('stopall')
|
|
elif op < 0.25:
|
|
s.tick(rng.randrange(1, 30), loading=1)
|
|
elif op < 0.28:
|
|
s.tick(rng.randrange(1, 30), paused=1)
|
|
else:
|
|
s.tick(rng.randrange(1, 60))
|
|
|
|
|
|
def fixtures():
|
|
s = Script()
|
|
scripted(s)
|
|
rng = random.Random(0x4d55534943)
|
|
for _ in range(60):
|
|
fuzz(s, rng, 120)
|
|
return s.lines
|
|
|
|
|
|
def main():
|
|
lines = fixtures()
|
|
matched, report = h.run_harness(
|
|
'music', 'VanillaMusicOracle', 'music_fixture', ''.join(l + '\n' for l in lines),
|
|
extra_sources=[h.lab.ROOT / 'mods/horizon/src/test/java/dev/horizonlab/parity/VanillaSoundRig.java',
|
|
h.lab.ROOT / 'crates/horizon-client/src/music.rs',
|
|
h.lab.ROOT / 'crates/horizon-client/examples/probe/audio_common/mod.rs'],
|
|
java_args=[h.RESOURCES], rust_args=[h.RESOURCES],
|
|
scope='Real MusicManager.tick/startPlaying/stopPlaying/fadePlaying/setMinutesBetweenSongs/showNowPlayingToastIfNeeded with a seeded '
|
|
'random over the real SoundEngine: track starts and replacements, nextSongDelay draws, the three music frequencies, the loading '
|
|
'screen hold, fades and the MUSIC category gain, silent starts, toasts, pauses, track ends and unplayable events. The situation '
|
|
'(getSituationalMusic/getMusicVolume) is an input here; see test_audio_attributes_parity.py for the attribute values.')
|
|
print(f'{matched:,} music lines match the pinned client ({len(lines):,} steps): {report}', flush=True)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|