VirtualBoxのUbuntuのC言語でゲームパッド

VirtualBoxUbuntuC/C++で、USB接続のゲームパッド/ジョイスティックを扱いたい。

まずWindowsで動作確認

「コントロールパネル」→「デバイスとプリンター」でゲームパッドを選択し、右クリックで「ゲームコントローラの設定」→「プロパティ」の「テスト」タブで動作を確認する。


VirtualBoxゲームパッドをゲストOSに接続

仮想マシンの「デバイス」→「USB」から、ゲームパッドをゲストOSに接続する。

Ubuntuで動作確認

USBデバイスとして認識されていることを確認する。
以下の例では、ゲームパッド「JC-U3912T」が認識されていることが分かる。

$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 007: ID 056e:200e Elecom Co., Ltd JC-U3912T
Bus 002 Device 002: ID 80ee:0021 VirtualBox USB Tablet
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub

jstest-gtkをインストール。

$ sudo apt-get install jstest-gtk

jstest-gtkを実行する。

$ jstest-gtk

この例では、/dev/input/js2 が所望のゲームパッド「JC-U3912T」であることが分かる。
ゲームパッドを選択して「Properties」を開くと、ゲームパッドの動作確認ができる。


C/C++ゲームパッドを扱う

#include <vector>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/joystick.h>

using namespace std;

// ゲームパッドのデバイスを指定
#define JOY_DEV "/dev/input/js2"

int main()
{
  int joy_fd = -1;          // ゲームパッドのファイル指定子
  int num_of_axis    = 0;   // 軸の数
  int num_of_buttons = 0;   // ボタンの数
  char name_of_joystick[80];// ゲームパッドの名前
  vector<int>  joy_axis;    // 各軸の値
  vector<char> joy_button;  // 各ボタンの値

  // ゲームパッドを開く
  if((joy_fd=open(JOY_DEV, O_RDONLY)) < 0) {
    printf("Failed to open %s\n", JOY_DEV);
    return -1;
  }
  // ゲームパッドの軸の数、ボタンの数、名前を取得
  ioctl(joy_fd, JSIOCGAXES, &num_of_axis);
  ioctl(joy_fd, JSIOCGBUTTONS, &num_of_buttons);
  ioctl(joy_fd, JSIOCGNAME(80), &name_of_joystick);

  printf("Joystick: %s\n", name_of_joystick);
  printf("Axis:     %d\n", num_of_axis);
  printf("Buttons:  %d\n", num_of_buttons);

  // Enter入力待ち
  printf("\nHit Enter key\n");
  while ( getchar() != '\n') { ; }

  // 軸の値、ボタンの値を取得するためのメモリ確保
  joy_button.resize(num_of_buttons,0);
  joy_axis.resize(num_of_axis,0);

  // ファイル制御:ノンブロッキングモード
  fcntl(joy_fd, F_SETFL, O_NONBLOCK);

  while(true)
  {
    // イベント取得
    js_event js;
    read(joy_fd, &js, sizeof(js_event));

    // イベント種別ごとの処理
    switch (js.type & ~JS_EVENT_INIT)
    {
    // 軸イベント
    case JS_EVENT_AXIS:
      // 軸番号のチェック
      if((int)js.number >= num_of_axis){
        printf("Axis number error: %d\n", (int)js.number);
        continue;
      }
      // 軸の値の取得
      joy_axis[(int)js.number] = js.value;
      break;
    // ボタンイベント
    case JS_EVENT_BUTTON:
      // ボタン番号のチェック
      if((int)js.number >= num_of_buttons){
        printf("Button number error: %d\n", (int)js.number);
        continue;
      }
      // ボタンの値の取得
      joy_button[(int)js.number] = js.value;
      break;
    }

    // 表示
    printf("Axis:");
    for(int i = 0; i < num_of_axis; i++){ printf(" %6d", joy_axis[i]); }
    printf("\n");
    printf("Button:");
    for(int i = 0; i < num_of_buttons; i++){ printf(" %d", joy_button[i]); }
    printf("\n");

    usleep(1000);
  }
  // ゲームパッドを閉じる
  close(joy_fd);
  return 0;
}

