JA

Slave Rift

作者:tueb2024 · シューティング
5.0 (1)
▶ 遊ぶ 💬 フィードバック ソース 🕘 バージョン 編集 GitHub
games/slave-rift / src / Audio / ProceduralScore.php · commit 1e9ac38
<?phpnamespace App\Audio;use App\Game\LevelRandom;/** Small phrase grammar; local musical RNG never reads or mutates Run. */final class ProceduralScore{    public readonly int $root;    public readonly array $scale;    private array $motif;    public function __construct(public readonly int $seed)    {        $rng=new LevelRandom(($seed+4093)%65520+1);        $this->root=48+$rng->nextInt(5);        $this->scale=$rng->chance(.5)?[0,2,3,5,7,8,10]:[0,2,3,5,7,9,10];        $this->motif=$rng->chance(.5)?[0,2,4,3,2,0]:[0,1,3,4,2,1];    }    /** One four-beat phrase, repeated with controlled variation every four bars. */    public function phrase(int $bar): array    {        $rotation=[0,3,4,0][intdiv(max(0,$bar),4)%4];        $degrees=$this->motif;        if($bar%4===3)$degrees[4]=max(0,$degrees[4]-1);        $lead=[];        foreach([0,3,6,8,11,14]as$i=>$step){$degree=min(6,$degrees[$i]+($rotation===0?0:1));$lead[$step]=$this->root+12+$this->scale[$degree];}        $counter=[];$last=$this->root-5;        foreach([0,5,9,13]as$step){            // Validate consonance against the next structural beat (held notes count).            $strong=match($step){0=>[0,4],5=>[8],9=>[12],default=>[0]};            $valid=[];            for($note=$this->root-7;$note<=$this->root+4;$note++){                $pc=(($note-$this->root)%12+12)%12;if(!in_array($pc,$this->scale,true))continue;                $ok=true;foreach($strong as$beat){$l=$lead[0];foreach($lead as$t=>$v)if($t<=$beat)$l=$v;                    if(!in_array(($l-$note)%12,[0,3,4,7,8,9],true)||$l-$note<8)$ok=false;}                if($ok)$valid[]=$note;            }            // Prefer small motion opposite the lead; seeded motif supplies identity.            $l=$lead[0];foreach($lead as$t=>$v)if($t<=$step)$l=$v;            $desired=$last+($l>$lead[0]?-2:2);            usort($valid,static fn($a,$b)=>abs($a-$desired)<=>abs($b-$desired));            $counter[$step]=$valid[0]??$this->root;$last=$counter[$step];        }        return ['lead'=>$lead,'counter'=>$counter,'bass'=>$this->root-12+$this->scale[$rotation]];    }    /** Pure event plan; arrangement intensity changes notes, never the clock. */    public function events(int $step,int $bar,int $level,bool $boss=false,int $pressure=0): array    {        $step%=16;$p=$this->phrase($bar);$events=[];        if($step%4===0)$events[]=['kick',null];        if($level>=4 && in_array($step,[4,12],true))$events[]=['snare',null];        if($level>=12 && in_array($step,[4,12],true))$events[]=['clap',null];        if(($pressure>=12||$boss)&&$step%2===1)$events[]=['hat',null];        if(($pressure>=20||$boss)&&$step===14)$events[]=['openhat',null];        if($step%8===0)$events[]=['bass',$p['bass']];        if($level>=8&&isset($p['lead'][$step]))$events[]=['lead',$p['lead'][$step]];        if($level>=16 && isset($p['counter'][$step]))$events[]=['counter',$p['counter'][$step]];        if($boss&&$step===15)$events[]=['bass',$this->root-12+7];        return $events;    }}