Learning Rust with ChatGPT and Copilot (1)

This is a series of posts of learning Rust by asking questions to ChatGPT. For reference, I had almost no experience of Rust but I knew some features of the language, like ownership and how annoyingly good the compiler is.

Keep in mind that GPT lies a lot. But the good thing about Rust is the excellent compiler. If GPT tells a lie, Rust compiler yells at you, then you can either try to ask GPT again or google it.

I used Copilot to assist me in writing one of my first Rust code. I didn't want to waste time to asking/googling some small syntax that I don't know of. It is also great when you create a bunch of sample data for your practice code. It does tend to overwork a little bit and sometimes too smart for me to get used to writing Rust by hand but... eventually this is going to be the way to write code, and it is more important to be able to understand AI-generated code, and correct wrong/imperfect parts.

Learning basics

I had GPT-4 list up the language features and asked for code examples for each. It tends to give me more useful answer if I give my situation like "I'm a software engineer and know TypeScript, but have never written Rust".

It is important to understand even small stuff as you go. I opened another conversation with GPT-3.5 (because I'll be asking a bunch of questions) and asked questions like "Why do you have to write String::from()" or "Can you teach me Rust enum in details".

You can also just google, but I found the Rust community tends to overcomplicate basic explanation when you're new. GPT does a better job until you grasp the overall picture.

Learning through practices

For each of the language feature GPT gave me, I asked for a exercise task. GPT tends to just giveaway answer right after giving the task so I made it explicit that I didn't want answer immediately.

You should definitely set up local environment and run it on your own. GPT is not a Rust compiler, and should never be relied on because it would just lie. Once I wrote and ran the code on my computer and it looks good, I gave it to GPT. Sometimes it gave me useful suggestions.

More practice

Once I finished going through the language features it initially gave me, I opened another GPT-4 conversation and asked for more exercise. It gave me a task that involves user inputs it was more practical.

My code

use regex::Regex;
use std::fmt;
use std::io;

enum TemperatureUnit {
    Celsius,
    Fahrenheit,
    Kelvin,
}
impl fmt::Display for TemperatureUnit {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            TemperatureUnit::Celsius => write!(f, "C"),
            TemperatureUnit::Fahrenheit => write!(f, "F"),
            TemperatureUnit::Kelvin => write!(f, "K"),
        }
    }
}

fn prompt_temperature() -> Result<(f64, TemperatureUnit), String> {
    let temperature_input_regex = Regex::new(r"^(\d+\.?\d*)\s*(C|F|K)$").unwrap();

    println!("Convert temperature with a unit (e.g. 92.8 C): ");

    let mut input = String::new();

    if let Err(err) = io::stdin().read_line(&mut input) {
        return Err(format!("Failed to read input: {err}"));
    }

    if let Some(captures) = temperature_input_regex.captures(input.trim()) {
        let value = match captures.get(1) {
            Some(value) => match value.as_str().parse::<f64>() {
                Ok(value) => value,
                Err(err) => return Err(format!("Failed to parse input: {err}")),
            },
            None => return Err("Invalid input".to_string()),
        };

        let unit = match captures.get(2) {
            Some(unit) => match unit.as_str() {
                "C" => TemperatureUnit::Celsius,
                "F" => TemperatureUnit::Fahrenheit,
                "K" => TemperatureUnit::Kelvin,
                unit => return Err(format!("Invalid unit: {unit}")),
            },
            None => return Err("Invalid input".to_string()),
        };

        return Ok((value, unit));
    } else {
        return Err(format!("Invalid input: {input}"));
    }
}

fn prompt_target_unit() -> Result<TemperatureUnit, String> {
    println!("Convert to unit (C, F, K): ");

    let mut input = String::new();

    if let Err(err) = io::stdin().read_line(&mut input) {
        return Err(format!("Failed to read input: {err}"));
    }

    match input.trim() {
        "C" => Ok(TemperatureUnit::Celsius),
        "F" => Ok(TemperatureUnit::Fahrenheit),
        "K" => Ok(TemperatureUnit::Kelvin),
        unit => Err(format!("Invalid unit: {}", unit)),
    }
}

fn convert(temperature: f64, from: &TemperatureUnit, to: &TemperatureUnit) -> f64 {
    match from {
        TemperatureUnit::Celsius => match to {
            TemperatureUnit::Celsius => temperature,
            TemperatureUnit::Fahrenheit => temperature * 9.0 / 5.0 + 32.0,
            TemperatureUnit::Kelvin => temperature + 273.15,
        },
        TemperatureUnit::Fahrenheit => match to {
            TemperatureUnit::Celsius => (temperature - 32.0) * 5.0 / 9.0,
            TemperatureUnit::Fahrenheit => temperature,
            TemperatureUnit::Kelvin => (temperature + 459.67) * 5.0 / 9.0,
        },
        TemperatureUnit::Kelvin => match to {
            TemperatureUnit::Celsius => temperature - 273.15,
            TemperatureUnit::Fahrenheit => temperature * 9.0 / 5.0 - 459.67,
            TemperatureUnit::Kelvin => temperature,
        },
    }
}

