PixiJSのv8.xが移行が難しすぎる件について

## はじめに
Nodejsで[ytdl-core](https://github.com/fent/node-ytdl-core)を使って取ってきたYoutube音声を再生して自動でAudioSpectrumを作るアプリを作ろうとしたときPixiJSでSpectrumを描画しようとしたら詰まったのでメモ。

## 環境構築(Preact)
自分が行ったとおり([Codespaces](https://github.com/codespaces)で[bun](https://bun.sh/)をつかって)やっていきますので所々違うかもしれませんがまあそこは自分で調整して下さい
```bash
npm i -g bun
bun create preact
bun add pixi.js
```
<details><summary>解説　読みたい人向け</summary>

```
npm i -g bun
```
普通にbunのinstall
```
bun create preact
```
bunでpreactプロジェクトの作成
bunのところをnpmに変えても動くのかな？しらんけど
```
bun add pixi.js
```
pixijsをinstall
</details>

## <本題>Spectrum Componentの作成
Componentはclass componentを使用します。そっちの方がそのまま書けるので

簡単にcanvasを操るテンプレ？がこちら
```typescript:spectrum.tsx
import { Component, createRef } from "preact";

export class Spectrum extends Component{
    canv = createRef<HTMLCanvasElement>();

    componentDidMount() {

    }

    render(){
        return <canvas ref={this.canv} />
    }
}
```
これにPixi Applicationなりを定義していくのですがv7とv8でPixiJS使用が大幅に変わっているらしい(性能も大幅によくなっているらしい)
[PixiJS v8 Launches! 🎉](https://pixijs.com/blog/pixi-v8-launches)

ということでドキュメントを読みながら書いてみたSpectrumがこれ
```typescript:spectrum.tsx
import { Component, createRef } from "preact";
import { Application, Graphics } from "pixi.js";
import Music from "../assets/music.mp3";

export class Spectrum extends Component {
    config = {
        size: 800,
        circleMode: false
    }
    state = {
        playing: false,
    }
    audio: HTMLAudioElement;
    audioContext: AudioContext;
    nodeAnalyser: AnalyserNode;
    nodeSource: MediaElementAudioSourceNode;
    canv = createRef<HTMLCanvasElement>();
    app: Application;
    bars: Graphics[] = [];

    playAudio = () => {
        this.audioContext = new AudioContext();
        this.nodeAnalyser = this.audioContext.createAnalyser();

        this.nodeAnalyser.fftSize = 1024; // ここの値を大きくすると細かくなります 範囲 32~32768
        this.nodeAnalyser.smoothingTimeConstant = 0.85;
        this.nodeAnalyser.connect(this.audioContext.destination);

        this.setState({ playing: true });
        this.audio = new Audio(Music);

        this.nodeSource = this.audioContext.createMediaElementSource(this.audio);
        this.nodeSource.connect(this.nodeAnalyser);

        this.audio.play();
        (async () => {
            this.app = new Application();
            await this.app.init({
                canvas: this.canv.current,
                width: this.config.size,
                height: this.config.size,
                backgroundColor: 0x000000,
                antialias: true,
            });

            for (let i = 0; i < this.nodeAnalyser.frequencyBinCount; i++) {
                const bar = new Graphics();
                bar.pivot.set(this.config.size / this.nodeAnalyser.frequencyBinCount / 2, 0);
                bar.rect(0, 0, this.config.size / this.nodeAnalyser.frequencyBinCount, 10);
                bar.fill(0xffffff/* * Math.random()*/); // Math.random()を掛けることで色がランダムになる
                this.bars.push(bar);
            }

            this.bars.forEach((bar, i) => {
                if (!this.config.circleMode) {
                    bar.x = (i + 0.5) * this.config.size / this.nodeAnalyser.frequencyBinCount;
                    bar.y = this.config.size / 2;
                    bar.rotation = Math.PI;
                } else {
                    bar.x = 200 + 100 * Math.cos(2 * Math.PI * i / this.nodeAnalyser.frequencyBinCount);
                    bar.y = 200 + 100 * Math.sin(2 * Math.PI * i / this.nodeAnalyser.frequencyBinCount);
                    bar.rotation = 2 * Math.PI * i / this.nodeAnalyser.frequencyBinCount - Math.PI / 2;
                }
                this.app.stage.addChild(bar);
            });

            this.audio.play();

            this.app.ticker.add(() => {
                this.update();
            });
        })()
    }

    update = () => {
        const data = new Uint8Array(this.nodeAnalyser.frequencyBinCount);
        this.nodeAnalyser.getByteFrequencyData(data);
        this.bars.forEach((bar, i) => {
            bar.height = data[i];
        });
    }

    render() {
        return (
            <div style={{ position: 'relative' }}>
                <button style={{ position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, margin: 'auto', height: 30, width: 120, display: this.state.playing && 'none' }} onClick={this.playAudio}>
                    Play
                </button>
                <canvas ref={this.canv} />
            </div>
        )
    }
}
```
css読み込むのがめんどっちかったので直書きstyle
Applicationの定義などの以前にasyncを使った非同期関数でくくらなきゃいけないらしい。もちろんいくつかは呼び出すときawaitを付けなきゃいけない。

以前はApplication classを呼び出す際の引数でoptionをいれていたが呼び出した後initの中にoptionを入れたり名前が少し変わったりしてる。たとえば以前は
```typescript
const app = new Application({
    view: this.canv.current
}) 
```
だったものが、viewが非推奨になって
```typescript
const app = new Application();
await app.init({
    canvas: this.canv.current
});
```
になったりしている。うーんv7からv8への移行が大変そう...

で開発陣も言っているv8へのアップデートで一番変わったのがGraphics
[PixiJS v8 移行ガイド](https://pixijs.com/8.x/guides/migrations/v8)を読んで変更点を[簡単にまとめた](#pixigraphics変更点まとめ)

### PIXI.Graphics変更点まとめ
- fillが先ではなく後に
- 図形を描画するメゾットの名前が変更

2つしかないじゃんって思った人もいると思うがこの2つが結構でかい...

今までだと
```javascript
// red rect
const graphics = new Graphics()
  .beginFill(0xFF0000)
  .drawRect(50, 50, 100, 100)
  .endFill();

// blur rect with stroke
const graphics2 = new Graphics()
  .lineStyle(2, 'white')
  .beginFill('blue')
  .circle(530, 50, 140, 100)
  .endFill();
```
まあこんな感じ。
これが
```javascript
// red rect
const graphics = new Graphics()
  .rect(50, 50, 100, 100)
  .fill(0xFF0000);


// blur rect with stroke
const graphics2 = new Graphics()
  .rect(50, 50, 100, 100)
  .fill('blue')
  .stroke({width:2, color:'white'});
```
こうなる。
beginFillで色を指定して図形を描画しendFillしていたものが先に図形を指定してfillで塗りつぶすなりstrokeで枠線を付けるようになってる。なんかいいコンバーターとかないかな...
つくってみるのもありかなと思ったけどそれほど需要はなさそうなので却下

まあGraphicsの変更はこんな感じであとは
- DisplayObjectが削除されてContainerがすべての要素の標準classに
- updateTransformが削除
- Assetsの追加方法の変更
- Texture.fromがurlから直接は出来なくなった <= 何気にめんどくさい
- app.ticker.add((d) => void)のdがdeltaTimeからTickerを返すようになった
- container.getBounds()で取得できていたものがcontainer.getBounds().rectangle

ちょっと多すぎるのでここまでにします詳しくは[公式移行ガイド](https://pixijs.com/8.x/guides/migrations/v8)を自分で見てもらった方が早いかなと思うけど変更ｵｵｽｷﾞｨ！

今回作ったもの https://github.com/msArray/AudioSpectrum