mirror of
https://github.com/mitch030504/AstroQuest-Frame.git
synced 2026-10-23 05:01:22 +02:00
On top of the shadps4-arm64 core imported in the previous commit: - PlayStation VR emulation: headset, tracker, reprojection, camera and social screen; the player's seat and view reset; head velocity for head-butts; field of view settings - Sound: a native Ngs2 engine (samplers, submixers, effects), the Audio3d spatializer, virtual surround for headset speakers, microphone pass-through; libSceJson2; libSceFiber on FEX - Fixes for CUSA12392 under emulation: real-time step, pace and resolution governor, larger render sizes on the PC, save loading at start - Renderer: geometry shaders fed with fans/polygons/loops, cube-array layers of light probes, sample-count mismatches on Adreno, timeline semaphore hang, FXAA in place of the MSAA resolve, sharpening, VR frame export - quest-host: the Quest app (OpenXR host that runs the glibc core as a child process, frames through AHardwareBuffers, DualSense mapping, hand-tracked controller placement, KGSL shim) - PC: in-process OpenXR host (Virtual Desktop or any OpenXR runtime), Touch controllers as the gamepad, launcher with a settings window; vendored OpenXR SDK 1.1.63 - Builds keep the build machine's folders out of the binaries (-ffile-prefix-map); machine settings (Python, headset serial, signing key) live in git-ignored tools/*.local files - tools: build, packaging and test scripts for Windows and the headset - README with installation, license (GPL-2.0-or-later), third-party notices and thanks No game files are part of the repository; .gitignore only lets listed paths in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
3.5 KiB
Python
96 lines
3.5 KiB
Python
"""Looks at a WAV file without a speaker: loudness over time, clipping, and a spectrogram picture.
|
|
|
|
python wavstat.py <file.wav> [--png out.png] [--from s] [--to s] [--step s]
|
|
"""
|
|
import sys
|
|
import wave
|
|
|
|
import numpy as np
|
|
|
|
|
|
def load(path):
|
|
with wave.open(path, "rb") as handle:
|
|
channels = handle.getnchannels()
|
|
rate = handle.getframerate()
|
|
width = handle.getsampwidth()
|
|
frames = handle.getnframes()
|
|
raw = handle.readframes(frames)
|
|
if width != 2:
|
|
raise SystemExit(f"{path}: only 16-bit files are handled")
|
|
data = np.frombuffer(raw, dtype="<i2").astype(np.float32) / 32768.0
|
|
return data.reshape(-1, channels), rate
|
|
|
|
|
|
def db(value):
|
|
return 20.0 * np.log10(max(value, 1e-9))
|
|
|
|
|
|
def main():
|
|
args = sys.argv[1:]
|
|
if not args:
|
|
raise SystemExit(__doc__)
|
|
path = args[0]
|
|
option = lambda name, default=None: args[args.index(name) + 1] if name in args else default
|
|
data, rate = load(path)
|
|
start = float(option("--from", 0))
|
|
end = float(option("--to", len(data) / rate))
|
|
step = float(option("--step", max((end - start) / 24.0, 0.25)))
|
|
data = data[int(start * rate):int(end * rate)]
|
|
channels = data.shape[1]
|
|
seconds = len(data) / rate
|
|
print(f"{path}: {channels} ch, {rate} Hz, {seconds:.1f} s")
|
|
if len(data) == 0:
|
|
return
|
|
|
|
peak = np.abs(data).max(axis=0)
|
|
rms = np.sqrt((data ** 2).mean(axis=0))
|
|
clipped = (np.abs(data) >= 0.999).sum(axis=0)
|
|
for c in range(channels):
|
|
print(f" ch{c}: peak {db(peak[c]):6.1f} dB rms {db(rms[c]):6.1f} dB clipped {clipped[c]}")
|
|
|
|
mono = data[:, :2].mean(axis=1) if channels >= 2 else data[:, 0]
|
|
print(" loudness over time (dB rms of ch0+ch1, one column per %.2f s):" % step)
|
|
window = int(step * rate)
|
|
row = []
|
|
for at in range(0, len(mono) - window + 1, window):
|
|
chunk = mono[at:at + window]
|
|
row.append(db(float(np.sqrt((chunk ** 2).mean()))))
|
|
for at in range(0, len(row), 12):
|
|
print(" %6.1fs " % (start + at * step) + " ".join("%6.1f" % v for v in row[at:at + 12]))
|
|
|
|
# Where the energy sits: a rough spectrum of the whole excerpt.
|
|
size = 4096
|
|
if len(mono) >= size:
|
|
frames = [mono[i:i + size] * np.hanning(size) for i in range(0, len(mono) - size, size // 2)]
|
|
spectrum = np.abs(np.fft.rfft(np.array(frames), axis=1)) ** 2
|
|
mean = spectrum.mean(axis=0)
|
|
freqs = np.fft.rfftfreq(size, 1.0 / rate)
|
|
bands = [(20, 100), (100, 300), (300, 1000), (1000, 3000), (3000, 8000), (8000, 20000)]
|
|
total = mean.sum() + 1e-20
|
|
print(" energy by band: " + " ".join(
|
|
"%d-%dHz %4.1f%%" % (lo, hi, 100.0 * mean[(freqs >= lo) & (freqs < hi)].sum() / total)
|
|
for lo, hi in bands))
|
|
|
|
png = option("--png")
|
|
if png:
|
|
import matplotlib
|
|
matplotlib.use("Agg")
|
|
import matplotlib.pyplot as plt
|
|
figure, axes = plt.subplots(2, 1, figsize=(14, 7), gridspec_kw={"height_ratios": [1, 3]})
|
|
times = np.arange(len(mono)) / rate + start
|
|
stride = max(len(mono) // 20000, 1)
|
|
axes[0].plot(times[::stride], mono[::stride], linewidth=0.4)
|
|
axes[0].set_xlim(times[0], times[-1])
|
|
axes[0].set_ylim(-1, 1)
|
|
axes[1].specgram(mono, NFFT=2048, Fs=rate, noverlap=1024, xextent=(times[0], times[-1]),
|
|
vmin=-120, vmax=-30, cmap="magma")
|
|
axes[1].set_ylim(0, 12000)
|
|
axes[1].set_xlabel("seconds")
|
|
figure.tight_layout()
|
|
figure.savefig(png, dpi=80)
|
|
print(f" wrote {png}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|