fn main() {
    let (temperature, from) = match prompt_temperature() {
        Ok(temperature) => temperature,
        Err(err) => {
            println!("{err}");
            return;
        }
    };

    let to = match prompt_target_unit() {
        Ok(unit) => unit,
        Err(err) => {
            println!("{err}");
            return;
        }
    };

    let result = convert(temperature, &from, &to);

    println!("Input temperature {temperature} {from} is equivalent to {result} {to}.")
}

(I've already falled in love with destructuring and pattern matching... It's so good, or at least way better than JavaScript/TypeScript)

I guess I take it as a win.

(cont.)

RTA in Japanを支える技術 ~オーディオ編~

RTA in Japanの運営で、技術なことをきりもりしていますが、裏でどんな技術が使われてどんなものが使われているのか、というのは他のイベントにも役に立つと思うので共有していきたいと思います。

RTA in Japan is 何?

RTA in Japanは日本で最初の大規模オフラインRTAイベントです」。もう3年目なので最初っていうのは微妙なんですが、ゲームやコミュニティの壁を超えて、ハイレベルだったり面白かったりするRTAを100時間くらい休みなく、RTAを披露するイベントです。会場を借りて、だれでも見れるようにして、カメラ付きでネット上で配信もします。

今回の内容

今回は音に関することです。オーディオ知らない、ミキサー触ったことないって人にもわかるように気をつけて書いていきます。RTAイベントだけでなく、一般的なゲームイベントやその他会場を使うイベントで、特に出入力が多い場合、役に立つ話だと思います。

注意: 記事の性質上、Amazonサウンドハウスなどの商品のリンクを載せていますが、特定の商品やサイトの販促などの目的はなく、このようなものを使ったという説明をわかりやすくする目的でリンクを張っています。

入ってくる音と出す音を整理する

オーディオの基本は、入ってくる音があって、ミキサーなどでごにょごにょしたあと、出すべきところに音を出すのが仕事です。なので入ってくるものと出すべきところを整理してみます。

Input(入ってくる音)

  • 出番のプレイヤー、解説のため最大6人のマイク
  • 最大プレイヤー2人 x ステレオ 2 x SD画質とHD画質で取り込み方が違って2 = 8
  • 会場の音を取り入れるマイクがステレオで 2
  • 休憩中に流す音楽がステレオで 2

ざっとここまで足すと、18チャンネルくらいあります。

Output(出す音)

  • 用意しているヘッドホン付きマイクは3台で、それぞれに出す音3つ x ステレオ 2 = 6
    • どのゲームを出すか、どのマイクの音をどれくらいのバランスで出すか、自分の声は聞くか、などがヘッドホンによって違うので、1つの出力で済ませられません
  • 会場に出す音がステレオ 2
  • 配信に出すMain出力がステレオ 2

合わせて10チャンネルくらい。

ミキサー: Behringer X32

入力が18個、出力が10個はけっこう多いです。しかも、それぞれのヘッドホンに出す音をそれぞれ決めたいだとか、いろいろと複雑にやりたいことがあって、それを実現できるミキサーは限られます。

ところで、RTA in Japanは、アメリカのRTAチャリティイベント Games Done Quick (GDQ) にインスパイアされて開催された経緯があります。規模は比べ物にならないですが、やっていることはだいたい同じなので、参考にできるのではないか?といろいろ調べてみたら、このミキサーを使っていることがわかりました。

www.soundhouse.co.jp

見た目かっちょいいですね。後ろの画像を見ればわかるんですが、

  • 入力32チャンネル
  • 出力16チャンネル

と、上にあげた必要な数を満たしています。

しかもれっきとしたデジタルミキサーで、写真にあるフェーダー(上下に動かすつまみ)は、内部のコンピュータとつながっていて、表示するものを変えるとピュンピュン勝手に動きます。

GDQやRPGLBなどの大きなRTAイベントで長い間使われている、数が足りる、機能が十分、他の同レベルのミキサーに比べてかなり安い、などいろいろ鑑みた結果購入を決めました。結果としてミキサー自体は本当に申し分ない仕事をしてくれました。

マイク

マイクは2種類用意しています

Audio Technica BPHS1

www.audio-technica.com

日本で販売されていませんが、アメリカのAmazonから輸入しました。GDQなどで使われている実績、強い単指向性*1マイクなどが決め手でした。

Shure WH20XLR

www.shure.com

こちらは、ヘッドホンが苦手な人用に用意しました。頭の後ろから耳にかける形のヘッドセットです。初見で正しくつけられた人はまだいません。マイクは指向性はしっかりしていていいのですが、少し音の質が上のBPHS1に比べたら悪いです。

