SVX日記

2004|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|12|
2023|01|02|03|04|05|06|07|08|09|10|11|12|
2024|01|02|03|04|05|06|07|08|09|10|11|12|
2025|01|02|03|04|05|06|07|08|09|10|11|12|
2026|01|02|

2026-02-05(Thu) AIに攻略法を聞きながらゲームする

  なんとなくネットニュースを見ていたら「AIレコーダ」がどうとかいう記事を見つけた。

  自分はロードスターでドライブする際、ボイスレコーダを携えていることが多い。気になる道路や建造物、思いついたアイデアなんかを、その場で喋って録音しておくのだ。運転中に筆記具でメモするのは難しいが、ボイスメモなら差し支えない。で、自宅に帰ったら聞き返しながらエディタに打ち込み、必要に応じて調べたり、TODOリストに入れたりするのだ。

  画像の説明

  ちなみに日本製だとこの用途に見合うボイスレコーダが見当たらない。スライドスイッチのワンアクションで録音を開始し、同じくワンアクションで完了したいだけなのだが。それとストラップホールは必須。で、アリエク。なんだかんだで手元に3つもある。紛失したり、買ってみたらワンアクションで録音できなかったりで、4つも買ってしまった。安いけど。小さいのに動画まで撮れたりするのもあるのだが、その機能はいらん。

  結局「AIレコーダ」の記事は読まなかったのだが、たぶん録音した内容を文字起こしするというような内容だろう。でも、それってぜんぜんレコーダ関係ない。結局はレコーダをPCに接続して、外部AIサービスに文字起こしを依頼するのだから。そんなの簡単に作れるだろう。

  簡単に作れるだろうが、現状の自作のライブラリにはその機能がないし、APIにも知見がない。というわけで、調べてみた。

  すると、文字起こし専用に「audio-transcriptions API」というものがあるらしい。音声データを渡すと、文字列を返してくれる。逆に音声で読み上げてくれる「audio-speech API」というものもある。文字列を渡すと、音声データを返してくれる。

  さらに画像を生成する「images-generations API」というものもある。文字列を渡すと、画像データを返してくれる。ほんじゃ、画像を説明してくれる「images-descriptions API」があるかというと……それはない。それはないが「chat API」に画像データを渡す機能ならある。釈然としないが、音声ならばその用途はほぼ文字起こしに決まっているだろうが、画像の場合には説明以外にも、文字を抜き出したり、場所を聞いたり、画像を編集したりと、目的が千差万別だかららしい。そらそうやな。

  ついでにいうと「chat API」には音声データを渡す機能もある。その場合、文字起こしを依頼してもいいし、声質や曲名を聞いてもいいのだろう。要するにAIにおける音声と画像の処理には非対称性があるってことだ。

  わかったところで順にライブラリに実装していく。NGSAudioTranscriptionsクラス、NGSAudioSpeechクラス、NGSImagesGenerationsクラス、そして、NGSクラスに画像や音声を渡せる機能を追加していく。

  と、そこで気づいたのだが「文字起こし」と「読み上げ」ができれば、音声による指示と、音声による応答が可能になる。要するにAIと会話ができるようになるわけだ。プログラミングしながらキーボードから手を離さずにチョット聞きたい。とか……そうだ! ゲームしながらチョット攻略法を聞きたい、なんて時に使えるのではないだろうか。

  ただし「文字起こし」「質問」「読み上げ」を順次行う都合から、応答速度はイマイチ。なので、最近はほぼリアルタイムな会話を実現する専用のAPIもあるらしい。んが、それを使ったらそれ以上に何の工夫もできなさそうだ。別に会話をしたいわけではないからな。

  とりあえずライブラリに実装できたところで、本体を書く。本体を書かないとライブラリの使い勝手がわからない。んが、極めてシンプルに本体を書けたので、ライブラリのAPIは意図通りだったってことだ。

  問題は、問合せを開始するトリガ。理想は「アレクサ」のように、特定の音声の発話で開始したいが、これは常時の音声認識が必要なので難しい。コマンド起動やマウス操作はうざったい。そこで、ジョイスティックのボタンをトリガにしてみた。作ってから1年位になるが、常にすぐ横に立てかけてあるので予備挙動なしで「チョン」と押せる。Rubyでの検出処理も意外と容易だった。CPU負荷なしで待機できること。コレ重要。

  そして極めて都合の良いことに「ゲームしながらチョット攻略法を聞きたい」状況にも最適なわけだ。自作のジョイスティックは3ボタンなのでアーケード版のグラIIのプレイ中には使えないが、ドルアーガなら大丈夫。プレイ中は「/dev/input/js0」が排他されてしまうかと思ったらそうはならなかった。

  早速、プレイ中にボタン3を押して「ドルアーガの塔の16面の宝の出し方を教えて下さい」と聞いてみる。滑舌が悪くて「ドルガーゴの塔」になってしまっているのに、キチンと「16面の宝箱は左右の外周に触れることで出現します」と音声で返ってきた。おいおいマジかよ。そこはトンチンカンな回答を返してオチをつけてくれよ。まぁURLまで発話してしまうというオチはあったけれどさ。

  画像の説明

  ライブラリには画像を生成させるAPIもあるので、30面のように「特定地点を通過する」のような出現条件の場合、画像で説明させるようにすることも可能だろう。ドラゴンバスターで出口を守っているルームガーダーの位置を聞いたりもできる。いや「/dev/input/js0」を渡しているのだから「ちょっとトイレいくんで進めといて」と、代行プレイを依頼することも……将来的には可能かもしれない。まぁ、オイラはゲーマーなので、宝の出し方も全部覚えているし、AIに頼るなんてアリエないけどな(←なんだよイマサラ)。

  そうでなくても、プログラミング中に音声で「xxx関数のサンプル出して」と頼むくらいは実用性があるかもしれない。その場合、音声で返されても困るけれどもw。

  参考に、本体の方のコードを示しておく。

#!/usr/bin/env ruby
# coding: utf-8
 
begin
    $LOAD_PATH.unshift('.', '/usr/local/lib/ruby')
    require 'libngs'
    require 'libjoy'
rescue LoadError
    raise
end
 
begin
    $LOAD_PATH.unshift('~', File.dirname(File.expand_path($0)))
    load 'ngs.config'
rescue LoadError
    raise
end
 
ngs = {
    :CHAT       => NGS.new(@configs),
    :TRANSCRIBE => NGSAudioTranscriptions.new(@configs),
    :SPEAK      => NGSAudioSpeech.new(@configs),
}
 
js = JoyStick.new
loop {
    puts('Press button 2 to start.'); js.gets2
    thread = Thread.new {
        $pid = Process.spawn('rec -c 1 -r 16000 query.wav')
    }
    puts('Done, press button 2.'); js.gets2
    Process.kill(:INT, $pid)
    thread.join
 
    # 音声認識
    fh = open('query.wav')
    query = ngs[:TRANSCRIBE].transcribe(fh)
    query += '50文字程度で回答をお願いします。'
    puts(query)
 
    # 問合せ
    answer = ngs[:CHAT].ask(query)
    puts(answer)
 
    # 音声発声
    res = ngs[:SPEAK].speak(answer)
    open('answer.mp3', 'w') {|fh|
        fh.write(res)
    }
    system('play answer.mp3')
}
 
__END__