C言語でログ出力のレベル分け

  • 下記のようなマクロを使用してログ出力のレベルを制御する。
  • さらに細かく、FATAL(致命的なエラー)、ERROR(エラー)、WARN(警告)、INFO(情報)、DEBUG(デバッグ情報)、TRACE(トレース情報)のように分けてもよい。
// デバッグ出力
#define DEBUG_LOG_LEVEL  2

#if DEBUG_LOG_LEVEL >= 4
#define DEBUG_LOG_TRACE(fmt, ...) printf("[TRACE]" fmt, ##__VA_ARGS__)
#else
#define DEBUG_LOG_TRACE(fmt, ...) /* 何もしない */
#endif
#if DEBUG_LOG_LEVEL >= 3
#define DEBUG_LOG_DEBUG(fmt, ...) printf("[DEBUG]" fmt, ##__VA_ARGS__)
#else
#define DEBUG_LOG_DEBUG(fmt, ...) /* 何もしない */
#endif
#if DEBUG_LOG_LEVEL >= 2
#define DEBUG_LOG_INFO(fmt, ...) printf("[INFO]" fmt, ##__VA_ARGS__)
#else
#define DEBUG_LOG_INFO(fmt, ...) /* 何もしない */
#endif
#if DEBUG_LOG_LEVEL >= 1
#define DEBUG_LOG_ERROR(fmt, ...) printf("[ERROR]" fmt, ##__VA_ARGS__)
#else
#define DEBUG_LOG_ERROR(fmt, ...) /* 何もしない */
#endif

Linuxで kbhit() と getch()

やりたいこと

  • WindowsC言語では kbhit() と getch() を使ってキーボード入力を即時に取得できる。
  • LinuxC言語ではこれらの関数は用意されておらず、キーボード入力は1行ごとにバッファされ、Enterキーが入力されるまで取得できない。
  • Linuxでも kbhit() と getch() 相当の機能を実現したい。

ちなみに、Windowsでも現在では kbhit() と getch() は非推奨の関数名で、代わって _kbhit() と _getch() を使う。

#include <stdio.h>
#include <conio.h>

int main()
{
    while (1) {
        while (!_kbhit()) {;}
        char ch = _getch();
        printf("kbhit: %c\n", ch);
        if (ch == 'q') break;
    }
    printf("\nQuit\n");
    return 0;
}

方法

  • 方法はいろいろあるが、基本的には tcgetattr() / tcsetattr() でターミナルの設定を変更する。
  • 後述の参考サイトのソースを参考に実装した。
  • 処理の無駄を省いたかわり、最初に kb_begin()、最後に kb_end() を実行する。
  • 無駄を省いた作りにしたので使い方を間違えないように注意。
  • スレッドセーフでない作りだが、そもそもキーボード入力を複数のスレッドから受けることはないはず。

ソース

kbhit.h

#pragma once

void kb_begin(void);
void kb_end(void);
int  kbhit(void);
char getch(void);

kbhit.c

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

#include "kbhit.h"

static struct termios g_old_set; // ターミナルの元の設定
static int g_read_char = -1;     // 読みだした文字 (-1のとき無効)

// 開始する
// (標準入力を、即時、エコーバックなし、ノンブロッキングに設定)
void kb_begin(void)
{
    // 現在の標準入力の設定を取得
    tcgetattr(STDIN_FILENO, &g_old_set);
    // 標準入力の設定を変更
    struct termios new_set = g_old_set;
    new_set.c_lflag &= ~ICANON; // 非カノニカル(改行を待たず即時入力)
    new_set.c_lflag &= ~ECHO;   // エコーバックなし
//  new_set.c_lflag &= ~ISIG;   // シグナル発生なし (Ctrl-Cなどの)
    new_set.c_cc[VMIN] = 0;     // 待ち受けしない (待ち受け文字数ゼロ)
    new_set.c_cc[VTIME] = 0;    // 待ち受けしない (待ち受け時間ゼロ)
    tcsetattr(STDIN_FILENO, TCSANOW, &new_set);
}

// 終了する
// (キーボード入力の設定を元に戻す)
void kb_end(void)
{
    // ターミナルの設定を戻す
    tcsetattr(STDIN_FILENO, TCSANOW, &g_old_set);
}

// キーボード入力があったかチェック
// return: 0:なし / 1:あり
int  kbhit(void)
{
    if(g_read_char != -1) return 1; // すでに入力があった
    
    // 標準入力から1文字読み出し
    char ch;
    int num = read(STDIN_FILENO, &ch, 1);
    // 読み出せたか?
    if(num == 1) {
        g_read_char = ch;
        return 1;
    }else{
        return 0;
    }
}

// キーボード入力を1文字取得
char getch(void)
{
    // すでに読みだした文字があればそれを返す
    if(g_read_char != -1) {
       char ch = g_read_char;
       g_read_char = -1;
       return (ch);
    }
    // なければ1文字読み出す (読み出せなければゼロを返す)
    else{
        char ch = 0;
        read(STDIN_FILENO, &ch, 1);
        return(ch);
    }
}

使い方

main.c

#include <stdio.h>
#include "kbhit.h"

int main()
{
    kb_begin();

    while (1) {
        while (!kbhit()) {;}
        char ch = getch();
        printf("kbhit: %c\n", ch);
        if (ch == 'q') break;
    }
    kb_end();

    printf("\nQuit\n");
    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.13)
project(kbhit_test C)
add_executable(kbhit_test main.c kbhit.c)

参考


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 ... である。
秀丸は日本だからドルじゃなくて円とおぼえておく。(ただしサクラエディタは$ )