会場の音

会場の音は、会場にもともと備品としてあったマイクを使いました。マイクスタンドをできるだけ伸ばし、上の方から全体的な音をとりこめるようにしました。

ゲーム音の取り込み

SD画質のゲーム(いわゆる三色ケーブル)は、そのまま以下のようなケーブルを利用してミキサーに直接入力します。

www.soundhouse.co.jp

HD画質のゲームは主にHDMIケーブルであることが多く、デジタルな信号からアナログ音声信号を取り出さなければいけません。そのため以下のような機器で音だけを取り出しています。

この分離器の先は、上と同じケーブルでミキサーに取り込みます。

X32での設定

ここからが本番。少しオーディオの知識が必要になるかも知れませんが、できるだけ噛み砕いて説明してみます。

まずX32の中での音の流れを簡単に説明してみます。

まず当然ですが、音は32個あるInputに入っていきます。それらは、Bus Mixと呼ばれるところに音量が調整された後送られ、最後に任意でMatrixに送ることができる、3段階です。デジタルミキサーなので、どの段階の音をOutputから出力するかは自由に決められます。今回はBus Mixまでしか使いませんでした。

f:id:Hoishin:20190105203834j:plain
全てつなげて設定し終わった図

さて、最初は入ってくる音をつないでいきます

  • 1-2, 3-4: SD画質のゲーム
  • 5-6, 7-8: HD画質のゲーム
  • 9-14: マイク
  • 15-16: 音楽を流すためのiPad
  • 31, 32: 会場の音を拾うマイク

そして、出す音をつないでいきます

  • 1-2, 3-4, 5-6: ヘッドホン3つ
  • 7-8: 会場の音
  • 15-16: Main(配信に流す音)

入る音、出る音は、ミキサー上でわかるように、それぞれのチャンネルに割り当てられている液晶画面を色分けし、文字を書いておきます(Micとか、HD1とか)。色もデタラメではなく、HDMIケーブルの先っぽに黄色いテープを貼っておき、それに対応するHD1チャンネルを黄色にしています。マイクなども同様。

数が多いので、ミキサーだけ見て瞬時に判断できるようにする工夫はとても大事です。

さて入力は揃ったので、Bus Mixに音を決めていきます。ここで、ミキサーの真ん中らへんにある SENDS ON FADER を押すと、そのボタンが点滅し始めます。その状態で、右側のBus Mixのチャンネルを SELECT すると、そのBux Mixチャンネルに流す音量を調整することができます。これは他のチャンネルや配信用のMainとは独立しています。

ところで、これでヘッドホンに送る音はいいのですが、会場に送るを音は配信に送る音から会場の音だけ消す、みたいなことが必要なのですが、割愛します。

以上ものすごく飛ばし飛ばしですが、X32で音を調整はこのような感じでやりました。

その他便利なもの

マルチケーブル

このくらいの規模になると、マルチケーブルと呼ばれる、線をまとめて延長できる機材がほぼ必須になってきます。

このような機材をゲームをするエリアの床に置き、端子をミキサーに繋ぐことで、マイクそれぞれのケーブルをバラバラに延長することなく、長さに余裕を持てます。RTA in Japanでは下のものを使いました。

www.soundhouse.co.jp

ケーブル固定テープ

配信や音を管理する場所から、会場に映像や音声を出す場所までが少し離れていたので、会場を横切ってケーブルを渡す必要があったため、以下のようなものを入手して、床に固定させました。

www.soundhouse.co.jp

天井を通すと余計に長さが必要になり不安定だし、これを使えば床に通しても足を引っかかる心配がなくなり、使える子でした。

色付き結束バンド、色付きテープ

ミキサー上で色を分けていましたが、ケーブルにもこのような道具で色を物理的につけることで、識別のしやすが格段に上がります。

終わりに

工夫して、少しやりたいことを減らせば、同じことはここまで用意しなくても可能です。ただ、RTA in Japanのようなイベントでは、オーディオわからなくてもある程度オペレーションができるというのが(僕が睡眠を取るために)必要になっているため、上のようなセットアップになっています。

ゲームイベントのプロの方、オーディオのプロの方、なにかアドバイスあげたくなってもらえたら、ぜひいただけると嬉しいです。僕は本職ソフトウェアエンジニアで、音に関しては全くの素人なので・・・。

イベントやってるまたはやりたいけど、どうすればいいのかわからない、という方気軽に相談もらえればいくらでものります。ここには貼らないですが、今までどんな機材を購入したのかとか共有できると思います。

次回は、映像に関することを書きたいと思っています。こちらはこちらでかなり大変です・・・。

*1:マイクの向いている方向のみから音を拾い、それ以外からの音をできるだけ拾わないマイクの性質。会場を借りて開催イベントだと、単指向性でない場合ハウリングしやすくなる