Spaces:
Sleeping
Sleeping
Complete the sampled kit provenance
Browse files
scripts/build_taiko_bank.py
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# /// script
|
| 2 |
+
# requires-python = ">=3.11"
|
| 3 |
+
# dependencies = ["numpy<2.1", "scipy", "soundfile"]
|
| 4 |
+
# ///
|
| 5 |
+
"""Build SoftChart's redistributable arcade Taiko one-shot bank.
|
| 6 |
+
|
| 7 |
+
The input directory must contain the pinned CC0 Freesound HQ OGG previews
|
| 8 |
+
listed in ``SOURCES``. This script is intentionally networkless: it verifies
|
| 9 |
+
every source hash, renders deterministic mono PCM16 assets, and writes a
|
| 10 |
+
provenance manifest. Commercial-game reference audio is never an input.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
|
| 15 |
+
import argparse
|
| 16 |
+
import hashlib
|
| 17 |
+
import json
|
| 18 |
+
import math
|
| 19 |
+
import wave
|
| 20 |
+
from dataclasses import dataclass
|
| 21 |
+
from fractions import Fraction
|
| 22 |
+
from pathlib import Path
|
| 23 |
+
|
| 24 |
+
import numpy as np
|
| 25 |
+
import soundfile as sf
|
| 26 |
+
from scipy.signal import butter, resample_poly, sosfilt
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
SAMPLE_RATE = 44_100
|
| 30 |
+
PACK_URL = "https://freesound.org/people/sazanami12/packs/45781/"
|
| 31 |
+
CC0_URL = "https://creativecommons.org/publicdomain/zero/1.0/"
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
@dataclass(frozen=True)
|
| 35 |
+
class SourceSpec:
|
| 36 |
+
sound_id: int
|
| 37 |
+
label: str
|
| 38 |
+
sha256: str
|
| 39 |
+
preview_url: str
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
SOURCES = {
|
| 43 |
+
856550: SourceSpec(856550, "center-soft", "78ed9dea56102480811e90a9653c08fe20a0eca5793c9c936f1c2592042b9a46", "https://cdn.freesound.org/previews/856/856550_17110092-hq.ogg"),
|
| 44 |
+
856552: SourceSpec(856552, "edge-soft", "85600ed1ddfab1c56fe07268f5fd68073706da8cd9a2f0cf7f16bde30bb8bb3e", "https://cdn.freesound.org/previews/856/856552_17110092-hq.ogg"),
|
| 45 |
+
856557: SourceSpec(856557, "rim-right", "6c24ee738d5995121fa85a432223a2afadb585f2a67b253747e2db0b3796bca0", "https://cdn.freesound.org/previews/856/856557_17110092-hq.ogg"),
|
| 46 |
+
856558: SourceSpec(856558, "rim-left", "5ac219e4254f27c4a3b5b581f5c67e7c72fe91fa3d0fb2e03d021793febc1872", "https://cdn.freesound.org/previews/856/856558_17110092-hq.ogg"),
|
| 47 |
+
856559: SourceSpec(856559, "rim-both-loud", "840a559e5d979e483cec2afb64e4f71a27d31c753c1bb138395303c5bd0d8253", "https://cdn.freesound.org/previews/856/856559_17110092-hq.ogg"),
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def _sha256(path: Path) -> str:
|
| 52 |
+
digest = hashlib.sha256()
|
| 53 |
+
with path.open("rb") as handle:
|
| 54 |
+
for block in iter(lambda: handle.read(1 << 20), b""):
|
| 55 |
+
digest.update(block)
|
| 56 |
+
return digest.hexdigest()
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def _load_source(source_dir: Path, sound_id: int) -> np.ndarray:
|
| 60 |
+
spec = SOURCES[sound_id]
|
| 61 |
+
path = source_dir / f"{sound_id}.ogg"
|
| 62 |
+
if not path.is_file():
|
| 63 |
+
raise FileNotFoundError(f"missing pinned CC0 source: {path}")
|
| 64 |
+
actual_hash = _sha256(path)
|
| 65 |
+
if actual_hash != spec.sha256:
|
| 66 |
+
raise ValueError(
|
| 67 |
+
f"source hash mismatch for {path.name}: {actual_hash} != {spec.sha256}"
|
| 68 |
+
)
|
| 69 |
+
audio, sample_rate = sf.read(path, dtype="float64", always_2d=True)
|
| 70 |
+
if sample_rate != SAMPLE_RATE or audio.shape[1] != 2:
|
| 71 |
+
raise ValueError(f"{path.name} must decode as 44.1 kHz stereo")
|
| 72 |
+
mono = np.mean(audio, axis=1)
|
| 73 |
+
peak = float(np.max(np.abs(mono)))
|
| 74 |
+
onset_candidates = np.flatnonzero(np.abs(mono) >= peak * 10.0 ** (-30.0 / 20.0))
|
| 75 |
+
if len(onset_candidates) == 0:
|
| 76 |
+
raise ValueError(f"{path.name} has no measurable onset")
|
| 77 |
+
start = max(0, int(onset_candidates[0]) - int(round(0.001 * SAMPLE_RATE)))
|
| 78 |
+
mono = mono[start:].copy()
|
| 79 |
+
mono -= float(np.mean(mono[: min(100, len(mono))]))
|
| 80 |
+
return mono
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def _highpass(signal: np.ndarray, cutoff_hz: float) -> np.ndarray:
|
| 84 |
+
sos = butter(3, cutoff_hz, btype="highpass", fs=SAMPLE_RATE, output="sos")
|
| 85 |
+
return sosfilt(sos, signal)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def _pitch(signal: np.ndarray, factor: float) -> np.ndarray:
|
| 89 |
+
ratio = Fraction(1.0 / factor).limit_denominator(1_000)
|
| 90 |
+
return resample_poly(signal, ratio.numerator, ratio.denominator)
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def _fit(signal: np.ndarray, frames: int) -> np.ndarray:
|
| 94 |
+
if len(signal) < frames:
|
| 95 |
+
return np.pad(signal, (0, frames - len(signal)))
|
| 96 |
+
return signal[:frames].copy()
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def _normalize(signal: np.ndarray, peak: float = 1.0) -> np.ndarray:
|
| 100 |
+
measured = float(np.max(np.abs(signal)))
|
| 101 |
+
if measured <= 1e-12:
|
| 102 |
+
raise ValueError("cannot normalize a silent sample")
|
| 103 |
+
return signal * (peak / measured)
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def _smooth_ramp(frames: int, seconds: float) -> np.ndarray:
|
| 107 |
+
position = np.arange(frames, dtype=np.float64) / SAMPLE_RATE
|
| 108 |
+
unit = np.clip(position / seconds, 0.0, 1.0)
|
| 109 |
+
return 0.06 + 0.94 * (unit * unit * (3.0 - 2.0 * unit))
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def _tail_fade(signal: np.ndarray, start_seconds: float, end_seconds: float) -> np.ndarray:
|
| 113 |
+
start = int(round(start_seconds * SAMPLE_RATE))
|
| 114 |
+
end = min(len(signal), int(round(end_seconds * SAMPLE_RATE)))
|
| 115 |
+
if not 0 <= start < end <= len(signal):
|
| 116 |
+
raise ValueError("invalid tail fade")
|
| 117 |
+
envelope = np.ones(len(signal), dtype=np.float64)
|
| 118 |
+
envelope[start:end] = 0.5 + 0.5 * np.cos(np.linspace(0.0, np.pi, end - start))
|
| 119 |
+
envelope[end:] = 0.0
|
| 120 |
+
return signal * envelope
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def _transient(source: dict[int, np.ndarray]) -> np.ndarray:
|
| 124 |
+
rim = _highpass(_pitch(source[856559], 2.15), 450.0)
|
| 125 |
+
search = rim[: int(round(0.100 * SAMPLE_RATE))]
|
| 126 |
+
peak = int(np.argmax(np.abs(search)))
|
| 127 |
+
start = max(0, peak - int(round(0.002 * SAMPLE_RATE)))
|
| 128 |
+
return _normalize(_fit(rim[start:], int(round(0.040 * SAMPLE_RATE))))
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def _don_body(source: dict[int, np.ndarray], sound_id: int, frames: int,
|
| 132 |
+
ramp_seconds: float) -> np.ndarray:
|
| 133 |
+
body = _fit(_normalize(_highpass(source[sound_id], 35.0)), frames)
|
| 134 |
+
return body * _smooth_ramp(frames, ramp_seconds)
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def _make_don(source: dict[int, np.ndarray], sound_id: int,
|
| 138 |
+
fade_start: float, fade_end: float) -> np.ndarray:
|
| 139 |
+
frames = int(round(0.830 * SAMPLE_RATE))
|
| 140 |
+
body = _don_body(source, sound_id, frames, 0.060)
|
| 141 |
+
hit = body + 0.15 * _fit(_transient(source), frames)
|
| 142 |
+
hit = np.tanh(_tail_fade(hit, fade_start, fade_end))
|
| 143 |
+
return _normalize(hit, 0.90)
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def _make_big_don(source: dict[int, np.ndarray]) -> np.ndarray:
|
| 147 |
+
frames = int(round(0.895 * SAMPLE_RATE))
|
| 148 |
+
center = _don_body(source, 856550, frames, 0.020)
|
| 149 |
+
hit = center + 0.60 * _fit(_transient(source), frames)
|
| 150 |
+
hit = np.tanh(_tail_fade(hit, 0.380, 0.800))
|
| 151 |
+
return _normalize(hit, 0.94)
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def _make_ka(source: dict[int, np.ndarray], primary_id: int,
|
| 155 |
+
secondary_id: int, secondary_gain: float) -> np.ndarray:
|
| 156 |
+
frames = int(round(0.407 * SAMPLE_RATE))
|
| 157 |
+
primary_raw = _pitch(source[primary_id], 2.15)
|
| 158 |
+
primary = _normalize(_highpass(primary_raw, 450.0))
|
| 159 |
+
secondary = _normalize(_highpass(_pitch(source[secondary_id], 0.85), 450.0))
|
| 160 |
+
hit = _fit(primary, frames) + secondary_gain * _fit(secondary, frames)
|
| 161 |
+
bright = _normalize(_highpass(_pitch(source[primary_id], 4.30), 1_800.0))
|
| 162 |
+
search = bright[: int(round(0.050 * SAMPLE_RATE))]
|
| 163 |
+
bright_peak = int(np.argmax(np.abs(search)))
|
| 164 |
+
bright_start = max(0, bright_peak - int(round(0.001 * SAMPLE_RATE)))
|
| 165 |
+
bright = _fit(bright[bright_start:], int(round(0.015 * SAMPLE_RATE)))
|
| 166 |
+
hit += 0.90 * _fit(bright, frames)
|
| 167 |
+
hit = _tail_fade(hit, 0.230, 0.407)
|
| 168 |
+
return _normalize(hit, 0.94)
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def _short_hit(signal: np.ndarray, duration: float, fade_start: float,
|
| 172 |
+
peak: float) -> np.ndarray:
|
| 173 |
+
frames = int(round(duration * SAMPLE_RATE))
|
| 174 |
+
hit = _fit(signal, frames)
|
| 175 |
+
hit = _tail_fade(hit, fade_start, duration)
|
| 176 |
+
return _normalize(hit, peak)
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def _build_assets(source: dict[int, np.ndarray]) -> dict[str, tuple[str, int, np.ndarray, list[int]]]:
|
| 180 |
+
don_1 = _make_don(source, 856552, 0.280, 0.710)
|
| 181 |
+
don_2 = _make_don(source, 856550, 0.220, 0.600)
|
| 182 |
+
don_big = _make_big_don(source)
|
| 183 |
+
ka_1 = _make_ka(source, 856559, 856557, 0.20)
|
| 184 |
+
ka_2 = _make_ka(source, 856557, 856558, 0.15)
|
| 185 |
+
ka_big = _normalize(0.70 * ka_1 + 0.70 * ka_2, 0.96)
|
| 186 |
+
roll_1 = _short_hit(don_1, 0.150, 0.065, 0.56)
|
| 187 |
+
roll_2 = _short_hit(don_2, 0.150, 0.065, 0.56)
|
| 188 |
+
roll_big_1 = _short_hit(don_big, 0.175, 0.080, 0.66)
|
| 189 |
+
roll_big_2 = _short_hit(0.72 * don_big + 0.28 * _fit(don_1, len(don_big)), 0.175, 0.080, 0.66)
|
| 190 |
+
balloon_1 = _short_hit(don_1, 0.135, 0.055, 0.54)
|
| 191 |
+
balloon_2 = _short_hit(don_2, 0.135, 0.055, 0.54)
|
| 192 |
+
return {
|
| 193 |
+
"don-01.wav": ("don", 1, don_1, [856552, 856559]),
|
| 194 |
+
"don-02.wav": ("don", 2, don_2, [856550, 856559]),
|
| 195 |
+
"don-big.wav": ("don_big", 1, don_big, [856550, 856559]),
|
| 196 |
+
"ka-01.wav": ("ka", 1, ka_1, [856559, 856557]),
|
| 197 |
+
"ka-02.wav": ("ka", 2, ka_2, [856557, 856558]),
|
| 198 |
+
"ka-big.wav": ("ka_big", 1, ka_big, [856559, 856557, 856558]),
|
| 199 |
+
"roll-01.wav": ("roll", 1, roll_1, [856552, 856559]),
|
| 200 |
+
"roll-02.wav": ("roll", 2, roll_2, [856550, 856559]),
|
| 201 |
+
"roll-big-01.wav": ("roll_big", 1, roll_big_1, [856550, 856559]),
|
| 202 |
+
"roll-big-02.wav": ("roll_big", 2, roll_big_2, [856550, 856552, 856559]),
|
| 203 |
+
"balloon-01.wav": ("balloon", 1, balloon_1, [856552, 856559]),
|
| 204 |
+
"balloon-02.wav": ("balloon", 2, balloon_2, [856550, 856559]),
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def _profile(signal: np.ndarray) -> dict[str, float]:
|
| 209 |
+
energy = signal * signal
|
| 210 |
+
cumulative = np.cumsum(energy)
|
| 211 |
+
spectrum = np.abs(np.fft.rfft(signal * np.hanning(len(signal)))) ** 2
|
| 212 |
+
frequency = np.fft.rfftfreq(len(signal), 1.0 / SAMPLE_RATE)
|
| 213 |
+
spectrum /= max(float(np.sum(spectrum)), 1e-15)
|
| 214 |
+
attack = signal[: int(round(0.010 * SAMPLE_RATE))]
|
| 215 |
+
attack_spectrum = np.abs(np.fft.rfft(attack * np.hanning(len(attack)))) ** 2
|
| 216 |
+
attack_frequency = np.fft.rfftfreq(len(attack), 1.0 / SAMPLE_RATE)
|
| 217 |
+
attack_spectrum /= max(float(np.sum(attack_spectrum)), 1e-15)
|
| 218 |
+
return {
|
| 219 |
+
"centroid_hz": round(float(frequency @ spectrum), 3),
|
| 220 |
+
"attack_centroid_hz": round(float(attack_frequency @ attack_spectrum), 3),
|
| 221 |
+
"t90_seconds": round(float(np.searchsorted(cumulative, 0.90 * cumulative[-1]) / SAMPLE_RATE), 6),
|
| 222 |
+
"t99_seconds": round(float(np.searchsorted(cumulative, 0.99 * cumulative[-1]) / SAMPLE_RATE), 6),
|
| 223 |
+
"crest_db": round(float(20.0 * math.log10(np.max(np.abs(signal)) / np.sqrt(np.mean(energy)))), 3),
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
def _write_pcm16(path: Path, signal: np.ndarray) -> tuple[str, str, int]:
|
| 228 |
+
pcm = np.rint(np.clip(signal, -1.0, 1.0) * 32767.0).astype("<i2")
|
| 229 |
+
with wave.open(str(path), "wb") as output:
|
| 230 |
+
output.setnchannels(1)
|
| 231 |
+
output.setsampwidth(2)
|
| 232 |
+
output.setframerate(SAMPLE_RATE)
|
| 233 |
+
output.writeframes(pcm.tobytes())
|
| 234 |
+
return _sha256(path), hashlib.sha256(pcm.tobytes()).hexdigest(), len(pcm)
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
def build(source_dir: Path, output_dir: Path) -> None:
|
| 238 |
+
source = {sound_id: _load_source(source_dir, sound_id) for sound_id in SOURCES}
|
| 239 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 240 |
+
files = []
|
| 241 |
+
for filename, (kind, round_robin, signal, source_ids) in _build_assets(source).items():
|
| 242 |
+
file_hash, pcm_hash, frames = _write_pcm16(output_dir / filename, signal)
|
| 243 |
+
peak = float(np.max(np.abs(signal)))
|
| 244 |
+
anchor = int(np.flatnonzero(np.abs(signal) >= peak * 10.0 ** (-30.0 / 20.0))[0])
|
| 245 |
+
files.append({
|
| 246 |
+
"filename": filename,
|
| 247 |
+
"kind": kind,
|
| 248 |
+
"round_robin": round_robin,
|
| 249 |
+
"sha256": file_hash,
|
| 250 |
+
"pcm_sha256": pcm_hash,
|
| 251 |
+
"sample_rate": SAMPLE_RATE,
|
| 252 |
+
"channels": 1,
|
| 253 |
+
"frames": frames,
|
| 254 |
+
"anchor_frame": anchor,
|
| 255 |
+
"source_sound_ids": source_ids,
|
| 256 |
+
"profile": _profile(signal),
|
| 257 |
+
})
|
| 258 |
+
manifest = {
|
| 259 |
+
"schema_version": 1,
|
| 260 |
+
"kit_id": "softchart-arcade-taiko-cc0",
|
| 261 |
+
"license": "CC0-1.0",
|
| 262 |
+
"license_url": CC0_URL,
|
| 263 |
+
"source_pack_url": PACK_URL,
|
| 264 |
+
"source_creator": "sazanami12",
|
| 265 |
+
"source_instrument": "Chinese Huapengu / Dagu recorded in Taipei, Taiwan",
|
| 266 |
+
"source_encoding": "public Freesound HQ OGG preview of the CC0 original WAV",
|
| 267 |
+
"sources": [spec.__dict__ for spec in SOURCES.values()],
|
| 268 |
+
"files": sorted(files, key=lambda item: item["filename"]),
|
| 269 |
+
}
|
| 270 |
+
(output_dir / "manifest.json").write_text(
|
| 271 |
+
json.dumps(manifest, indent=2, ensure_ascii=False) + "\n",
|
| 272 |
+
encoding="utf-8",
|
| 273 |
+
)
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
def main() -> None:
|
| 277 |
+
parser = argparse.ArgumentParser()
|
| 278 |
+
parser.add_argument("--source-dir", type=Path, required=True)
|
| 279 |
+
parser.add_argument("--output-dir", type=Path, required=True)
|
| 280 |
+
args = parser.parse_args()
|
| 281 |
+
build(args.source_dir, args.output_dir)
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
if __name__ == "__main__":
|
| 285 |
+
main()
|
softchart/assets/taiko/README.md
CHANGED
|
@@ -5,7 +5,8 @@ Drum Dagu Oneshots** pack recorded in Taipei by `sazanami12`:
|
|
| 5 |
|
| 6 |
- Source: https://freesound.org/people/sazanami12/packs/45781/
|
| 7 |
- License: CC0 1.0 — https://creativecommons.org/publicdomain/zero/1.0/
|
| 8 |
-
- Build recipe: `scripts/build_taiko_bank.py`
|
|
|
|
| 9 |
- Exact source and output hashes: `manifest.json`
|
| 10 |
|
| 11 |
The source instrument is a Chinese Huapengu / Dagu, selected as a
|
|
|
|
| 5 |
|
| 6 |
- Source: https://freesound.org/people/sazanami12/packs/45781/
|
| 7 |
- License: CC0 1.0 — https://creativecommons.org/publicdomain/zero/1.0/
|
| 8 |
+
- Build recipe: `scripts/build_taiko_bank.py` in both the source repository and
|
| 9 |
+
the self-contained Space payload
|
| 10 |
- Exact source and output hashes: `manifest.json`
|
| 11 |
|
| 12 |
The source instrument is a Chinese Huapengu / Dagu, selected as a
|
softchart/assets/taiko/manifest.json
CHANGED
|
@@ -8,24 +8,12 @@
|
|
| 8 |
"source_instrument": "Chinese Huapengu / Dagu recorded in Taipei, Taiwan",
|
| 9 |
"source_encoding": "public Freesound HQ OGG preview of the CC0 original WAV",
|
| 10 |
"sources": [
|
| 11 |
-
{
|
| 12 |
-
"sound_id": 856549,
|
| 13 |
-
"label": "center-loud",
|
| 14 |
-
"sha256": "26d230aff7bb043fc88e823d7eecf00f807208f4c1d4d27c2cc1a7a2773b9d87",
|
| 15 |
-
"preview_url": "https://cdn.freesound.org/previews/856/856549_17110092-hq.ogg"
|
| 16 |
-
},
|
| 17 |
{
|
| 18 |
"sound_id": 856550,
|
| 19 |
"label": "center-soft",
|
| 20 |
"sha256": "78ed9dea56102480811e90a9653c08fe20a0eca5793c9c936f1c2592042b9a46",
|
| 21 |
"preview_url": "https://cdn.freesound.org/previews/856/856550_17110092-hq.ogg"
|
| 22 |
},
|
| 23 |
-
{
|
| 24 |
-
"sound_id": 856551,
|
| 25 |
-
"label": "edge-loud",
|
| 26 |
-
"sha256": "85ff2986eb902296e541a9d44b21bb61b9c2b8a05b5510be2fac9fbc99738399",
|
| 27 |
-
"preview_url": "https://cdn.freesound.org/previews/856/856551_17110092-hq.ogg"
|
| 28 |
-
},
|
| 29 |
{
|
| 30 |
"sound_id": 856552,
|
| 31 |
"label": "edge-soft",
|
|
@@ -49,12 +37,6 @@
|
|
| 49 |
"label": "rim-both-loud",
|
| 50 |
"sha256": "840a559e5d979e483cec2afb64e4f71a27d31c753c1bb138395303c5bd0d8253",
|
| 51 |
"preview_url": "https://cdn.freesound.org/previews/856/856559_17110092-hq.ogg"
|
| 52 |
-
},
|
| 53 |
-
{
|
| 54 |
-
"sound_id": 856560,
|
| 55 |
-
"label": "rim-both-soft",
|
| 56 |
-
"sha256": "4d66f33d35d2fe160da02c9a310231cdd6f2e8724dcfa7706de9c8409b5b7f97",
|
| 57 |
-
"preview_url": "https://cdn.freesound.org/previews/856/856560_17110092-hq.ogg"
|
| 58 |
}
|
| 59 |
],
|
| 60 |
"files": [
|
|
|
|
| 8 |
"source_instrument": "Chinese Huapengu / Dagu recorded in Taipei, Taiwan",
|
| 9 |
"source_encoding": "public Freesound HQ OGG preview of the CC0 original WAV",
|
| 10 |
"sources": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
{
|
| 12 |
"sound_id": 856550,
|
| 13 |
"label": "center-soft",
|
| 14 |
"sha256": "78ed9dea56102480811e90a9653c08fe20a0eca5793c9c936f1c2592042b9a46",
|
| 15 |
"preview_url": "https://cdn.freesound.org/previews/856/856550_17110092-hq.ogg"
|
| 16 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
{
|
| 18 |
"sound_id": 856552,
|
| 19 |
"label": "edge-soft",
|
|
|
|
| 37 |
"label": "rim-both-loud",
|
| 38 |
"sha256": "840a559e5d979e483cec2afb64e4f71a27d31c753c1bb138395303c5bd0d8253",
|
| 39 |
"preview_url": "https://cdn.freesound.org/previews/856/856559_17110092-hq.ogg"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
}
|
| 41 |
],
|
| 42 |
"files": [
|