参考


エスケープシーケンスまとめ

エスケープシーケンスとは?

コンソールに、\033 または \x1b から始まる文字を出力することでコンソールの表示を制御する。
8進数 033 は、16進数で 0x1b、10進数で27であり、ESCを表す。

画面消去

文字列 説明
\033[0J カーソル位置から画面右下まで消去
\033[1J カーソル位置から画面左上まで消去
\033[2J 全画面消去
\033[0K カーソル位置から行末まで消去
\033[1K カーソル位置から行頭まで消去
\033[2K カーソル位置の行を消去

カーソル移動

文字列 説明
\033[nA カーソルをn行上へ移動
\033[nB カーソルをn行下へ移動
\033[nC カーソルをn桁右へ移動
\033[nD カーソルをn桁左へ移動
\033[r;cH カーソルをr行、c列目へ移動

文字修飾

文字列 説明
\033[1m 強調(太字)
\033[4m 下線
\033[7m 文字色と背景色の反転
\033[0m 標準に戻す

文字色

文字列 説明
\033[30m
\033[31m
\033[32m
\033[33m 黄色
\033[34m
\033[35m マゼンタ
\033[36m シアン
\033[37m
\033[39m 標準色に戻す

背景色

文字列 説明
\033[40m
\033[41m
\033[42m
\033[43m 黄色
\033[44m
\033[45m マゼンタ
\033[46m シアン
\033[47m
\033[49m 標準色に戻す

マクロ

以下のようなマクロを定義しておくと便利かもしれない。

// 消去
#define CLR_SCR()       printf("\033[2J") // 全画面消去
#define CLR_RIGHT()     printf("\033[0K") // カーソル位置から行末まで消去
#define CLR_LEFT()      printf("\033[1K") // カーソル位置から行頭まで消去
#define CLR_LINE()      printf("\033[2K") // カーソル位置の行を消去

// カーソル移動
#define LOCATE(r,c)     printf("\033[%d;%dH", r, c)  //カーソル位置を移動

// 文字修飾
#define CHR_BOLD        "\033[1m"   // 太字
#define CHR_REVERT      "\033[7m"   // 文字色反転
#define CHR_RESET       "\033[0m"   // 通常に戻す

// 文字色
#define CHR_BLACK       "\033[30m"  //黒
#define CHR_RED         "\033[31m"  //赤
#define CHR_GREEN       "\033[32m"  //緑
#define CHR_YELLOW      "\033[33m"  //黄色
#define CHR_BLUE        "\033[34m"  //青
#define CHR_MAGENTA     "\033[35m"  //マゼンタ
#define CHR_CYAN        "\033[36m"  //シアン
#define CHR_WHITE       "\033[37m"  //白
#define CHR_NORMAL      "\033[39m"  //標準色に戻す

// 背景色
#define BG_BLACK        "\033[40m"  //黒
#define BG_RED          "\033[41m"  //赤
#define BG_GREEN        "\033[42m"  //緑
#define BG_YELLOW       "\033[43m"  //黄色
#define BG_BLUE         "\033[44m"  //青
#define BG_MAGENTA      "\033[45m"  //マゼンタ
#define BG_CYAN         "\033[46m"  //シアン
#define BG_WHITE        "\033[47m"  //白
#define BG_NORMAL       "\033[49m"  //標準色に戻す

使い方

上記のマクロ定義を esc.h としてインクルード。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "esc.h"

int main(void)
{
    int count = 0;

    CLR_SCR();
    LOCATE(1, 1);
    printf(CHR_BOLD CHR_GREEN "Escape Sequence Test");

    LOCATE(3, 1);
    printf(CHR_BLUE "count:  \n");
    printf(CHR_BLUE "random: \n");
    printf(CHR_RESET);
    fflush(stdout);

    while(1){
        LOCATE(3, 9); CLR_RIGHT();
        printf("%4d", count);
        LOCATE(4, 9); CLR_RIGHT();
        printf("%4d", rand() % 10000);
        printf("\n");
        count++;
        usleep(500 * 1000);
    }
    return 0;
}

VirtualBoxの共有フォルダの設定 (ゲストOS=Ubuntu)

VirtualBoxの共有フォルダの機能を利用し、ホストOS側のフォルダをゲストOSと共有する。

VirtualBox側の設定

仮想マシンの「設定」→「共有フォルダ」で共有フォルダを追加する。

  • 「フォルダのパス」にホストOS側のフォルダを指定する。
  • 「フォルダ名」にゲストOS側でのフォルダ名を指定する。
  • 「マウントポイント」は空欄でよい。
  • 「読み込み専用」のチェックははずす。
  • 「自動マウント」と「永続化する」はチェックする。


ゲストOS(Ubuntu)側の設定

どこにマウントされているか確認する。
例えば、VirtualBoxで共有フォルダ名を「shared」と設定すると、
/media/sf_shared にマウントされる。sf_ という接頭辞がつくことに注意。

$ mount | grep shared
shared on /media/sf_shared type vboxsf (rw,nodev,relatime,iocharset=utf8,uid=0,gid=998,dmode=0770,fmode=0770,tag=VBoxAutomounter)

$ ls -alF /media/ | grep shared
drwxrwx---   1 root vboxsf    0  5月 10 19:01 sf_shared/

このままではユーザグループ vboxsf にしかアクセス権限がないのでアクセスできない。
そこで自分のユーザ名をユーザグループ vboxsf に追加する。
コマンド実行後は、いったんログオフしてログインしなおすこと。

$ sudo usermod -aG vboxsf ユーザ名
または
$ sudo adduser ユーザ名 vboxsf

適当な場所にシンボリックリンクを作る。
例えばホームディレクトリの直下に shared という名前で作る。

$ ln -s /media/sf_shared ~/shared

ゲストOS側から共有フォルダのファイルの読み書き、ファイルの作成/削除ができることを確認する。

ヤマハのFM音源チップいろいろ

  • ヤマハFM音源チップは製品群の系統が複雑なので簡単にまとめる。
  • ここでは主要な型番のみを挙げるが、派生製品も多いことに注意。
  • OPL系、OPN系、OPM系の3つが有名。
  • その他に、OPX系、OPS系などがある。
  • これらとは別に着メロ用のMA系がある。

【表中の略語】
op=オペレータ, R = リズム, モノ = モノラル, ステ = ステレオ

OPL系

MSXSound Blaster で採用。

名称 型番 オペレータ FM ch 原波形 出力 その他 採用例
OPL YM3526 2 9
7+R4
6+R5
1 モノ アーケードゲーム
MSX-AUDIO Y8950 2 同上 1 モノ ADPCM 1ch MSX
拡張カートリッジ
OPL2 YM3812 2 同上 4 モノ Sound Blaster
OPLL YM2413 2 9
6+R5
2 モノ 音色内蔵 MSX
拡張カートリッジ
(MSX-MUSIC)
OPL3 YMF262-M 2/4 18(2op)
6(4op)+6(2op)
などモード多数
8 4ch Sound Blaster Pro2
OPL4 YMF278 2/4 同上 同上 同上 PCM 24ch YAMAHA SOUND EDGE

OPN系

多くの国産PC (PC-88, FM-77, MZ-2500, PC-98, FM TOWNS) で採用。

名称 型番 オペレータ FM ch 原波形 出力 その他 採用例
OPN YM2203 4 3 1 モノ PSG(SSG)3ch
ノイズ1ch
多くの国産PC(※)
OPNA YM2608 4 6+R6 1 ステ PSG(SSG)6ch
ADPCM1ch
ノイズ1ch
PC-88/98シリーズ
の後期機種/サウンドボード
OPNB YM2610 4 4 1 ステ PSG(SSG)3ch
ADPCM7ch
ノイズ1ch
ネオジオ
OPN2 YM2612 4 6 1 ステ FM TOWNSメガドライブ
OPN3 YMF288 4 6+R6 1 ステ PSG(SSG)3ch
ノイズ1ch
PC-9821

(※) PC-8800シリーズFM-77シリーズMZ-2500シリーズPC-9800シリーズなど

OPM系

X1、X68000 で採用。

名称 型番 オペレータ FM ch 原波形 出力 その他 採用例
OPM YM2151 4 8 1 ステ X1X68000
80~90年代のアーケードゲーム (※)
OPP YM2164 4 8 1 ステ DX21/DX27/DX100
などのシンセサイザー
OPZ YM2414 4 8 8 ステ DX11などのシンセサイザー

(※) ファンタジーゾーン源平討魔伝沙羅曼蛇ストリートファイターII 他多数

その他

名称 型番 オペレータ FM ch 原波形 出力 その他 採用例
OPX YMF271-F 2/3/4 9(全4op)
~18(全2op)
7+PCM 4ch PCM12ch アーケードゲーム
OPS YM2128 6 16 1 ステ DX7/DX1/DX5/TX216/TX816
などのシンセサイザー (※)
OPSII YM2604 6 16 1 ステ DX7IID/DX7IIFD/TX802
などのシンセサイザー (※)

(※) OPS系は外販されず、内部仕様は不明。

モバイルオーディオ(MA)系

携帯電話の着メロ用として開発。

名称 型番 オペレータ FM ch 原波形 出力 その他 採用例
MA-1 YMU757 2 4 2 ステ
MA-2 YMU759 2/4 16(2op)
8(4op)
8 ステ 4bitADPCM 1ch
PA-1 YMF761 同上 同上 同上 同上 同上 PalmOS
MA-3 YMU762 2/4 32(2op)
16(4op)
29 ステ WaveTable 8ch
MA-5 YMU765 2/4 32(2op)
16(4op)
29 ステ WaveTable 32ch
MA-7
(AudioEngine)
YMU786 - - - ステ WaveTable 128ch
SD-1 YMF825 4 16 29 モノ 中国市場向け家電用
電子工作キット用

備忘録:正規表現

これは、たまに正規表現を使うけどすぐ忘れてしまう人のメモです。
正規表現の基本については下記の記事を参照。

忘れがちなメタ文字6つ

他言語からの類推が働かず、どれがどれか忘れがちなメタ文字は以下の6つ。
無理やり語呂合わせでおぼえることにする。

メタ文字 機能 語呂合わせ
. 任意の1文字 全てに終止符
^ 行の先頭 頭にハット
$ 行の末尾 最後はカネ
* 繰り返し(0文字以上) ゼロからの米作り
+ 繰り返し(1文字以上) プラスは0を含まない
? 繰り返し(0文字か1文字) 有りや無しや?

3種類のカッコ

これはまあそんなに混乱しない。

カッコ 機能
[ ] 集合のどれか1文字
( ) 2文字以上をグループ化
{ } 直前の文字の繰り返し回数

メタ文字の基本

任意の1文字 .

ワイルドカードでは ? だが、正規表現では . であることに注意。

行の先頭 ^ と行の末尾 $

この2つはセットでおぼえる。
正規表現の 先頭 / 末尾 以外では通常の文字扱いとなることに注意。

直前の文字の繰り返し * + ?

この3つはセットでおぼえる。
* と ? はワイルドカードとは意味が異なることに注意。

エスケープ \

これは他言語でもおなじみ。

集合のどれか1文字 [ ] と 補集合のどれか1文字 [^ ]

^ は正規表現の先頭の場合(行の先頭)とは意味が異なるので注意。
こちらのほうが、C言語等のビットNOT演算子から類推できる。

2文字以上をグループ化 ( )

これは分かりやすい。

いずれかの文字列 |

これも分かりやすい。

直前の文字の繰り返し回数 { }

{n} は n文字、{n,} は n文字以上、{n,m} は n文字以上m文字以下。

エスケープシーケンス

メタ文字のエスケープと、おなじみの \n (改行) や \t (タブ) など以外に下記のようなものが使える。

  • \s : 空白文字 (半角スペース、タブ、改行)など ⇔ \S : それ以外の文字
  • \d ⇔ \D : 数字とそれ以外、\l ⇔ \L : 小文字とそれ以外、\u ⇔ \U : 大文字とそれ以外
  • \w ⇔ \W : 英数字+アンダースコア とそれ以外
  • \Q~\E : 囲まれた範囲はメタ文字を解釈しない

置換

( )でくくった部分を前から順に $1 $2 $3 ... で置換できる。
ただし、これには方言差があり、秀丸では \1 \2 \3 ... である。
秀丸は日本だからドルじゃなくて円とおぼえておく。(ただしサクラエディタは$ )

Haskellのモナドいろいろ (まとめ)

分類

  • 上記の記事で述べた8つのモナドは、2種類に分類できる。
  • 失敗系:List, Maybe, Either
  • 状態系:IO, ST, State, Reader, Writer
  • モナドには他にも種類があるが、まずはこの2種類を押さえておく。

失敗系 (List, Maybe, Either)

  • 失敗系モナドは、本来期待されるデータ型以外に、値が無い(失敗)という値がありうる。
  • そのため、エラー処理に利用できる。
  • 失敗系モナドは、計算が失敗する可能性がある関数を連鎖させることができる。
  • どこかで失敗した場合、その後の計算は行われず、最終結果は失敗になる。
  • List を失敗系と呼ぶのは違和感があるが、空リスト [] を値にとりうるのでここに含める。
  • Maybe は値が無い (Nothing) という値をとりうる。
  • Either は値が無いときにエラー情報の値を持たせることができる。

状態系 (IO, ST, State, Reader, Writer)

  • 状態系は、純粋関数型言語である Haskell で状態を扱うために使われる。
  • IO は入出力を扱う。また IORef は変更可能な変数のようなものとして利用できる。
  • IO の外に値を持ち出すことはできない。(IOがつきまとう。)
  • ST は外に値を持ち出すことができる。STRef は変更可能なローカル変数のようなものとして利用できる。
  • State は値だけでなく状態も持ち出せる。
  • State は連鎖した関数の間で状態を共有してグローバル変数のようなものとして利用できる。
  • Reader は読み取り専用の値を共有できる。グローバル定数のようなものとして利用できる。
  • Writer は追記専用の値を共有できる。ログを取るために使われることが多い。

ポエム

  • モナドジェネリック型のコンテナのようなもの」というのは本質的な理解ではないが、実践的な理解の足掛かりにはなる。
  • 「クラスは構造体の拡張のようなもの」というのは本質的な理解ではないが、実践的な理解の足掛かりにはなったのと同じ。
  • モナドブリトーのようなもの」みたいなよく分からない比喩は役に立たない。
  • 「クラスはたい焼きの型のようなもの」みたいなよく分からない比喩は役に立たなかったのと同じ。
  • そもそもブリトーなんて料理食べたことないから比喩として成立しない。
  • モナドは自己関手の圏におけるモノイド」という説明は、ブリトーと五十歩百歩。
  • あれは圏論の教科書の一節であって、群論の基礎知識があると想定される読者への説明。
  • モノイドなにそれおいしいのな人には何の説明にもならない。

Haskellのモナドいろいろ (4)

Readerモナド

Readerモナドは、読み取り専用のStateモナドのようなものである。
Stateモナドグローバル変数のようなものとして利用できたが、
Readerモナドはグローバル定数のようなものとして利用できる。
Stateモナドでは状態の取得にgetを用いたが、Readerモナドでは ask を用いる。
Stateモナドの runState に相当するのは runReader だが、状態は変化しないので値のみ返す。

import Control.Monad.Reader

calcTax :: Int -> Reader Int Int
calcTax x = do
    tax <- ask
    let y = (x * (100 + tax)) `div` 100
    return y

main :: IO ()
main = do
    print $ (`runReader` 8) $ do
        a <- calcTax 100
        b <- calcTax 500
        c <- calcTax 1000
        return (a, b, c) -- (108,540,1080)
    print $ (`runReader` 10) $ do
        a <- calcTax 100
        b <- calcTax 500
        c <- calcTax 1000
        return (a, b, c) -- (110,550,1100)

モナドだから return で値を中に入れることができる。

import Control.Monad.Reader

hoge :: Int -> Reader String Int -- 状態: String, 値: Int
hoge x = return x

main :: IO ()
main = do
    let ma = hoge 5
    let a = runReader ma "hoge"
    print a -- 5    ※ Stateとは異なり、状態は返さない

モナドだから join で一皮むくことができるが、 join で裸にはできない。

import Control.Monad
import Control.Monad.Reader

hoge :: Int -> Reader s Int
hoge x = return x

main :: IO ()
main = do
    let ma  = hoge 5 :: Reader String Int
    let mma = return ma :: Reader String (Reader String Int)
    let ma' = join mma -- Reader String Int
--  let a'  = join ma'  -- エラー
    let a'  = runReader ma' "hoge"
    print a' -- 5

モナドだから fmap で中の値を関数に渡すことができる。

import Control.Monad.Reader

hoge :: Int -> Reader String Int
hoge x = return x

main :: IO ()
main = do
    let ma = hoge 5 :: Reader String Int
    let mb = fmap (*2) ma
    let b = runReader mb "hoge"
    print b -- 10

モナドだから bind ( >>= ) で中の値を、モナドを返す関数に渡すことができる。

import Control.Monad.Reader

hoge :: Int -> Reader String Int
hoge x = return (x * 2)

main :: IO ()
main = do
    let ma = hoge 5
    let mb = ma >>= hoge
    let a = runReader ma "hoge"
    let b = runReader mb "piyo"
    print a -- 10
    print b -- 20

またすでに見た通り、モナドだから do構文が使える。

Writerモナド

Writerモナドは、追記専用のStateモナドのようなものである。
値の更新ではなく追記であることに注意。主にリストに追加して使う。
ログを取るために使われることが多い。
Stateモナドでは状態の更新に put / modify を用いたが、Writerモナドでは tell を用いる。
Stateモナドの runState, execState に相当するのは runWriter, execWriter である。

import Control.Monad.Writer

main :: IO ()
main = do
    let s = execWriter $ do
            tell "hoge\n"
            tell "piyo\n"
            tell "huga\n"
            return ()
    putStrLn s

モナドだから return で値を中に入れることができる。

import Control.Monad.Writer

hoge :: Int -> Writer String Int -- 状態: String, 値: Int
hoge x = return x

main :: IO ()
main = do
    let ma = hoge 5
    let a = runWriter ma -- 初期値が無いことに注意
    print a -- (5,"")

モナドだから join で一皮むくことができるが、 join で裸にはできない。

import Control.Monad
import Control.Monad.Writer

hoge :: Int -> Writer String Int
hoge x = return x

main :: IO ()
main = do
    let ma  = hoge 5 :: Writer String Int
    let mma = return ma :: Writer String (Writer String Int)
    let ma' = join mma -- Writer String Int
--  let a'  = join ma'  -- エラー
    let a'  = runWriter ma'
    print a' -- (5,"")

モナドだから fmap で中の値を関数に渡すことができる。

import Control.Monad.Writer

hoge :: Int -> Writer String Int
hoge x = return x

main :: IO ()
main = do
    let ma = hoge 5 :: Writer String Int
    let mb = fmap (*2) ma
    let b = runWriter mb
    print b -- (10,"")

モナドだから bind ( >>= ) で中の値を、モナドを返す関数に渡すことができる。

import Control.Monad.Writer

hoge :: Int -> Writer String Int
hoge x = return (x * 2)

main :: IO ()
main = do
    let ma = hoge 5
    let mb = ma >>= hoge
    let a = runWriter ma
    let b = runWriter mb
    print a -- (10,"")
    print b -- (20,"")

またすでに見た通り、モナドだから do構文が使える。